Counters.java
上传用户:quxuerui
上传日期:2018-01-08
资源大小:41811k
文件大小:22k
源码类别:

网格计算

开发平台:

Java

  1. /**
  2.  * Licensed to the Apache Software Foundation (ASF) under one
  3.  * or more contributor license agreements.  See the NOTICE file
  4.  * distributed with this work for additional information
  5.  * regarding copyright ownership.  The ASF licenses this file
  6.  * to you under the Apache License, Version 2.0 (the
  7.  * "License"); you may not use this file except in compliance
  8.  * with the License.  You may obtain a copy of the License at
  9.  *
  10.  *     http://www.apache.org/licenses/LICENSE-2.0
  11.  *
  12.  * Unless required by applicable law or agreed to in writing, software
  13.  * distributed under the License is distributed on an "AS IS" BASIS,
  14.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15.  * See the License for the specific language governing permissions and
  16.  * limitations under the License.
  17.  */
  18. package org.apache.hadoop.mapred;
  19. import java.io.DataInput;
  20. import java.io.DataOutput;
  21. import java.io.IOException;
  22. import java.text.ParseException;
  23. import java.util.ArrayList;
  24. import java.util.Collection;
  25. import java.util.HashMap;
  26. import java.util.IdentityHashMap;
  27. import java.util.Iterator;
  28. import java.util.Map;
  29. import java.util.MissingResourceException;
  30. import java.util.ResourceBundle;
  31. import org.apache.commons.logging.*;
  32. import org.apache.hadoop.io.IntWritable;
  33. import org.apache.hadoop.io.Text;
  34. import org.apache.hadoop.io.Writable;
  35. import org.apache.hadoop.io.WritableUtils;
  36. import org.apache.hadoop.util.StringUtils;
  37. /**
  38.  * A set of named counters.
  39.  * 
  40.  * <p><code>Counters</code> represent global counters, defined either by the 
  41.  * Map-Reduce framework or applications. Each <code>Counter</code> can be of
  42.  * any {@link Enum} type.</p>
  43.  * 
  44.  * <p><code>Counters</code> are bunched into {@link Group}s, each comprising of
  45.  * counters from a particular <code>Enum</code> class. 
  46.  * @deprecated Use {@link org.apache.hadoop.mapreduce.Counters} instead.
  47.  */
  48. @Deprecated
  49. public class Counters implements Writable, Iterable<Counters.Group> {
  50.   private static final Log LOG = LogFactory.getLog(Counters.class);
  51.   private static final char GROUP_OPEN = '{';
  52.   private static final char GROUP_CLOSE = '}';
  53.   private static final char COUNTER_OPEN = '[';
  54.   private static final char COUNTER_CLOSE = ']';
  55.   private static final char UNIT_OPEN = '(';
  56.   private static final char UNIT_CLOSE = ')';
  57.   private static char[] charsToEscape =  {GROUP_OPEN, GROUP_CLOSE, 
  58.                                           COUNTER_OPEN, COUNTER_CLOSE, 
  59.                                           UNIT_OPEN, UNIT_CLOSE};
  60.   
  61.   //private static Log log = LogFactory.getLog("Counters.class");
  62.   
  63.   /**
  64.    * A counter record, comprising its name and value. 
  65.    */
  66.   public static class Counter extends org.apache.hadoop.mapreduce.Counter {
  67.     
  68.     Counter() { 
  69.     }
  70.     Counter(String name, String displayName, long value) {
  71.       super(name, displayName);
  72.       increment(value);
  73.     }
  74.     
  75.     public void setDisplayName(String newName) {
  76.       super.setDisplayName(newName);
  77.     }
  78.     
  79.     /**
  80.      * Returns the compact stringified version of the counter in the format
  81.      * [(actual-name)(display-name)(value)]
  82.      */
  83.     public synchronized String makeEscapedCompactString() {
  84.       StringBuffer buf = new StringBuffer();
  85.       buf.append(COUNTER_OPEN);
  86.       
  87.       // Add the counter name
  88.       buf.append(UNIT_OPEN);
  89.       buf.append(escape(getName()));
  90.       buf.append(UNIT_CLOSE);
  91.       
  92.       // Add the display name
  93.       buf.append(UNIT_OPEN);
  94.       buf.append(escape(getDisplayName()));
  95.       buf.append(UNIT_CLOSE);
  96.       
  97.       // Add the value
  98.       buf.append(UNIT_OPEN);
  99.       buf.append(this.getValue());
  100.       buf.append(UNIT_CLOSE);
  101.       
  102.       buf.append(COUNTER_CLOSE);
  103.       
  104.       return buf.toString();
  105.     }
  106.     
  107.     // Checks for (content) equality of two (basic) counters
  108.     @Deprecated
  109.     synchronized boolean contentEquals(Counter c) {
  110.       return this.equals(c);
  111.     }
  112.     
  113.     /**
  114.      * What is the current value of this counter?
  115.      * @return the current value
  116.      */
  117.     public synchronized long getCounter() {
  118.       return getValue();
  119.     }
  120.     
  121.   }
  122.   
  123.   /**
  124.    *  <code>Group</code> of counters, comprising of counters from a particular 
  125.    *  counter {@link Enum} class.  
  126.    *
  127.    *  <p><code>Group</code>handles localization of the class name and the 
  128.    *  counter names.</p>
  129.    */
  130.   public static class Group implements Writable, Iterable<Counter> {
  131.     private String groupName;
  132.     private String displayName;
  133.     private Map<String, Counter> subcounters = new HashMap<String, Counter>();
  134.     
  135.     // Optional ResourceBundle for localization of group and counter names.
  136.     private ResourceBundle bundle = null;    
  137.     
  138.     Group(String groupName) {
  139.       try {
  140.         bundle = getResourceBundle(groupName);
  141.       }
  142.       catch (MissingResourceException neverMind) {
  143.       }
  144.       this.groupName = groupName;
  145.       this.displayName = localize("CounterGroupName", groupName);
  146.       LOG.debug("Creating group " + groupName + " with " +
  147.                (bundle == null ? "nothing" : "bundle"));
  148.     }
  149.     
  150.     /**
  151.      * Returns the specified resource bundle, or throws an exception.
  152.      * @throws MissingResourceException if the bundle isn't found
  153.      */
  154.     private static ResourceBundle getResourceBundle(String enumClassName) {
  155.       String bundleName = enumClassName.replace('$','_');
  156.       return ResourceBundle.getBundle(bundleName);
  157.     }
  158.     
  159.     /**
  160.      * Returns raw name of the group.  This is the name of the enum class
  161.      * for this group of counters.
  162.      */
  163.     public String getName() {
  164.       return groupName;
  165.     }
  166.     
  167.     /**
  168.      * Returns localized name of the group.  This is the same as getName() by
  169.      * default, but different if an appropriate ResourceBundle is found.
  170.      */
  171.     public String getDisplayName() {
  172.       return displayName;
  173.     }
  174.     
  175.     /**
  176.      * Set the display name
  177.      */
  178.     public void setDisplayName(String displayName) {
  179.       this.displayName = displayName;
  180.     }
  181.     
  182.     /**
  183.      * Returns the compact stringified version of the group in the format
  184.      * {(actual-name)(display-name)(value)[][][]} where [] are compact strings for the
  185.      * counters within.
  186.      */
  187.     public String makeEscapedCompactString() {
  188.       StringBuffer buf = new StringBuffer();
  189.       buf.append(GROUP_OPEN); // group start
  190.       
  191.       // Add the group name
  192.       buf.append(UNIT_OPEN);
  193.       buf.append(escape(getName()));
  194.       buf.append(UNIT_CLOSE);
  195.       
  196.       // Add the display name
  197.       buf.append(UNIT_OPEN);
  198.       buf.append(escape(getDisplayName()));
  199.       buf.append(UNIT_CLOSE);
  200.       
  201.       // write the value
  202.       for(Counter counter: subcounters.values()) {
  203.         buf.append(counter.makeEscapedCompactString());
  204.       }
  205.       
  206.       buf.append(GROUP_CLOSE); // group end
  207.       return buf.toString();
  208.     }
  209.     @Override
  210.     public int hashCode() {
  211.       return subcounters.hashCode();
  212.     }
  213.     /** 
  214.      * Checks for (content) equality of Groups
  215.      */
  216.     @Override
  217.     public synchronized boolean equals(Object obj) {
  218.       boolean isEqual = false;
  219.       if (obj != null && obj instanceof Group) {
  220.         Group g = (Group) obj;
  221.         if (size() == g.size()) {
  222.           isEqual = true;
  223.           for (Map.Entry<String, Counter> entry : subcounters.entrySet()) {
  224.             String key = entry.getKey();
  225.             Counter c1 = entry.getValue();
  226.             Counter c2 = g.getCounterForName(key);
  227.             if (!c1.contentEquals(c2)) {
  228.               isEqual = false;
  229.               break;
  230.             }
  231.           }
  232.         }
  233.       }
  234.       return isEqual;
  235.     }
  236.     
  237.     /**
  238.      * Returns the value of the specified counter, or 0 if the counter does
  239.      * not exist.
  240.      */
  241.     public synchronized long getCounter(String counterName) {
  242.       for(Counter counter: subcounters.values()) {
  243.         if (counter != null && counter.getDisplayName().equals(counterName)) {
  244.           return counter.getValue();
  245.         }
  246.       }
  247.       return 0L;
  248.     }
  249.     
  250.     /**
  251.      * Get the counter for the given id and create it if it doesn't exist.
  252.      * @param id the numeric id of the counter within the group
  253.      * @param name the internal counter name
  254.      * @return the counter
  255.      * @deprecated use {@link #getCounter(String)} instead
  256.      */
  257.     @Deprecated
  258.     public synchronized Counter getCounter(int id, String name) {
  259.       return getCounterForName(name);
  260.     }
  261.     
  262.     /**
  263.      * Get the counter for the given name and create it if it doesn't exist.
  264.      * @param name the internal counter name
  265.      * @return the counter
  266.      */
  267.     public synchronized Counter getCounterForName(String name) {
  268.       Counter result = subcounters.get(name);
  269.       if (result == null) {
  270.         LOG.debug("Adding " + name);
  271.         result = new Counter(name, localize(name + ".name", name), 0L);
  272.         subcounters.put(name, result);
  273.       }
  274.       return result;
  275.     }
  276.     
  277.     /**
  278.      * Returns the number of counters in this group.
  279.      */
  280.     public synchronized int size() {
  281.       return subcounters.size();
  282.     }
  283.     
  284.     /**
  285.      * Looks up key in the ResourceBundle and returns the corresponding value.
  286.      * If the bundle or the key doesn't exist, returns the default value.
  287.      */
  288.     private String localize(String key, String defaultValue) {
  289.       String result = defaultValue;
  290.       if (bundle != null) {
  291.         try {
  292.           result = bundle.getString(key);
  293.         }
  294.         catch (MissingResourceException mre) {
  295.         }
  296.       }
  297.       return result;
  298.     }
  299.     
  300.     public synchronized void write(DataOutput out) throws IOException {
  301.       Text.writeString(out, displayName);
  302.       WritableUtils.writeVInt(out, subcounters.size());
  303.       for(Counter counter: subcounters.values()) {
  304.         counter.write(out);
  305.       }
  306.     }
  307.     
  308.     public synchronized void readFields(DataInput in) throws IOException {
  309.       displayName = Text.readString(in);
  310.       subcounters.clear();
  311.       int size = WritableUtils.readVInt(in);
  312.       for(int i=0; i < size; i++) {
  313.         Counter counter = new Counter();
  314.         counter.readFields(in);
  315.         subcounters.put(counter.getName(), counter);
  316.       }
  317.     }
  318.     public synchronized Iterator<Counter> iterator() {
  319.       return new ArrayList<Counter>(subcounters.values()).iterator();
  320.     }
  321.   }
  322.   
  323.   // Map from group name (enum class name) to map of int (enum ordinal) to
  324.   // counter record (name-value pair).
  325.   private Map<String,Group> counters = new HashMap<String, Group>();
  326.   /**
  327.    * A cache from enum values to the associated counter. Dramatically speeds up
  328.    * typical usage.
  329.    */
  330.   private Map<Enum, Counter> cache = new IdentityHashMap<Enum, Counter>();
  331.   
  332.   /**
  333.    * Returns the names of all counter classes.
  334.    * @return Set of counter names.
  335.    */
  336.   public synchronized Collection<String> getGroupNames() {
  337.     return counters.keySet();
  338.   }
  339.   public synchronized Iterator<Group> iterator() {
  340.     return counters.values().iterator();
  341.   }
  342.   /**
  343.    * Returns the named counter group, or an empty group if there is none
  344.    * with the specified name.
  345.    */
  346.   public synchronized Group getGroup(String groupName) {
  347.     Group result = counters.get(groupName);
  348.     if (result == null) {
  349.       result = new Group(groupName);
  350.       counters.put(groupName, result);
  351.     }
  352.     return result;
  353.   }
  354.   /**
  355.    * Find the counter for the given enum. The same enum will always return the
  356.    * same counter.
  357.    * @param key the counter key
  358.    * @return the matching counter object
  359.    */
  360.   public synchronized Counter findCounter(Enum key) {
  361.     Counter counter = cache.get(key);
  362.     if (counter == null) {
  363.       Group group = getGroup(key.getDeclaringClass().getName());
  364.       counter = group.getCounterForName(key.toString());
  365.       cache.put(key, counter);
  366.     }
  367.     return counter;    
  368.   }
  369.   /**
  370.    * Find a counter given the group and the name.
  371.    * @param group the name of the group
  372.    * @param name the internal name of the counter
  373.    * @return the counter for that name
  374.    */
  375.   public synchronized Counter findCounter(String group, String name) {
  376.     return getGroup(group).getCounterForName(name);
  377.   }
  378.   /**
  379.    * Find a counter by using strings
  380.    * @param group the name of the group
  381.    * @param id the id of the counter within the group (0 to N-1)
  382.    * @param name the internal name of the counter
  383.    * @return the counter for that name
  384.    * @deprecated
  385.    */
  386.   @Deprecated
  387.   public synchronized Counter findCounter(String group, int id, String name) {
  388.     return getGroup(group).getCounterForName(name);
  389.   }
  390.   /**
  391.    * Increments the specified counter by the specified amount, creating it if
  392.    * it didn't already exist.
  393.    * @param key identifies a counter
  394.    * @param amount amount by which counter is to be incremented
  395.    */
  396.   public synchronized void incrCounter(Enum key, long amount) {
  397.     findCounter(key).increment(amount);
  398.   }
  399.   
  400.   /**
  401.    * Increments the specified counter by the specified amount, creating it if
  402.    * it didn't already exist.
  403.    * @param group the name of the group
  404.    * @param counter the internal name of the counter
  405.    * @param amount amount by which counter is to be incremented
  406.    */
  407.   public synchronized void incrCounter(String group, String counter, long amount) {
  408.     getGroup(group).getCounterForName(counter).increment(amount);
  409.   }
  410.   
  411.   /**
  412.    * Returns current value of the specified counter, or 0 if the counter
  413.    * does not exist.
  414.    */
  415.   public synchronized long getCounter(Enum key) {
  416.     return findCounter(key).getValue();
  417.   }
  418.   
  419.   /**
  420.    * Increments multiple counters by their amounts in another Counters 
  421.    * instance.
  422.    * @param other the other Counters instance
  423.    */
  424.   public synchronized void incrAllCounters(Counters other) {
  425.     for (Group otherGroup: other) {
  426.       Group group = getGroup(otherGroup.getName());
  427.       group.displayName = otherGroup.displayName;
  428.       for (Counter otherCounter : otherGroup) {
  429.         Counter counter = group.getCounterForName(otherCounter.getName());
  430.         counter.setDisplayName(otherCounter.getDisplayName());
  431.         counter.increment(otherCounter.getValue());
  432.       }
  433.     }
  434.   }
  435.   /**
  436.    * Convenience method for computing the sum of two sets of counters.
  437.    */
  438.   public static Counters sum(Counters a, Counters b) {
  439.     Counters counters = new Counters();
  440.     counters.incrAllCounters(a);
  441.     counters.incrAllCounters(b);
  442.     return counters;
  443.   }
  444.   
  445.   /**
  446.    * Returns the total number of counters, by summing the number of counters
  447.    * in each group.
  448.    */
  449.   public synchronized  int size() {
  450.     int result = 0;
  451.     for (Group group : this) {
  452.       result += group.size();
  453.     }
  454.     return result;
  455.   }
  456.   
  457.   /**
  458.    * Write the set of groups.
  459.    * The external format is:
  460.    *     #groups (groupName group)*
  461.    *
  462.    * i.e. the number of groups followed by 0 or more groups, where each 
  463.    * group is of the form:
  464.    *
  465.    *     groupDisplayName #counters (false | true counter)*
  466.    *
  467.    * where each counter is of the form:
  468.    *
  469.    *     name (false | true displayName) value
  470.    */
  471.   public synchronized void write(DataOutput out) throws IOException {
  472.     out.writeInt(counters.size());
  473.     for (Group group: counters.values()) {
  474.       Text.writeString(out, group.getName());
  475.       group.write(out);
  476.     }
  477.   }
  478.   
  479.   /**
  480.    * Read a set of groups.
  481.    */
  482.   public synchronized void readFields(DataInput in) throws IOException {
  483.     int numClasses = in.readInt();
  484.     counters.clear();
  485.     while (numClasses-- > 0) {
  486.       String groupName = Text.readString(in);
  487.       Group group = new Group(groupName);
  488.       group.readFields(in);
  489.       counters.put(groupName, group);
  490.     }
  491.   }
  492.   
  493.   /**
  494.    * Logs the current counter values.
  495.    * @param log The log to use.
  496.    */
  497.   public void log(Log log) {
  498.     log.info("Counters: " + size());
  499.     for(Group group: this) {
  500.       log.info("  " + group.getDisplayName());
  501.       for (Counter counter: group) {
  502.         log.info("    " + counter.getDisplayName() + "=" + 
  503.                  counter.getCounter());
  504.       }   
  505.     }
  506.   }
  507.   
  508.   /**
  509.    * Return textual representation of the counter values.
  510.    */
  511.   public synchronized String toString() {
  512.     StringBuilder sb = new StringBuilder("Counters: " + size());
  513.     for (Group group: this) {
  514.       sb.append("nt" + group.getDisplayName());
  515.       for (Counter counter: group) {
  516.         sb.append("ntt" + counter.getDisplayName() + "=" + 
  517.                   counter.getCounter());
  518.       }
  519.     }
  520.     return sb.toString();
  521.   }
  522.   /**
  523.    * Convert a counters object into a single line that is easy to parse.
  524.    * @return the string with "name=value" for each counter and separated by ","
  525.    */
  526.   public synchronized String makeCompactString() {
  527.     StringBuffer buffer = new StringBuffer();
  528.     boolean first = true;
  529.     for(Group group: this){   
  530.       for(Counter counter: group) {
  531.         if (first) {
  532.           first = false;
  533.         } else {
  534.           buffer.append(',');
  535.         }
  536.         buffer.append(group.getDisplayName());
  537.         buffer.append('.');
  538.         buffer.append(counter.getDisplayName());
  539.         buffer.append(':');
  540.         buffer.append(counter.getCounter());
  541.       }
  542.     }
  543.     return buffer.toString();
  544.   }
  545.   
  546.   /**
  547.    * Represent the counter in a textual format that can be converted back to 
  548.    * its object form
  549.    * @return the string in the following format
  550.    * {(groupname)(group-displayname)[(countername)(displayname)(value)][][]}{}{}
  551.    */
  552.   public synchronized String makeEscapedCompactString() {
  553.     StringBuffer buffer = new StringBuffer();
  554.     for(Group group: this){
  555.       buffer.append(group.makeEscapedCompactString());
  556.     }
  557.     return buffer.toString();
  558.   }
  559.   // Extracts a block (data enclosed within delimeters) ignoring escape 
  560.   // sequences. Throws ParseException if an incomplete block is found else 
  561.   // returns null.
  562.   private static String getBlock(String str, char open, char close, 
  563.                                 IntWritable index) throws ParseException {
  564.     StringBuilder split = new StringBuilder();
  565.     int next = StringUtils.findNext(str, open, StringUtils.ESCAPE_CHAR, 
  566.                                     index.get(), split);
  567.     split.setLength(0); // clear the buffer
  568.     if (next >= 0) {
  569.       ++next; // move over '('
  570.       
  571.       next = StringUtils.findNext(str, close, StringUtils.ESCAPE_CHAR, 
  572.                                    next, split);
  573.       if (next >= 0) {
  574.         ++next; // move over ')'
  575.         index.set(next);
  576.         return split.toString(); // found a block
  577.       } else {
  578.         throw new ParseException("Unexpected end of block", next);
  579.       }
  580.     }
  581.     return null; // found nothing
  582.   }
  583.   
  584.   /**
  585.    * Convert a stringified counter representation into a counter object. Note 
  586.    * that the counter can be recovered if its stringified using 
  587.    * {@link #makeEscapedCompactString()}. 
  588.    * @return a Counter
  589.    */
  590.   public static Counters fromEscapedCompactString(String compactString) 
  591.   throws ParseException {
  592.     Counters counters = new Counters();
  593.     IntWritable index = new IntWritable(0);
  594.     
  595.     // Get the group to work on
  596.     String groupString = 
  597.       getBlock(compactString, GROUP_OPEN, GROUP_CLOSE, index);
  598.     
  599.     while (groupString != null) {
  600.       IntWritable groupIndex = new IntWritable(0);
  601.       
  602.       // Get the actual name
  603.       String groupName = 
  604.         getBlock(groupString, UNIT_OPEN, UNIT_CLOSE, groupIndex);
  605.       groupName = unescape(groupName);
  606.       
  607.       // Get the display name
  608.       String groupDisplayName = 
  609.         getBlock(groupString, UNIT_OPEN, UNIT_CLOSE, groupIndex);
  610.       groupDisplayName = unescape(groupDisplayName);
  611.       
  612.       // Get the counters
  613.       Group group = counters.getGroup(groupName);
  614.       group.setDisplayName(groupDisplayName);
  615.       
  616.       String counterString = 
  617.         getBlock(groupString, COUNTER_OPEN, COUNTER_CLOSE, groupIndex);
  618.       
  619.       while (counterString != null) {
  620.         IntWritable counterIndex = new IntWritable(0);
  621.         
  622.         // Get the actual name
  623.         String counterName = 
  624.           getBlock(counterString, UNIT_OPEN, UNIT_CLOSE, counterIndex);
  625.         counterName = unescape(counterName);
  626.         
  627.         // Get the display name
  628.         String counterDisplayName = 
  629.           getBlock(counterString, UNIT_OPEN, UNIT_CLOSE, counterIndex);
  630.         counterDisplayName = unescape(counterDisplayName);
  631.         
  632.         // Get the value
  633.         long value = 
  634.           Long.parseLong(getBlock(counterString, UNIT_OPEN, UNIT_CLOSE, 
  635.                                   counterIndex));
  636.         
  637.         // Add the counter
  638.         Counter counter = group.getCounterForName(counterName);
  639.         counter.setDisplayName(counterDisplayName);
  640.         counter.increment(value);
  641.         
  642.         // Get the next counter
  643.         counterString = 
  644.           getBlock(groupString, COUNTER_OPEN, COUNTER_CLOSE, groupIndex);
  645.       }
  646.       
  647.       groupString = getBlock(compactString, GROUP_OPEN, GROUP_CLOSE, index);
  648.     }
  649.     return counters;
  650.   }
  651.   // Escapes all the delimiters for counters i.e {,[,(,),],}
  652.   private static String escape(String string) {
  653.     return StringUtils.escapeString(string, StringUtils.ESCAPE_CHAR, 
  654.                                     charsToEscape);
  655.   }
  656.   
  657.   // Unescapes all the delimiters for counters i.e {,[,(,),],}
  658.   private static String unescape(String string) {
  659.     return StringUtils.unEscapeString(string, StringUtils.ESCAPE_CHAR, 
  660.                                       charsToEscape);
  661.   }
  662.   @Override 
  663.   public synchronized int hashCode() {
  664.     return counters.hashCode();
  665.   }
  666.   @Override
  667.   public synchronized boolean equals(Object obj) {
  668.     boolean isEqual = false;
  669.     if (obj != null && obj instanceof Counters) {
  670.       Counters other = (Counters) obj;
  671.       if (size() == other.size()) {
  672.         isEqual = true;
  673.         for (Map.Entry<String, Group> entry : this.counters.entrySet()) {
  674.           String key = entry.getKey();
  675.           Group sourceGroup = entry.getValue();
  676.           Group targetGroup = other.getGroup(key);
  677.           if (!sourceGroup.equals(targetGroup)) {
  678.             isEqual = false;
  679.             break;
  680.           }
  681.         }
  682.       }
  683.     }
  684.     return isEqual;
  685.   }
  686. }