JAL-3438 spotless for 2.11.2.0
[jalview.git] / src / org / json / JSONPointer.java
index fc0b04b..4060411 100644 (file)
@@ -39,255 +39,324 @@ SOFTWARE.
  * 
  * In a nutshell, JSONPointer allows the user to navigate into a JSON document
  * using strings, and retrieve targeted objects, like a simple form of XPATH.
- * Path segments are separated by the '/' char, which signifies the root of
- * the document when it appears as the first char of the string. Array 
- * elements are navigated using ordinals, counting from 0. JSONPointer strings
- * may be extended to any arbitrary number of segments. If the navigation
- * is successful, the matched item is returned. A matched item may be a
- * JSONObject, a JSONArray, or a JSON value. If the JSONPointer string building 
- * fails, an appropriate exception is thrown. If the navigation fails to find
- * a match, a JSONPointerException is thrown. 
+ * Path segments are separated by the '/' char, which signifies the root of the
+ * document when it appears as the first char of the string. Array elements are
+ * navigated using ordinals, counting from 0. JSONPointer strings may be
+ * extended to any arbitrary number of segments. If the navigation is
+ * successful, the matched item is returned. A matched item may be a JSONObject,
+ * a JSONArray, or a JSON value. If the JSONPointer string building fails, an
+ * appropriate exception is thrown. If the navigation fails to find a match, a
+ * JSONPointerException is thrown.
  * 
  * @author JSON.org
  * @version 2016-05-14
  */
-public class JSONPointer {
+public class JSONPointer
+{
 
-    // used for URL encoding and decoding
-    private static final String ENCODING = "utf-8";
+  // used for URL encoding and decoding
+  private static final String ENCODING = "utf-8";
 
-    /**
-     * This class allows the user to build a JSONPointer in steps, using
-     * exactly one segment in each step.
-     */
-    public static class Builder {
-
-        // Segments for the eventual JSONPointer string
-        private final List<String> refTokens = new ArrayList<String>();
-
-        /**
-         * Creates a {@code JSONPointer} instance using the tokens previously set using the
-         * {@link #append(String)} method calls.
-         */
-        public JSONPointer build() {
-            return new JSONPointer(this.refTokens);
-        }
+  /**
+   * This class allows the user to build a JSONPointer in steps, using exactly
+   * one segment in each step.
+   */
+  public static class Builder
+  {
 
-        /**
-         * Adds an arbitrary token to the list of reference tokens. It can be any non-null value.
-         * 
-         * Unlike in the case of JSON string or URI fragment representation of JSON pointers, the
-         * argument of this method MUST NOT be escaped. If you want to query the property called
-         * {@code "a~b"} then you should simply pass the {@code "a~b"} string as-is, there is no
-         * need to escape it as {@code "a~0b"}.
-         * 
-         * @param token the new token to be appended to the list
-         * @return {@code this}
-         * @throws NullPointerException if {@code token} is null
-         */
-        public Builder append(String token) {
-            if (token == null) {
-                throw new NullPointerException("token cannot be null");
-            }
-            this.refTokens.add(token);
-            return this;
-        }
+    // Segments for the eventual JSONPointer string
+    private final List<String> refTokens = new ArrayList<String>();
 
-        /**
-         * Adds an integer to the reference token list. Although not necessarily, mostly this token will
-         * denote an array index. 
-         * 
-         * @param arrayIndex the array index to be added to the token list
-         * @return {@code this}
-         */
-        public Builder append(int arrayIndex) {
-            this.refTokens.add(String.valueOf(arrayIndex));
-            return this;
-        }
+    /**
+     * Creates a {@code JSONPointer} instance using the tokens previously set
+     * using the {@link #append(String)} method calls.
+     */
+    public JSONPointer build()
+    {
+      return new JSONPointer(this.refTokens);
     }
 
     /**
-     * Static factory method for {@link Builder}. Example usage:
+     * Adds an arbitrary token to the list of reference tokens. It can be any
+     * non-null value.
      * 
-     * <pre><code>
-     * JSONPointer pointer = JSONPointer.builder()
-     *       .append("obj")
-     *       .append("other~key").append("another/key")
-     *       .append("\"")
-     *       .append(0)
-     *       .build();
-     * </code></pre>
+     * Unlike in the case of JSON string or URI fragment representation of JSON
+     * pointers, the argument of this method MUST NOT be escaped. If you want to
+     * query the property called {@code "a~b"} then you should simply pass the
+     * {@code "a~b"} string as-is, there is no need to escape it as
+     * {@code "a~0b"}.
      * 
-     *  @return a builder instance which can be used to construct a {@code JSONPointer} instance by chained
-     *  {@link Builder#append(String)} calls.
+     * @param token
+     *          the new token to be appended to the list
+     * @return {@code this}
+     * @throws NullPointerException
+     *           if {@code token} is null
      */
-    public static Builder builder() {
-        return new Builder();
+    public Builder append(String token)
+    {
+      if (token == null)
+      {
+        throw new NullPointerException("token cannot be null");
+      }
+      this.refTokens.add(token);
+      return this;
     }
 
-    // Segments for the JSONPointer string
-    private final List<String> refTokens;
-
     /**
-     * Pre-parses and initializes a new {@code JSONPointer} instance. If you want to
-     * evaluate the same JSON Pointer on different JSON documents then it is recommended
-     * to keep the {@code JSONPointer} instances due to performance considerations.
+     * Adds an integer to the reference token list. Although not necessarily,
+     * mostly this token will denote an array index.
      * 
-     * @param pointer the JSON String or URI Fragment representation of the JSON pointer.
-     * @throws IllegalArgumentException if {@code pointer} is not a valid JSON pointer
+     * @param arrayIndex
+     *          the array index to be added to the token list
+     * @return {@code this}
      */
-    public JSONPointer(final String pointer) {
-        if (pointer == null) {
-            throw new NullPointerException("pointer cannot be null");
-        }
-        if (pointer.isEmpty() || pointer.equals("#")) {
-            this.refTokens = Collections.emptyList();
-            return;
-        }
-        String refs;
-        if (pointer.startsWith("#/")) {
-            refs = pointer.substring(2);
-            try {
-                refs = URLDecoder.decode(refs, ENCODING);
-            } catch (UnsupportedEncodingException e) {
-                throw new RuntimeException(e);
-            }
-        } else if (pointer.startsWith("/")) {
-            refs = pointer.substring(1);
-        } else {
-            throw new IllegalArgumentException("a JSON pointer should start with '/' or '#/'");
-        }
-        this.refTokens = new ArrayList<String>();
-        int slashIdx = -1;
-        int prevSlashIdx = 0;
-        do {
-            prevSlashIdx = slashIdx + 1;
-            slashIdx = refs.indexOf('/', prevSlashIdx);
-            if(prevSlashIdx == slashIdx || prevSlashIdx == refs.length()) {
-                // found 2 slashes in a row ( obj//next )
-                // or single slash at the end of a string ( obj/test/ )
-                this.refTokens.add("");
-            } else if (slashIdx >= 0) {
-                final String token = refs.substring(prevSlashIdx, slashIdx);
-                this.refTokens.add(unescape(token));
-            } else {
-                // last item after separator, or no separator at all.
-                final String token = refs.substring(prevSlashIdx);
-                this.refTokens.add(unescape(token));
-            }
-        } while (slashIdx >= 0);
-        // using split does not take into account consecutive separators or "ending nulls"
-        //for (String token : refs.split("/")) {
-        //    this.refTokens.add(unescape(token));
-        //}
+    public Builder append(int arrayIndex)
+    {
+      this.refTokens.add(String.valueOf(arrayIndex));
+      return this;
     }
+  }
 
-    public JSONPointer(List<String> refTokens) {
-        this.refTokens = new ArrayList<String>(refTokens);
-    }
+  /**
+   * Static factory method for {@link Builder}. Example usage:
+   * 
+   * <pre>
+   * <code>
+   * JSONPointer pointer = JSONPointer.builder()
+   *       .append("obj")
+   *       .append("other~key").append("another/key")
+   *       .append("\"")
+   *       .append(0)
+   *       .build();
+   * </code>
+   * </pre>
+   * 
+   * @return a builder instance which can be used to construct a
+   *         {@code JSONPointer} instance by chained
+   *         {@link Builder#append(String)} calls.
+   */
+  public static Builder builder()
+  {
+    return new Builder();
+  }
 
-    private String unescape(String token) {
-        return token.replace("~1", "/").replace("~0", "~")
-                .replace("\\\"", "\"")
-                .replace("\\\\", "\\");
-    }
+  // Segments for the JSONPointer string
+  private final List<String> refTokens;
 
-    /**
-     * Evaluates this JSON Pointer on the given {@code document}. The {@code document}
-     * is usually a {@link JSONObject} or a {@link JSONArray} instance, but the empty
-     * JSON Pointer ({@code ""}) can be evaluated on any JSON values and in such case the
-     * returned value will be {@code document} itself. 
-     * 
-     * @param document the JSON document which should be the subject of querying.
-     * @return the result of the evaluation
-     * @throws JSONPointerException if an error occurs during evaluation
-     */
-    public Object queryFrom(Object document) throws JSONPointerException {
-        if (this.refTokens.isEmpty()) {
-            return document;
-        }
-        Object current = document;
-        for (String token : this.refTokens) {
-            if (current instanceof JSONObject) {
-                current = ((JSONObject) current).opt(unescape(token));
-            } else if (current instanceof JSONArray) {
-                current = readByIndexToken(current, token);
-            } else {
-                throw new JSONPointerException(format(
-                        "value [%s] is not an array or object therefore its key %s cannot be resolved", current,
-                        token));
-            }
-        }
-        return current;
+  /**
+   * Pre-parses and initializes a new {@code JSONPointer} instance. If you want
+   * to evaluate the same JSON Pointer on different JSON documents then it is
+   * recommended to keep the {@code JSONPointer} instances due to performance
+   * considerations.
+   * 
+   * @param pointer
+   *          the JSON String or URI Fragment representation of the JSON
+   *          pointer.
+   * @throws IllegalArgumentException
+   *           if {@code pointer} is not a valid JSON pointer
+   */
+  public JSONPointer(final String pointer)
+  {
+    if (pointer == null)
+    {
+      throw new NullPointerException("pointer cannot be null");
+    }
+    if (pointer.isEmpty() || pointer.equals("#"))
+    {
+      this.refTokens = Collections.emptyList();
+      return;
+    }
+    String refs;
+    if (pointer.startsWith("#/"))
+    {
+      refs = pointer.substring(2);
+      try
+      {
+        refs = URLDecoder.decode(refs, ENCODING);
+      } catch (UnsupportedEncodingException e)
+      {
+        throw new RuntimeException(e);
+      }
+    }
+    else if (pointer.startsWith("/"))
+    {
+      refs = pointer.substring(1);
     }
+    else
+    {
+      throw new IllegalArgumentException(
+              "a JSON pointer should start with '/' or '#/'");
+    }
+    this.refTokens = new ArrayList<String>();
+    int slashIdx = -1;
+    int prevSlashIdx = 0;
+    do
+    {
+      prevSlashIdx = slashIdx + 1;
+      slashIdx = refs.indexOf('/', prevSlashIdx);
+      if (prevSlashIdx == slashIdx || prevSlashIdx == refs.length())
+      {
+        // found 2 slashes in a row ( obj//next )
+        // or single slash at the end of a string ( obj/test/ )
+        this.refTokens.add("");
+      }
+      else if (slashIdx >= 0)
+      {
+        final String token = refs.substring(prevSlashIdx, slashIdx);
+        this.refTokens.add(unescape(token));
+      }
+      else
+      {
+        // last item after separator, or no separator at all.
+        final String token = refs.substring(prevSlashIdx);
+        this.refTokens.add(unescape(token));
+      }
+    } while (slashIdx >= 0);
+    // using split does not take into account consecutive separators or "ending
+    // nulls"
+    // for (String token : refs.split("/")) {
+    // this.refTokens.add(unescape(token));
+    // }
+  }
 
-    /**
-     * Matches a JSONArray element by ordinal position
-     * @param current the JSONArray to be evaluated
-     * @param indexToken the array index in string form
-     * @return the matched object. If no matching item is found a
-     * @throws JSONPointerException is thrown if the index is out of bounds
-     */
-    private Object readByIndexToken(Object current, String indexToken) throws JSONPointerException {
-        try {
-            int index = Integer.parseInt(indexToken);
-            JSONArray currentArr = (JSONArray) current;
-            if (index >= currentArr.length()) {
-                throw new JSONPointerException(format("index %d is out of bounds - the array has %d elements", index,
-                        currentArr.length()));
-            }
-            try {
-                               return currentArr.get(index);
-                       } catch (JSONException e) {
-                               throw new JSONPointerException("Error reading value at index position " + index, e);
-                       }
-        } catch (NumberFormatException e) {
-            throw new JSONPointerException(format("%s is not an array index", indexToken), e);
-        }
+  public JSONPointer(List<String> refTokens)
+  {
+    this.refTokens = new ArrayList<String>(refTokens);
+  }
+
+  private String unescape(String token)
+  {
+    return token.replace("~1", "/").replace("~0", "~").replace("\\\"", "\"")
+            .replace("\\\\", "\\");
+  }
+
+  /**
+   * Evaluates this JSON Pointer on the given {@code document}. The
+   * {@code document} is usually a {@link JSONObject} or a {@link JSONArray}
+   * instance, but the empty JSON Pointer ({@code ""}) can be evaluated on any
+   * JSON values and in such case the returned value will be {@code document}
+   * itself.
+   * 
+   * @param document
+   *          the JSON document which should be the subject of querying.
+   * @return the result of the evaluation
+   * @throws JSONPointerException
+   *           if an error occurs during evaluation
+   */
+  public Object queryFrom(Object document) throws JSONPointerException
+  {
+    if (this.refTokens.isEmpty())
+    {
+      return document;
+    }
+    Object current = document;
+    for (String token : this.refTokens)
+    {
+      if (current instanceof JSONObject)
+      {
+        current = ((JSONObject) current).opt(unescape(token));
+      }
+      else if (current instanceof JSONArray)
+      {
+        current = readByIndexToken(current, token);
+      }
+      else
+      {
+        throw new JSONPointerException(format(
+                "value [%s] is not an array or object therefore its key %s cannot be resolved",
+                current, token));
+      }
     }
+    return current;
+  }
 
-    /**
-     * Returns a string representing the JSONPointer path value using string
-     * representation
-     */
-    @Override
-    public String toString() {
-        StringBuilder rval = new StringBuilder("");
-        for (String token: this.refTokens) {
-            rval.append('/').append(escape(token));
-        }
-        return rval.toString();
+  /**
+   * Matches a JSONArray element by ordinal position
+   * 
+   * @param current
+   *          the JSONArray to be evaluated
+   * @param indexToken
+   *          the array index in string form
+   * @return the matched object. If no matching item is found a
+   * @throws JSONPointerException
+   *           is thrown if the index is out of bounds
+   */
+  private Object readByIndexToken(Object current, String indexToken)
+          throws JSONPointerException
+  {
+    try
+    {
+      int index = Integer.parseInt(indexToken);
+      JSONArray currentArr = (JSONArray) current;
+      if (index >= currentArr.length())
+      {
+        throw new JSONPointerException(format(
+                "index %d is out of bounds - the array has %d elements",
+                index, currentArr.length()));
+      }
+      try
+      {
+        return currentArr.get(index);
+      } catch (JSONException e)
+      {
+        throw new JSONPointerException(
+                "Error reading value at index position " + index, e);
+      }
+    } catch (NumberFormatException e)
+    {
+      throw new JSONPointerException(
+              format("%s is not an array index", indexToken), e);
     }
+  }
 
-    /**
-     * Escapes path segment values to an unambiguous form.
-     * The escape char to be inserted is '~'. The chars to be escaped 
-     * are ~, which maps to ~0, and /, which maps to ~1. Backslashes
-     * and double quote chars are also escaped.
-     * @param token the JSONPointer segment value to be escaped
-     * @return the escaped value for the token
-     */
-    private String escape(String token) {
-        return token.replace("~", "~0")
-                .replace("/", "~1")
-                .replace("\\", "\\\\")
-                .replace("\"", "\\\"");
+  /**
+   * Returns a string representing the JSONPointer path value using string
+   * representation
+   */
+  @Override
+  public String toString()
+  {
+    StringBuilder rval = new StringBuilder("");
+    for (String token : this.refTokens)
+    {
+      rval.append('/').append(escape(token));
     }
+    return rval.toString();
+  }
 
-    /**
-     * Returns a string representing the JSONPointer path value using URI
-     * fragment identifier representation
-     */
-    public String toURIFragment() {
-        try {
-            StringBuilder rval = new StringBuilder("#");
-            for (String token : this.refTokens) {
-                rval.append('/').append(URLEncoder.encode(token, ENCODING));
-            }
-            return rval.toString();
-        } catch (UnsupportedEncodingException e) {
-            throw new RuntimeException(e);
-        }
+  /**
+   * Escapes path segment values to an unambiguous form. The escape char to be
+   * inserted is '~'. The chars to be escaped are ~, which maps to ~0, and /,
+   * which maps to ~1. Backslashes and double quote chars are also escaped.
+   * 
+   * @param token
+   *          the JSONPointer segment value to be escaped
+   * @return the escaped value for the token
+   */
+  private String escape(String token)
+  {
+    return token.replace("~", "~0").replace("/", "~1").replace("\\", "\\\\")
+            .replace("\"", "\\\"");
+  }
+
+  /**
+   * Returns a string representing the JSONPointer path value using URI fragment
+   * identifier representation
+   */
+  public String toURIFragment()
+  {
+    try
+    {
+      StringBuilder rval = new StringBuilder("#");
+      for (String token : this.refTokens)
+      {
+        rval.append('/').append(URLEncoder.encode(token, ENCODING));
+      }
+      return rval.toString();
+    } catch (UnsupportedEncodingException e)
+    {
+      throw new RuntimeException(e);
     }
-    
+  }
+
 }