org.chromium.debug.core/src/org/chromium/debug/core/util/JsValueStringifier.java
changeset 2 e4420d2515f1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/util/JsValueStringifier.java	Wed Dec 23 17:13:18 2009 -0800
@@ -0,0 +1,193 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.debug.core.util;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.SortedMap;
+
+import org.chromium.sdk.JsArray;
+import org.chromium.sdk.JsObject;
+import org.chromium.sdk.JsValue;
+import org.chromium.sdk.JsVariable;
+import org.chromium.sdk.JsValue.Type;
+
+/**
+ * A converter of JsValues into human-readable strings used in various contexts.
+ */
+public class JsValueStringifier {
+
+  /**
+   * A configuration class
+   */
+  public static class Config {
+
+    /**
+     * The maximum length of the text to render. If the value length exceeds
+     * the limit, an ellipsis will be used to truncate the value.
+     * The default is 80 characters.
+     */
+    public int maxLength = 80;
+  }
+
+  private static final String ELLIPSIS = "..."; //$NON-NLS-1$
+
+  private static final String UNKNOWN_VALUE = "<null>"; //$NON-NLS-1$
+
+  private final Config config;
+
+
+  /**
+   * Constructs a visible string for the given {@code value} (without exposing
+   * the value structure). Encloses JavaScript string values in double quotes.
+   *
+   * @param value to build a visible string for
+   * @return {@code value.getValueString()} (enclosed in double quotes if
+   *         {@code value.getType() == TYPE_STRING}), or {@code null} if
+   *         {@code value==null}
+   */
+  public static String toVisibleString(JsValue value) {
+    return possiblyQuoteValueString(value);
+  }
+
+  private static String possiblyQuoteValueString(JsValue value) {
+    if (value == null) {
+      return UNKNOWN_VALUE;
+    }
+    String valueString = value.getValueString();
+    return value.getType() == JsValue.Type.TYPE_STRING
+        ? "\"" + valueString + "\"" //$NON-NLS-1$ //$NON-NLS-2$
+        : valueString;
+  }
+
+  /**
+   * Use the default config values.
+   */
+  public JsValueStringifier() {
+    this.config = new Config();
+  }
+
+  /**
+   * Use the specified {@code config} data.
+   * @param config to use when rendering values.
+   */
+  public JsValueStringifier(Config config) {
+    this.config = config;
+  }
+
+  public String render(JsValue value) {
+    if (value == null) {
+      return UNKNOWN_VALUE;
+    }
+    StringBuilder output = new StringBuilder();
+    renderInternal(value, config.maxLength, true, output);
+    return output.toString();
+  }
+
+  /**
+   * @param value to render
+   * @param maxLength the maximum length of the {@code output}
+   * @param descend whether to descend into the object contents
+   * @param output to render into
+   */
+  private void renderInternal(JsValue value, int maxLength, boolean descend, StringBuilder output) {
+    if (!descend) {
+      renderPrimitive(value, maxLength, output);
+      return;
+    }
+    Type type = value.getType();
+    // TODO(apavlov): implement good stringification of other types?
+    switch (type) {
+      case TYPE_ARRAY:
+        renderArray(value.asObject().asArray(), maxLength, output);
+        break;
+      case TYPE_OBJECT:
+        renderObject(value.asObject(), maxLength, output);
+        break;
+      default:
+        renderPrimitive(value, maxLength, output);
+        break;
+    }
+  }
+
+  private void renderPrimitive(JsValue value, int maxLength, StringBuilder output) {
+    output.append(possiblyQuoteValueString(value));
+    truncate(output, maxLength, ELLIPSIS);
+  }
+
+  private void truncate(StringBuilder valueBuilder, int maxLength, String suffix) {
+    int length = valueBuilder.length();
+    if (length > maxLength) {
+      valueBuilder.setLength(maxLength);
+      valueBuilder.replace(
+          maxLength - suffix.length(), maxLength,  suffix);
+    }
+  }
+
+  private StringBuilder renderArray(JsArray value, int maxLength, StringBuilder output) {
+    output.append('[');
+    SortedMap<Integer, ? extends JsVariable> indexToElement = value.toSparseArray();
+    boolean isFirst = true;
+    int maxLengthWithoutLastBracket = maxLength - 1;
+    StringBuilder elementBuilder = new StringBuilder();
+    int entriesWritten = 0;
+    for (Map.Entry<Integer, ? extends JsVariable> entry : indexToElement.entrySet()) {
+      Integer index = entry.getKey();
+      JsVariable var = entry.getValue();
+      if (!isFirst) {
+        output.append(',');
+      } else {
+        isFirst = false;
+      }
+      elementBuilder.setLength(0);
+      elementBuilder.append(index).append('=');
+      renderInternal(var.getValue(), maxLengthWithoutLastBracket /* essentially, no limit */,
+          false, elementBuilder);
+      if (output.length() + elementBuilder.length() >= maxLengthWithoutLastBracket) {
+        // reached max length
+        appendNMore(output, indexToElement.size() - entriesWritten);
+        break;
+      } else {
+        output.append(elementBuilder.toString());
+        entriesWritten++;
+      }
+    }
+    return output.append(']');
+  }
+
+  private StringBuilder renderObject(JsObject value, int maxLength, StringBuilder output) {
+    output.append('[');
+    Collection<? extends JsVariable> properties = value.getProperties();
+    boolean isFirst = true;
+    int maxLengthWithoutLastBracket = maxLength - 1;
+    StringBuilder elementBuilder = new StringBuilder();
+    int entriesWritten = 0;
+    for (JsVariable property : properties) {
+      String name = property.getName();
+      if (!isFirst) {
+        output.append(',');
+      } else {
+        isFirst = false;
+      }
+      elementBuilder.setLength(0);
+      elementBuilder.append(name).append('=');
+      renderInternal(property.getValue(), maxLengthWithoutLastBracket /* essentially, no limit */,
+          false, elementBuilder);
+      if (output.length() + elementBuilder.length() >= maxLengthWithoutLastBracket) {
+        // reached max length
+        appendNMore(output, properties.size() - entriesWritten);
+        break;
+      } else {
+        output.append(elementBuilder.toString());
+        entriesWritten++;
+      }
+    }
+    return output.append(']');
+  }
+
+  private StringBuilder appendNMore(StringBuilder output, int n) {
+    return output.append(" +").append(n).append(ELLIPSIS); //$NON-NLS-1$
+  }
+}
\ No newline at end of file