JAL-3541 selectively merged build.gradle and gradle.properties
[jalview.git] / unused / javajs / util / JSJSONParser.java
diff --git a/unused/javajs/util/JSJSONParser.java b/unused/javajs/util/JSJSONParser.java
new file mode 100644 (file)
index 0000000..552270d
--- /dev/null
@@ -0,0 +1,325 @@
+package javajs.util;
+
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+
+
+/**
+ * a very simple JSON parser for JSON objects that are compatible with JavaScript
+ * A gross simplification of https://github.com/douglascrockford/JSON-java
+ * 
+ * A SUBSET of JSON with similarly to window.JSON.parse():
+ * 
+ * In JavaScript returns "null" for a null value, not null
+ * 
+ *  -- requires quoted strings for keys and values
+ *  
+ *  -- does not allow /xxx/ objects
+ *  
+ *  @author Bob Hanson
+ *  
+ */
+public class JSJSONParser {
+
+  private String str;
+  private int index;
+  private int len;
+  private boolean asHashTable;
+
+  public JSJSONParser () {
+    // for reflection
+  }
+  
+  /**
+   * requires { "key":"value", "key":"value",....}
+   * 
+   * @param str
+   * @param asHashTable TODO
+   * 
+   * @return Map or null
+   */
+  @SuppressWarnings("unchecked")
+  public Map<String, Object> parseMap(String str, boolean asHashTable) {
+    index = 0;
+    this.asHashTable = asHashTable;
+    this.str = str;
+    len = str.length();
+    if (getChar() != '{')
+      return null;
+    returnChar();
+    return (Map<String, Object>) getValue(false);
+  }
+  
+  /**
+   * Could return Integer, Float, Boolean, String, Map<String, Object>, Lst<Object>, or null
+   * 
+   * @param str
+   * @param asHashTable 
+   * @return a object equivalent to the JSON string str
+   * 
+   */
+  public Object parse(String str, boolean asHashTable) {
+    index = 0;
+    this.asHashTable = asHashTable;
+    this.str = str;
+    len = str.length();
+    return getValue(false);
+  }
+
+  private char next() {
+    return (index < len ? str.charAt(index++) : '\0');
+  }
+
+  private void returnChar() {
+    index--;
+  }
+
+  /**
+   * Get the next char in the string, skipping whitespace.
+   * 
+   * @throws JSONException
+   * @return one character, or 0 if there are no more characters.
+   */
+  private char getChar() throws JSONException {
+    for (;;) {
+      char c = next();
+      if (c == 0 || c > ' ') {
+        return c;
+      }
+    }
+  }
+
+  /**
+   * only allowing the following values:
+   * 
+   * {...} object
+   * 
+   * [...] array
+   * 
+   * Integer
+   * 
+   * Float
+   * 
+   * "quoted string"
+   * 
+   * 
+   * @param isKey if we should allow {...} and [...]
+   * @return a subclass of Object
+   * @throws JSONException
+   */
+  private Object getValue(boolean isKey) throws JSONException {
+    int i = index;
+    char c = getChar();
+    switch (c) {
+    case '\0':
+      return null;
+    case '"':
+    case '\'':
+      return getString(c);
+    case '{':
+      if (!isKey)
+        return getObject();
+      c = 0;
+      break;
+    case '[':
+      if (!isKey)
+        return getArray();
+      c = 0;
+      break;
+    default:
+      // standard syntax is assumed; not checking all possible invalid keys
+      // for example, "-" is not allowed in JavaScript, which is what this is for
+      returnChar();
+      while (c >= ' ' && "[,]{:}'\"".indexOf(c) < 0)
+        c = next();
+      returnChar();
+      if (isKey && c != ':')
+        c = 0;
+      break;
+    }
+    if (isKey && c == 0)
+      throw new JSONException("invalid key");
+
+    String string = str.substring(i, index).trim();
+
+    // check for the only valid simple words: true, false, null (lower case)
+    // and in this case, only for 
+
+    if (!isKey) {
+      if (string.equals("true")) {
+        return Boolean.TRUE;
+      }
+      if (string.equals("false")) {
+        return Boolean.FALSE;
+      }
+      if (string.equals("null")) {
+        return (asHashTable ? string : null);
+      }
+    }
+    //  only numbers from here on:
+    c = string.charAt(0);
+    if (c >= '0' && c <= '9' || c == '-')
+      try {
+        if (string.indexOf('.') < 0 && string.indexOf('e') < 0
+            && string.indexOf('E') < 0)
+          return new Integer(string);
+        // not allowing infinity or NaN
+        // using float here because Jmol does not use Double
+        Float d = Float.valueOf(string);
+        if (!d.isInfinite() && !d.isNaN())
+          return d;
+      } catch (Exception e) {
+      }
+    // not a valid number
+    System.out.println("JSON parser cannot parse " + string);
+    throw new JSONException("invalid value");
+  }
+
+  private String getString(char quote) throws JSONException {
+    char c;
+    SB sb = null;
+    int i0 = index;
+    for (;;) {
+      int i1 = index;
+      switch (c = next()) {
+      case '\0':
+      case '\n':
+      case '\r':
+        throw syntaxError("Unterminated string");
+      case '\\':
+        switch (c = next()) {
+        case '"':
+        case '\'':
+        case '\\':
+        case '/':
+          break;
+        case 'b':
+          c = '\b';
+          break;
+        case 't':
+          c = '\t';
+          break;
+        case 'n':
+          c = '\n';
+          break;
+        case 'f':
+          c = '\f';
+          break;
+        case 'r':
+          c = '\r';
+          break;
+        case 'u':
+          int i = index;
+          index += 4;
+          try {
+            c = (char) Integer.parseInt(str.substring(i, index), 16);
+          } catch (Exception e) {
+            throw syntaxError("Substring bounds error");
+          }
+          break;
+        default:
+          throw syntaxError("Illegal escape.");
+        }
+        break;
+      default:
+        if (c == quote)
+          return (sb == null ? str.substring(i0, i1) : sb.toString());
+        break;
+      }
+      if (index > i1 + 1) {
+        if (sb == null) {
+          sb = new SB();
+          sb.append(str.substring(i0, i1));
+        }
+      }
+      if (sb != null)
+        sb.appendC(c);
+    }
+  }
+
+  private Object getObject() {
+    Map<String, Object> map = (asHashTable ? new Hashtable<String, Object>() : new HashMap<String, Object>());
+    String key = null;
+    switch (getChar()) {
+    case '}':
+      return map;
+    case 0:
+      throw new JSONException("invalid object");
+    }
+    returnChar();
+    boolean isKey = false;
+    for (;;) {
+      if ((isKey = !isKey) == true)
+        key = getValue(true).toString();
+      else
+        map.put(key, getValue(false));
+      switch (getChar()) {
+      case '}':
+        return map;
+      case ':':
+        if (isKey)
+          continue;
+        isKey = true;
+        //$FALL-THROUGH$
+      case ',':
+        if (!isKey)
+          continue;
+        //$FALL-THROUGH$
+      default:
+        throw syntaxError("Expected ',' or ':' or '}'");
+      }
+    }
+  }
+
+  private Object getArray() {
+    Lst<Object> l = new Lst<Object>();
+    switch (getChar()) {
+    case ']':
+      return l;
+    case '\0':
+      throw new JSONException("invalid array");
+    }
+    returnChar();
+    boolean isNull = false;
+    for (;;) {
+      if (isNull) {
+        l.addLast(null);
+        isNull = false;
+      } else {
+        l.addLast(getValue(false));
+      }
+      switch (getChar()) {
+      case ',':
+        switch (getChar()) {
+        case ']':
+          // terminal ,
+          return l;
+        case ',':
+          // empty value
+          isNull = true;
+          //$FALL-THROUGH$
+        default:
+          returnChar();
+        }
+        continue;
+      case ']':
+        return l;
+      default:
+        throw syntaxError("Expected ',' or ']'");
+      }
+    }
+  }
+
+  /**
+   * Make a JSONException to signal a syntax error.
+   * 
+   * @param message
+   *        The error message.
+   * @return A JSONException object, suitable for throwing
+   */
+  public JSONException syntaxError(String message) {
+    return new JSONException(message + " for " + str.substring(0, Math.min(index,  len)));
+  }
+
+}