org.chromium.debug.core/src/org/chromium/debug/core/util/JsValueStringifier.java
changeset 2 e4420d2515f1
equal deleted inserted replaced
1:ef76fc2ac88c 2:e4420d2515f1
       
     1 // Copyright (c) 2009 The Chromium Authors. All rights reserved.
       
     2 // Use of this source code is governed by a BSD-style license that can be
       
     3 // found in the LICENSE file.
       
     4 
       
     5 package org.chromium.debug.core.util;
       
     6 
       
     7 import java.util.Collection;
       
     8 import java.util.Map;
       
     9 import java.util.SortedMap;
       
    10 
       
    11 import org.chromium.sdk.JsArray;
       
    12 import org.chromium.sdk.JsObject;
       
    13 import org.chromium.sdk.JsValue;
       
    14 import org.chromium.sdk.JsVariable;
       
    15 import org.chromium.sdk.JsValue.Type;
       
    16 
       
    17 /**
       
    18  * A converter of JsValues into human-readable strings used in various contexts.
       
    19  */
       
    20 public class JsValueStringifier {
       
    21 
       
    22   /**
       
    23    * A configuration class
       
    24    */
       
    25   public static class Config {
       
    26 
       
    27     /**
       
    28      * The maximum length of the text to render. If the value length exceeds
       
    29      * the limit, an ellipsis will be used to truncate the value.
       
    30      * The default is 80 characters.
       
    31      */
       
    32     public int maxLength = 80;
       
    33   }
       
    34 
       
    35   private static final String ELLIPSIS = "..."; //$NON-NLS-1$
       
    36 
       
    37   private static final String UNKNOWN_VALUE = "<null>"; //$NON-NLS-1$
       
    38 
       
    39   private final Config config;
       
    40 
       
    41 
       
    42   /**
       
    43    * Constructs a visible string for the given {@code value} (without exposing
       
    44    * the value structure). Encloses JavaScript string values in double quotes.
       
    45    *
       
    46    * @param value to build a visible string for
       
    47    * @return {@code value.getValueString()} (enclosed in double quotes if
       
    48    *         {@code value.getType() == TYPE_STRING}), or {@code null} if
       
    49    *         {@code value==null}
       
    50    */
       
    51   public static String toVisibleString(JsValue value) {
       
    52     return possiblyQuoteValueString(value);
       
    53   }
       
    54 
       
    55   private static String possiblyQuoteValueString(JsValue value) {
       
    56     if (value == null) {
       
    57       return UNKNOWN_VALUE;
       
    58     }
       
    59     String valueString = value.getValueString();
       
    60     return value.getType() == JsValue.Type.TYPE_STRING
       
    61         ? "\"" + valueString + "\"" //$NON-NLS-1$ //$NON-NLS-2$
       
    62         : valueString;
       
    63   }
       
    64 
       
    65   /**
       
    66    * Use the default config values.
       
    67    */
       
    68   public JsValueStringifier() {
       
    69     this.config = new Config();
       
    70   }
       
    71 
       
    72   /**
       
    73    * Use the specified {@code config} data.
       
    74    * @param config to use when rendering values.
       
    75    */
       
    76   public JsValueStringifier(Config config) {
       
    77     this.config = config;
       
    78   }
       
    79 
       
    80   public String render(JsValue value) {
       
    81     if (value == null) {
       
    82       return UNKNOWN_VALUE;
       
    83     }
       
    84     StringBuilder output = new StringBuilder();
       
    85     renderInternal(value, config.maxLength, true, output);
       
    86     return output.toString();
       
    87   }
       
    88 
       
    89   /**
       
    90    * @param value to render
       
    91    * @param maxLength the maximum length of the {@code output}
       
    92    * @param descend whether to descend into the object contents
       
    93    * @param output to render into
       
    94    */
       
    95   private void renderInternal(JsValue value, int maxLength, boolean descend, StringBuilder output) {
       
    96     if (!descend) {
       
    97       renderPrimitive(value, maxLength, output);
       
    98       return;
       
    99     }
       
   100     Type type = value.getType();
       
   101     // TODO(apavlov): implement good stringification of other types?
       
   102     switch (type) {
       
   103       case TYPE_ARRAY:
       
   104         renderArray(value.asObject().asArray(), maxLength, output);
       
   105         break;
       
   106       case TYPE_OBJECT:
       
   107         renderObject(value.asObject(), maxLength, output);
       
   108         break;
       
   109       default:
       
   110         renderPrimitive(value, maxLength, output);
       
   111         break;
       
   112     }
       
   113   }
       
   114 
       
   115   private void renderPrimitive(JsValue value, int maxLength, StringBuilder output) {
       
   116     output.append(possiblyQuoteValueString(value));
       
   117     truncate(output, maxLength, ELLIPSIS);
       
   118   }
       
   119 
       
   120   private void truncate(StringBuilder valueBuilder, int maxLength, String suffix) {
       
   121     int length = valueBuilder.length();
       
   122     if (length > maxLength) {
       
   123       valueBuilder.setLength(maxLength);
       
   124       valueBuilder.replace(
       
   125           maxLength - suffix.length(), maxLength,  suffix);
       
   126     }
       
   127   }
       
   128 
       
   129   private StringBuilder renderArray(JsArray value, int maxLength, StringBuilder output) {
       
   130     output.append('[');
       
   131     SortedMap<Integer, ? extends JsVariable> indexToElement = value.toSparseArray();
       
   132     boolean isFirst = true;
       
   133     int maxLengthWithoutLastBracket = maxLength - 1;
       
   134     StringBuilder elementBuilder = new StringBuilder();
       
   135     int entriesWritten = 0;
       
   136     for (Map.Entry<Integer, ? extends JsVariable> entry : indexToElement.entrySet()) {
       
   137       Integer index = entry.getKey();
       
   138       JsVariable var = entry.getValue();
       
   139       if (!isFirst) {
       
   140         output.append(',');
       
   141       } else {
       
   142         isFirst = false;
       
   143       }
       
   144       elementBuilder.setLength(0);
       
   145       elementBuilder.append(index).append('=');
       
   146       renderInternal(var.getValue(), maxLengthWithoutLastBracket /* essentially, no limit */,
       
   147           false, elementBuilder);
       
   148       if (output.length() + elementBuilder.length() >= maxLengthWithoutLastBracket) {
       
   149         // reached max length
       
   150         appendNMore(output, indexToElement.size() - entriesWritten);
       
   151         break;
       
   152       } else {
       
   153         output.append(elementBuilder.toString());
       
   154         entriesWritten++;
       
   155       }
       
   156     }
       
   157     return output.append(']');
       
   158   }
       
   159 
       
   160   private StringBuilder renderObject(JsObject value, int maxLength, StringBuilder output) {
       
   161     output.append('[');
       
   162     Collection<? extends JsVariable> properties = value.getProperties();
       
   163     boolean isFirst = true;
       
   164     int maxLengthWithoutLastBracket = maxLength - 1;
       
   165     StringBuilder elementBuilder = new StringBuilder();
       
   166     int entriesWritten = 0;
       
   167     for (JsVariable property : properties) {
       
   168       String name = property.getName();
       
   169       if (!isFirst) {
       
   170         output.append(',');
       
   171       } else {
       
   172         isFirst = false;
       
   173       }
       
   174       elementBuilder.setLength(0);
       
   175       elementBuilder.append(name).append('=');
       
   176       renderInternal(property.getValue(), maxLengthWithoutLastBracket /* essentially, no limit */,
       
   177           false, elementBuilder);
       
   178       if (output.length() + elementBuilder.length() >= maxLengthWithoutLastBracket) {
       
   179         // reached max length
       
   180         appendNMore(output, properties.size() - entriesWritten);
       
   181         break;
       
   182       } else {
       
   183         output.append(elementBuilder.toString());
       
   184         entriesWritten++;
       
   185       }
       
   186     }
       
   187     return output.append(']');
       
   188   }
       
   189 
       
   190   private StringBuilder appendNMore(StringBuilder output, int n) {
       
   191     return output.append(" +").append(n).append(ELLIPSIS); //$NON-NLS-1$
       
   192   }
       
   193 }