package com.smtscript.utils; /* * Copyright (C) 2011 Miami-Dade County. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Note: this file incorporates source code from 3d party entities. Such code * is copyrighted by those entities as indicated below. */ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.math.BigDecimal; import java.math.BigInteger; import java.text.CharacterIterator; import java.text.StringCharacterIterator; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * *

* Represents a JSON (JavaScript Object Notation) entity. For more information about JSON, please see * http://www.json.org. *

* *

* A JSON entity can be one of several things: an object (set of name/Json entity pairs), an array (a list of * other JSON entities), a string, a number, a boolean or null. All of those are represented as Json * instances. Each of the different types of entities supports a different set of operations. However, this class * unifies all operations into a single interface so in Java one is always dealing with a single object type: this class. * The approach effectively amounts to dynamic typing where using an unsupported operation won't be detected at * compile time, but will throw a runtime {@link UnsupportedOperationException}. It simplifies working with JSON * structures considerably and it leads to shorter at cleaner Java code. It makes much easier to work * with JSON structure without the need to convert to "proper" Java representation in the form of * POJOs and the like. When traversing a JSON, there's no need to type-cast at each step because there's * only one type: Json. *

* *

* One can examine the concrete type of a Json with one of the isXXX methods: * {@link #isObject()}, {@link #isArray()},{@link #isNumber()},{@link #isBoolean()},{@link #isString()}, * {@link #isNull()}. *

* *

* The underlying representation of a given Json instance can be obtained by calling * the generic {@link #getValue()} method or one of the asXXX methods such * as {@link #asBoolean()} or {@link #asString()} etc. * JSON objects are represented as Java {@link Map}s while JSON arrays are represented as Java * {@link List}s. Because those are mutable aggregate structures, there are two versions of the * corresponding asXXX methods: {@link #asMap()} which performs a deep copy of the underlying * map, unwrapping every nested Json entity to its Java representation and {@link #asJsonMap()} which * simply return the map reference. Similarly there are {@link #asList()} and {@link #asJsonList()}. *

* *

Constructing and Modifying JSON Structures

* *

* There are several static factory methods in this class that allow you to create new * Json instances: *

* * * * * * * * * * * * * * * * * * * * * * * *
{@link #read(String)}Parse a JSON string and return the resulting Json instance. The syntax * recognized is as defined in http://www.json.org. *
{@link #make(Object)}Creates a Json instance based on the concrete type of the parameter. The types * recognized are null, numbers, primitives, String, Map, Collection, Java arrays * and Json itself.
{@link #nil()}Return a Json instance representing JSON null.
{@link #object()}Create and return an empty JSON object.
{@link #object(Object...)}Create and return a JSON object populated with the key/value pairs * passed as an argument sequence. Each even parameter becomes a key (via * toString) and each odd parameter is converted to a Json * value.
{@link #array()}Create and return an empty JSON array.
{@link #array(Object...)}Create and return a JSON array from the list of arguments.
* *

* To customize how Json elements are represented and to provide your own version of the * {@link #make(Object)} method, you create an implementation of the {@link Factory} interface * and configure it either globally with the {@link #setGlobalFactory(Factory)} method or * on a per-thread basis with the {@link #attachFactory(Factory)}/{@link #dettachFactory()} * methods. *

* *

* If a Json instance is an object, you can set its properties by * calling the {@link #set(String, Object)} method which will add a new property or replace an existing one. * Adding elements to an array Json is done with the {@link #add(Object)} method. * Removing elements by their index (or key) is done with the {@link #delAt(int)} (or * {@link #delAt(String)}) method. You can also remove an element from an array without * knowing its index with the {@link #remove(Object)} method. All these methods return the * Json instance being manipulated so that method calls can be chained. * If you want to remove an element from an object or array and return the removed element * as a result of the operation, call {@link #atDel(int)} or {@link #atDel(String)} instead. *

* *

* If you want to add properties to an object in bulk or append a sequence of elements to array, * use the {@link #with(Json)} method. When used on an object, this method expects another * object as its argument and it will copy all properties of that argument into itself. Similarly, * when called on array, the method expects another array and it will append all elements of its * argument to itself. *

* *

* To make a clone of a Json object, use the {@link #dup()} method. This method will create a new * object even for the immutable primitive Json types. Objects and arrays are cloned * (i.e. duplicated) recursively. *

* *

Navigating JSON Structures

* *

* The {@link #at(int)} method returns the array element at the specified index and the * {@link #at(String)} method does the same for a property of an object instance. You can * use the {@link #at(String, Object)} version to create an object property with a default * value if it doesn't exist already. *

* *

* To test just whether a Json object has a given property, use the {@link #has(String)} method. To test * whether a given object property or an array elements is equal to a particular value, use the * {@link #is(String, Object)} and {@link #is(int, Object)} methods respectively. Those methods return * true if the given named property (or indexed element) is equal to the passed in Object as the second * parameter. They return false if an object doesn't have the specified property or an index array is out * of bounds. For example is(name, value) is equivalent to 'has(name) && at(name).equals(make(value))'. *

* *

* To help in navigating JSON structures, instances of this class contain a reference to the * enclosing JSON entity (object or array) if any. The enclosing entity can be accessed * with {@link #up()} method. *

* *

* The combination of method chaining when modifying Json instances and * the ability to navigate "inside" a structure and then go back to the enclosing * element lets one accomplish a lot in a single Java statement, without the need * of intermediary variables. Here for example how the following JSON structure can * be created in one statement using chained calls: *

* *

 * {"menu": {
 * "id": "file",
 * "value": "File",
 * "popup": {
 *   "menuitem": [
 *     {"value": "New", "onclick": "CreateNewDoc()"},
 *     {"value": "Open", "onclick": "OpenDoc()"},
 *     {"value": "Close", "onclick": "CloseDoc()"}
 *   ]
 * }
 * "position": 0
 * }}
 * 
* *

 * import mjson.Json;
 * import static mjson.Json.*;
 * ...
 * Json j = object()
 *  .at("menu", object())
 *    .set("id", "file") 
 *    .set("value", "File")
 *    .at("popup", object())
 *      .at("menuitem", array())
 *        .add(object("value", "New", "onclick", "CreateNewDoc()"))
 *        .add(object("value", "Open", "onclick", "OpenDoc()"))
 *        .add(object("value", "Close", "onclick", "CloseDoc()"))
 *        .up()
 *      .up()
 *    .set("position", 0)
 *  .up();       
 * ...
 * 
* *

* If there's no danger of naming conflicts, a static import of the factory methods ( * import static json.Json.*;) would reduce typing even further and make the code more * readable. *

* *

Converting to String

* *

* To get a compact string representation, simply use the {@link #toString()} method. If you * want to wrap it in a JavaScript callback (for JSON with padding), use the {@link #pad(String)} * method. *

* * @author Borislav Iordanov * @version 1.3 */ public class Json { public static String _jsonPathSplit = "|"; /** *

* This interface defines how Json instances are constructed. There is a * default implementation for each kind of Json value, but you can provide * your own implementation. For example, you might want a different representation of * an object than a regular HashMap. Or you might want string comparison to be * case insensitive. *

* *

* In addition, the {@link #make(Object)} method allows you plug-in your own mapping * of arbitrary Java objects to Json instances. You might want to implement * a Java Beans to JSON mapping or any other JSON serialization that makes sense in your * project. *

* *

* To avoid implementing all methods in that interface, you can extend the {@link DefaultFactory} * default implementation and simply overwrite the ones you're interested in. *

* *

* The factory implementation used by the Json classes is specified simply by calling * the {@link #setGlobalFactory(Factory)} method. The factory is a static, global variable by default. * If you need different factories in different areas of a single application, you may attach them * to different threads of execution using the {@link #attachFactory(Factory)}. Recall a separate * copy of static variables is made per ClassLoader, so for example in a web application context, that * global factory can be different for each web application (as Java web servers usually use a separate * class loader per application). Thread-local factories are really a provision for special cases. *

* * @author Borislav Iordanov * */ public static interface Factory { Json nil(); Json bool(boolean value); Json string(String value); Json number(Number value); Json object(); Json array(); Json make(Object anything); } public static class DefaultFactory implements Factory { public Json nil() { return Json.topnull; } public Json bool(boolean x) { return new BooleanJson(x ? Boolean.TRUE : Boolean.FALSE, null); } public Json string(String x) { return new StringJson(x, null); } public Json number(Number x) { return new NumberJson(x, null); } public Json array() { return new ArrayJson(); } public Json object() { return new ObjectJson(); } public Json make(Object anything) { if (anything == null) return topnull; else if (anything instanceof Json) return (Json)anything; else if (anything instanceof String) return factory().string((String)anything); else if (anything instanceof Collection) { Json L = array(); for (Object x : (Collection)anything) L.add(factory().make(x)); return L; } else if (anything instanceof Map) { Json O = object(); for (Map.Entry x : ((Map)anything).entrySet()) O.set(x.getKey().toString(), factory().make(x.getValue())); return O; } else if (anything instanceof Boolean) return factory().bool((Boolean)anything); else if (anything instanceof Number) return factory().number((Number)anything); else if (anything.getClass().isArray()) { Class comp = anything.getClass().getComponentType(); if (!comp.isPrimitive()) return Json.array((Object[])anything); Json A = array(); if (boolean.class == comp) for (boolean b : (boolean[])anything) A.add(b); else if (byte.class == comp) for (byte b : (byte[])anything) A.add(b); else if (char.class == comp) for (char b : (char[])anything) A.add(b); else if (short.class == comp) for (short b : (short[])anything) A.add(b); else if (int.class == comp) for (int b : (int[])anything) A.add(b); else if (long.class == comp) for (long b : (long[])anything) A.add(b); else if (float.class == comp) for (float b : (float[])anything) A.add(b); else if (double.class == comp) for (double b : (double[])anything) A.add(b); return A; } else throw new IllegalArgumentException("Don't know how to convert to Json : " + anything); } } public static final Factory defaultFactory = new DefaultFactory(); private static Factory globalFactory = defaultFactory; // TODO1: maybe use initialValue thread-local method to attach global factory by default here... private static ThreadLocal threadFactory = new ThreadLocal(); public static Factory factory() { Factory f = threadFactory.get(); return f != null ? f : globalFactory; } /** *

* Specify a global Json {@link Factory} to be used by all threads that don't have a * specific thread-local factory attached to them. *

* * @param factory */ public static void setGlobalFactory(Factory factory) { globalFactory = factory; } /** *

* Attach a thread-local Json {@link Factory} to be used specifically by this thread. Thread-local * Json factories are the only means to have different {@link Factory} implementations used simultaneously * in the same application (well, more accurately, the same ClassLoader). *

* * @param factory */ public static void attachFactory(Factory factory) { threadFactory.set(factory); } /** *

* Clear the thread-local factory previously attached to this thread via the * {@link #attachFactory(Factory)} method. The global factory takes effect after * a call to this method. *

*/ public static void dettachFactory() { threadFactory.remove(); } /** *

* Parse a JSON entity from its string representation. *

* * @param jsonAsString A valid JSON representation as per the json.org * grammar. Cannot be null. * @return The JSON entity parsed: an object, array, string, number or boolean, or null. Note that * this method will never return the actual Java null. * @ */ public static Json read(String jsonAsString, IJsonParseExt propReader) throws RuntimeException { Reader rd = new Reader(); return (Json)rd.read(jsonAsString, propReader); } public static Json read(InputStream stream) throws RuntimeException { Reader rd = new Reader(); return (Json)rd.read(readJsonStream(stream), null); } public static Json read(String jsonAsString) { return read(jsonAsString, false); } public static Json read(String jsonAsString, boolean convert) throws RuntimeException { Reader rd = new Reader(); return (Json)rd.read(convert ? convToJsonString(jsonAsString) : jsonAsString, null); } public static Json read(File file) { return read(file, null, null); } public static Json read(File file, IJsonParseExt propReader, String[] defineMap) { Reader rd = new Reader(); if(defineMap != null && defineMap.length > 0) rd.importDefineMap(defineMap); return (Json)rd.read(file, propReader); } private static String readTextStream(InputStream inputStream) { InputStreamReader rd = null; try { StringBuilder sb = new StringBuilder(); int size = 0; char[] data = new char[10240]; rd=new InputStreamReader(inputStream, "UTF-8"); while((size = rd.read(data)) > 0) { sb.append(data, 0, size); } return sb.toString(); } catch(Exception ex) { throw new RuntimeException(ex); } finally { if(rd != null) try { rd.close(); } catch (IOException e) { } } } public static String readJsonFile(File file) { try { FileInputStream fi = new FileInputStream(file); try { return convToJsonString(readTextStream(fi)); } finally { fi.close(); } } catch(Exception ex) { throw new RuntimeException(String.format("read json file [%s] error", file.getAbsolutePath()), ex); } } public static String readTextFile(File file) { try { FileInputStream fi = new FileInputStream(file); try { return readTextStream(fi).replace("\r", ""); } finally { fi.close(); } } catch(Exception ex) { throw new RuntimeException(String.format("read json file [%s] error", file.getAbsolutePath()), ex); } } public static String readJsonStream(InputStream inputStream) { return convToJsonString(readTextStream(inputStream)); } public static String splitJsonMapString(String jsonStr) { for(int pos = 0; pos < jsonStr.length(); pos ++) { if(jsonStr.charAt(pos) == '{') { jsonStr = jsonStr.substring(pos + 1); break; } } for(int pos = jsonStr.length() - 1; pos >= 0; pos --) { if(jsonStr.charAt(pos) == '}') { jsonStr = jsonStr.substring(0, pos); break; } } return jsonStr; } private static String convToJsonString(String jsonStr) { while(jsonStr.length() > 0 && jsonStr.charAt(0) >= 128) jsonStr = jsonStr.substring(1); for(int i = 0; i < jsonStr.length(); i ++) { char ch = jsonStr.charAt(i); if(ch == '[' || ch == '{' || ch =='\r' || ch == '\n') { jsonStr = jsonStr.substring(i); break; } if(ch == '=') { jsonStr = jsonStr.substring(i + 1); break; } } return jsonStr.replace("\r", ""); } /** *

* Parse a JSON entity from a {@link CharacterIterator}. *

* @see #read(String) */ public static Json read(JsonCharacterIterator it) { return (Json)new Reader().read(it); } /** *

Return the null Json instance.

*/ public static Json nil() { return factory().nil(); } /** *

Return a newly constructed, empty JSON object.

*/ public static Json object() { return factory().object(); } /** *

Return a new JSON object initialized from the passed list of * name/value pairs. The number of arguments must * be even. Each argument at an even position is taken to be a name * for the following value. The name arguments are normally of type * Java String, but they can be of any other type having an appropriate * toString method. Each value is first converted * to a Json instance using the {@link #make(Object)} method. *

* @param args A sequence of name value pairs. */ public static Json object(Object...args) { Json j = object(); if (args.length % 2 != 0) throw new IllegalArgumentException("An even number of arguments is expected."); for (int i = 0; i < args.length; i++) j.set(args[i].toString(), factory().make(args[++i])); return j; } /** *

Return a new constructed, empty JSON array.

*/ public static Json array() { return factory().array(); } /** *

Return a new JSON array filled up with the list of arguments.

* * @param args The initial content of the array. */ public static Json array(Object...args) { Json A = array(); for (Object x : args) A.add(factory().make(x)); return A; } /** *

* Convert an arbitrary Java instance to a {@link Json} instance. *

* *

* Maps, Collections and arrays are recursively copied where each of * their elements concerted into Json instances as well. The keys * of a {@link Map} parameter are normally strings, but anything with a meaningful * toString implementation will work as well. *

* * @param anything * @return The Json. This method will never return null. It will * throw an {@link IllegalArgumentException} if it doesn't know how to convert the argument * to a Json instance. * @throws IllegalArgumentException when the concrete type of the parameter is * unknown. */ public static Json make(Object anything) { return factory().make(anything); } // end of static utility method section Json enclosing = null; protected Json() { } protected Json(Json enclosing) { this.enclosing = enclosing; } protected Json getLinkOneTail(Json jsonRoot) { return this; } public Map safeGetJsonMap(String key) { if(!this.has(key)) return null; return at(key).asJsonMap(); } public List safeGetJsonList(String key) { if(!this.has(key)) return null; return at(key).asJsonList(); } public Json safeGetJson(String key) { return at(key); } public Json getJsonPath(String path, boolean skipNFind) throws RuntimeException { try { Json jsonRoot = this; String[] spPath = path.split(_jsonPathSplit.replace("\\", "\\\\").replace("|", "\\|")); for(int i = 0; i < spPath.length; i ++) { String subPath = spPath[i]; if(subPath.length() == 0) continue; if(subPath.charAt(0) == '#') { int index = Integer.parseInt(subPath.substring(1)); if(skipNFind && index >= jsonRoot.asJsonList().size()) return null; jsonRoot = jsonRoot.at(index); } else { if(skipNFind && !jsonRoot.has(spPath[i])) return null; jsonRoot = jsonRoot.getJson(spPath[i]); } } return jsonRoot; } catch(Exception ex) { throw new RuntimeException(String.format("get json path [%s] error:[%s]", path, this.toString()), ex); } } public Json getJsonDefault(String key) throws RuntimeException { if(this.has(key)) return this.at(key); return this.getJson("*default*"); } public Json getJson(String key) { Json jsonRet = at(key); if(jsonRet == null) throw new RuntimeException(String.format("can't find json key [%s] for [%s]", key, this.toString())); return jsonRet; } public String safeGetStr(String key, String defValue) { if(!this.has(key)) return defValue; return at(key).asString(); } public int safeGetInt(String key, int defValue) { if(!this.has(key)) return defValue; return at(key).asInteger(); } public boolean safeGetBoolean(String key, boolean defValue) { if(!this.has(key)) return defValue; return at(key).asBoolean(); } /** *

Explicitly set the parent of this element. The parent is presumably an array * or an object. Normally, there's no need to call this method as the parent is * automatically set by the framework. You may need to call it however, if you implement * your own {@link Factory} with your own implementations of the Json types. *

* * @param enclosing The parent element. */ public void attachTo(Json enclosing) { this.enclosing = enclosing; } /** *

Return the Json entity, if any, enclosing this * Json. The returned value can be null or * a Json object or list, but not one of the primitive types.

*/ public final Json up() { return enclosing; } /** *

Return a clone (a duplicate) of this Json entity. Note that cloning * is deep if array and objects. Primitives are also cloned, even though their values are immutable * because the new enclosing entity (the result of the {@link #up()} method) may be different. * since they are immutable.

*/ public Json dup() { return this; } /** *

Return the Json element at the specified index of this * Json array. This method applies only to Json arrays. *

* * @param index The index of the desired element. */ public Json at(int index) { throw new UnsupportedOperationException(); } /** *

* Return the specified property of a Json object or null * if there's no such property. This method applies only to Json objects. *

*/ public Json at(String property) { throw new UnsupportedOperationException(); } /** *

* Return the specified property of a Json object if it exists. * If it doesn't, then create a new property with value the def * parameter and return that parameter. *

* * @param property The property to return. * @param def The default value to set and return in case the property doesn't exist. */ public final Json at(String property, Json def) { Json x = at(property); if (x == null) { set(property, def); return def; } else return x; } /** *

* Return the specified property of a Json object if it exists. * If it doesn't, then create a new property with value the def * parameter and return that parameter. *

* * @param property The property to return. * @param def The default value to set and return in case the property doesn't exist. */ public final Json at(String property, Object def) { return at(property, make(def)); } /** *

* Return true if this Json object has the specified property * and false otherwise. *

* * @param property The name of the property. */ public boolean has(String property) { throw new UnsupportedOperationException(); } /** *

* Return true if and only if this Json object has a property with * the specified value. In particular, if the object has no such property false is returned. *

* * @param property The property name. * @param value The value to compare with. Comparison is done via the equals method. * If the value is not an instance of Json, it is first converted to * such an instance. * @return */ public boolean is(String property, Object value) { throw new UnsupportedOperationException(); } /** *

* Return true if and only if this Json array has an element with * the specified value at the specified index. In particular, if the array has no element at * this index, false is returned. *

* * @param property The property name. * @param value The value to compare with. Comparison is done via the equals method. * If the value is not an instance of Json, it is first converted to * such an instance. * @return */ public boolean is(int index, Object value) { throw new UnsupportedOperationException(); } /** *

* Add the specified Json element to this array. *

* * @return this */ public Json add(Json el) { throw new UnsupportedOperationException(); } /** *

* Add an arbitrary Java object to this Json array. The object * is first converted to a Json instance by calling the static * {@link #make} method. *

* * @param anything Any Java object that can be converted to a Json instance. * @return this */ public final Json add(Object anything) { return add(make(anything)); } /** *

* Remove the specified property from a Json object and return * that property. *

* * @param property The property to be removed. * @return The property value or null if the object didn't have such * a property to begin with. */ public Json atDel(String property) { throw new UnsupportedOperationException(); } /** *

* Remove the element at the specified index from a Json array and return * that element. *

* * @param index The index of the element to delete. * @return The element value. */ public Json atDel(int index) { throw new UnsupportedOperationException(); } /** *

* Delete the specified property from a Json object. *

* * @param property The property to be removed. * @return this */ public Json delAt(String property) { throw new UnsupportedOperationException(); } /** *

* Remove the element at the specified index from a Json array. *

* * @param index The index of the element to delete. * @return this */ public Json delAt(int index) { throw new UnsupportedOperationException(); } /** *

* Remove the specified element from a Json array. *

* * @param el The element to delete. * @return this */ public Json remove(Json el) { throw new UnsupportedOperationException(); } /** *

* Remove the specified Java object (converted to a Json instance) * from a Json array. This is equivalent to * remove({@link #make(anything)}). *

* * @param anything The object to delete. * @return this */ public final Json remove(Object anything) { return remove(make(anything)); } /** *

* Set a Json objects's property. *

* * @param property The property name. * @param value The value of the property. * @return this */ public Json set(String property, Json value) { throw new UnsupportedOperationException(); } /** *

* Set a Json objects's property. *

* * @param property The property name. * @param value The value of the property, converted to a Json representation * with {@link #make}. * @return this */ public final Json set(String property, Object value) { return set(property, make(value)); } /** *

* Combine this object or array with the passed in object or array. The types of * this and the object argument must match. If both are * Json objects, all properties of the parameter are added to this. * If both are arrays, all elements of the parameter are appended to this *

* @param object The object or array whose properties or elements must be added to this * Json object or array. * @return this */ public Json with(Json object) { throw new UnsupportedOperationException(); } /** *

Return the underlying value of this Json entity. The actual value will * be a Java Boolean, String, Number, Map, List or null. For complex entities (objects * or arrays), the method will perform a deep copy and extra underlying values recursively * for all nested elements.

*/ public Object getValue() { throw new UnsupportedOperationException(); } /** *

Return the boolean value of a boolean Json instance. Call * {@link #isBoolean()} first if you're not sure this instance is indeed a * boolean.

*/ public boolean asBoolean() { throw new UnsupportedOperationException(); } /** *

Return the string value of a string Json instance. Call * {@link #isString()} first if you're not sure this instance is indeed a * string.

*/ public String asString() { throw new UnsupportedOperationException(); } /** *

Return the integer value of a number Json instance. Call * {@link #isNumber()} first if you're not sure this instance is indeed a * number.

*/ public int asInteger() { throw new UnsupportedOperationException(); } /** *

Return the float value of a float Json instance. Call * {@link #isNumber()} first if you're not sure this instance is indeed a * number.

*/ public float asFloat() { throw new UnsupportedOperationException(); } /** *

Return the double value of a number Json instance. Call * {@link #isNumber()} first if you're not sure this instance is indeed a * number.

*/ public double asDouble() { throw new UnsupportedOperationException(); } /** *

Return the long value of a number Json instance. Call * {@link #isNumber()} first if you're not sure this instance is indeed a * number.

*/ public long asLong() { throw new UnsupportedOperationException(); } /** *

Return the short value of a number Json instance. Call * {@link #isNumber()} first if you're not sure this instance is indeed a * number.

*/ public short asShort() { throw new UnsupportedOperationException(); } /** *

Return the byte value of a number Json instance. Call * {@link #isNumber()} first if you're not sure this instance is indeed a * number.

*/ public byte asByte() { throw new UnsupportedOperationException(); } /** *

Return the first character of a string Json instance. Call * {@link #isString()} first if you're not sure this instance is indeed a * string.

*/ public char asChar() { throw new UnsupportedOperationException(); } /** *

Return a map of the properties of an object Json instance. The map * is a clone of the object and can be modified safely without affecting it. Call * {@link #isObject()} first if you're not sure this instance is indeed a * Json object.

*/ public Map asMap() { throw new UnsupportedOperationException(); } /** *

Return the underlying map of properties of a Json object. The returned * map is the actual object representation so any modifications to it are modifications * of the Json object itself. Call * {@link #isObject()} first if you're not sure this instance is indeed a * Json object. *

*/ public Map asJsonMap() { throw new UnsupportedOperationException(); } /** *

Return a list of the elements of a Json array. The list is a clone * of the array and can be modified safely without affecting it. Call * {@link #isArray()} first if you're not sure this instance is indeed a * Json array. *

*/ public List asList() { throw new UnsupportedOperationException(); } /** *

Return the underlying {@link List} representation of a Json array. * The returned list is the actual array representation so any modifications to it * are modifications of the Json array itself. Call * {@link #isArray()} first if you're not sure this instance is indeed a * Json array. *

*/ public List asJsonList() { throw new UnsupportedOperationException(); } /** *

Return true if this is a Json null entity * and false otherwise. *

*/ public boolean isNull() { return false; } /** *

Return true if this is a Json string entity * and false otherwise. *

*/ public boolean isString() { return false; } /** *

Return true if this is a Json number entity * and false otherwise. *

*/ public boolean isNumber() { return false; } /** *

Return true if this is a Json boolean entity * and false otherwise. *

*/ public boolean isBoolean() { return false; } /** *

Return true if this is a Json array (i.e. list) entity * and false otherwise. *

*/ public boolean isArray() { return false; } /** *

Return true if this is a Json object entity * and false otherwise. *

*/ public boolean isObject(){ return false; } /** *

Return true if this is a Json primitive entity * (one of string, number or boolean) and false otherwise. *

*/ public boolean isPrimitive() { return isString() || isNumber() || isBoolean(); } /** *

* Json-pad this object as an argument to a callback function. *

* * @param callback The name of the callback function. Can be null or empty, * in which case no padding is done. * @return The jsonpadded, stringified version of this object if the callback * is not null or empty, or just the stringified version of the object. */ public String pad(String callback) { return (callback != null && callback.length() > 0) ? callback + "(" + toString() + ");" : toString(); } //------------------------------------------------------------------------- // END OF PUBLIC INTERFACE //------------------------------------------------------------------------- static class NullJson extends Json { NullJson() {} NullJson(Json e) {super(e);} public Object getValue() { return null; } public Json dup() { return new NullJson(); } public boolean isNull() { return true; } public String toString() { return "null"; } public List asList() { return (List)Collections.singletonList(null); } public int hashCode() { return 0; } public boolean equals(Object x) { return x instanceof NullJson; } } static NullJson topnull = new NullJson(); static abstract class WriteBackJson extends Json { protected String _link; protected Json _parent; protected Object _key; protected boolean _writed = false; protected WriteBackJson(String link) { _link = link; } protected abstract Json writeBack(Json rootJson, boolean throwDup); protected void setWriteBackParent(Json parent, Object key) { _parent = parent; _key = key; } protected boolean isDupWriteBack(boolean throwDup) { if(_writed) { if(throwDup) throw new RuntimeException(String.format("dup writeBack for link [%s]", _link)); else return true; } _writed = true; return false; } protected void repaceOneJson(Json jsonReplace, Json jsonRoot) { if(_parent == null) return; jsonReplace = jsonReplace.getLinkOneTail(jsonRoot); if(_parent instanceof ArrayJson) ((ArrayJson)_parent).set((int)(Integer)_key, jsonReplace); else if(_parent instanceof ObjectJson) ((ObjectJson)_parent).set(_key.toString(), jsonReplace); else throw new RuntimeException("unsupport parent json"); } protected void replaceJsonList(Json jsonList, Json jsonRoot) { if(_parent == null) return; if(!(_parent instanceof ArrayJson)) throw new RuntimeException("parent json must be ArrayJson"); jsonList = jsonList.getLinkOneTail(jsonRoot); if(!jsonList.isArray()) throw new RuntimeException("linked json must be ArrayJson"); ((ArrayJson)_parent).replaceLinkList((Integer)_key, jsonList.asJsonList(), jsonRoot); } protected void replaceJsonMap(Json jsonMap, char mode, Json jsonRoot, Pattern patFilter, String replaceFilter) { if(_parent == null) return; if(!(_parent instanceof ObjectJson)) throw new RuntimeException("parent json must be ObjectJson"); jsonMap = jsonMap.getLinkOneTail(jsonRoot); if(!jsonMap.isObject()) throw new RuntimeException("linked json must be ObjectJson"); ((ObjectJson)_parent).replaceLinkMap((String)_key, mode, jsonMap.asJsonMap(), jsonRoot, patFilter, replaceFilter); } } static class LinkOneJson extends WriteBackJson { protected LinkOneJson(String link) { super(link); } @Override protected Json getLinkOneTail(Json rootJson) { Json json = writeBack(rootJson, true); return json; } @Override protected Json writeBack(Json jsonRoot, boolean throwDup) { if(isDupWriteBack(throwDup)) return null; Json json = jsonRoot.getJsonPath(_link, false).getLinkOneTail(jsonRoot); repaceOneJson(json, jsonRoot); return json; } public String toString() { return "@one:" + _link; } } static class LinkListJson extends WriteBackJson { protected LinkListJson(String link) { super(link); } @Override protected Json getLinkOneTail(Json rootJson) { Json json = writeBack(rootJson, true); return json; } @Override protected Json writeBack(Json rootJson, boolean throwDup) throws RuntimeException { if(isDupWriteBack(throwDup)) return null; Json json = rootJson.getJsonPath(_link, false); replaceJsonList(json, rootJson); return _parent; } public String toString() { return "@list:" + _link; } } static class LinkMapJson extends WriteBackJson { private char _mode; private Pattern _patFilter; private String _replaceFilter; public LinkMapJson(String link, char mode, String regFilter, String replaceFilter) { super(link); _mode = mode; _patFilter = (regFilter == null || regFilter.length() == 0) ? null : Pattern.compile(regFilter); _replaceFilter = replaceFilter; } @Override protected Json getLinkOneTail(Json rootJson) { Json json = writeBack(rootJson, true); return json; } @Override public Json writeBack(Json rootJson, boolean throwDup) throws RuntimeException { if(isDupWriteBack(throwDup)) return null; Json json = rootJson.getJsonPath(_link, false); replaceJsonMap(json, _mode, rootJson, _patFilter, _replaceFilter); return _parent; } public String toString() { return "@map:" + _link; } } static class BooleanJson extends Json { boolean val; BooleanJson() {} BooleanJson(Json e) {super(e);} BooleanJson(Boolean val, Json e) { super(e); this.val = val; } public Object getValue() { return val; } public Json dup() { return new BooleanJson(val, null); } public boolean asBoolean() { return val; } public boolean isBoolean() { return true; } public String toString() { return val ? "true" : "false"; } @SuppressWarnings("unchecked") public List asList() { return (List)(List)Collections.singletonList(val); } public int hashCode() { return val ? 1 : 0; } public boolean equals(Object x) { return x instanceof BooleanJson && ((BooleanJson)x).val == val; } } static class StringJson extends Json { String val; StringJson() {} StringJson(Json e) {super(e);} StringJson(String val, Json e) { super(e); this.val = val; } public Json dup() { return new StringJson(val, null); } public boolean isString() { return true; } public Object getValue() { return val; } public String asString() { return val; } public int asInteger() { return Integer.parseInt(val); } public float asFloat() { return Float.parseFloat(val); } public double asDouble() { return Double.parseDouble(val); } public long asLong() { return Long.parseLong(val); } public short asShort() { return Short.parseShort(val); } public byte asByte() { return Byte.parseByte(val); } public char asChar() { return val.charAt(0); } @SuppressWarnings("unchecked") public List asList() { return (List)(List)Collections.singletonList(val); } public String toString() { return '"' + escaper.escapeJsonString(val) + '"'; } public int hashCode() { return val.hashCode(); } public boolean equals(Object x) { return x instanceof StringJson && ((StringJson)x).val.equals(val); } } static class NumberJson extends Json { Number val; NumberJson() {} NumberJson(Json e) {super(e);} NumberJson(Number val, Json e) { super(e); this.val = val; } public Json dup() { return new NumberJson(val, null); } public boolean isNumber() { return true; } public Object getValue() { return val; } public String asString() { return val.toString(); } public int asInteger() { return val.intValue(); } public float asFloat() { return val.floatValue(); } public double asDouble() { return val.doubleValue(); } public long asLong() { return val.longValue(); } public short asShort() { return val.shortValue(); } public byte asByte() { return val.byteValue(); } @SuppressWarnings("unchecked") public List asList() { return (List)(List)Collections.singletonList(val); } public String toString() { return SMTStatic.toString(val); } public int hashCode() { return val.hashCode(); } public boolean equals(Object x) { return x instanceof NumberJson && ((NumberJson)x).val.equals(val); } } static class ArrayJson extends Json { List L = new ArrayList(); ArrayJson() { } ArrayJson(Json e) { super(e); } public Json dup() { ArrayJson j = new ArrayJson(); for (Json e : L) { Json v = e.dup(); v.enclosing = j; j.L.add(v); } return j; } public List asJsonList() { return L; } public List asList() { ArrayList A = new ArrayList(); for (Json x: L) A.add(x.getValue()); return A; } public boolean is(int index, Object value) { if (index < 0 || index >= L.size()) return false; else return L.get(index).equals(make(value)); } public Object getValue() { return asList(); } public boolean isArray() { return true; } public Json at(int index) { return L.get(index); } public Json add(Json el) { L.add(el); el.enclosing = this; return this; } public Json remove(Json el) { L.remove(el); el.enclosing = null; return this; } public void set(int index, Json json) { L.set(index, json); } private Json[] copyList(List list) { Json[] jsonArr = new Json[list.size()]; for(int i = 0; i < list.size(); i ++) jsonArr[i] = list.get(i); return jsonArr; } protected void replaceLinkList(int index, List list, Json jsonRoot) { L.remove(index); if(list.size() == 0) return; Json[] copyList = copyList(list); for(int i = 0; i < copyList.length; i ++) index = insertLinkItem(index, copyList[i], jsonRoot); } protected int insertLinkItem(int index, Json jsonValue, Json jsonRoot) { if(jsonValue instanceof LinkListJson) { List jsonList = jsonValue.getLinkOneTail(jsonRoot).asJsonList(); Json[] copyList = copyList(jsonList); for(int i = 0; i < copyList.length; i ++) index = insertLinkItem(index, copyList[i], jsonRoot); return index; } else { L.add(index, jsonValue.getLinkOneTail(jsonRoot)); return index + 1; } } public Json with(Json object) { if (!object.isArray()) throw new UnsupportedOperationException(); // what about "enclosing" here? we don't have a provision where a Json // element belongs to more than one enclosing elements... L.addAll(((ArrayJson)object).L); return this; } public Json atDel(int index) { Json el = L.remove(index); if (el != null) el.enclosing = null; return el; } public Json delAt(int index) { Json el = L.remove(index); if (el != null) el.enclosing = null; return this; } public String toString() { StringBuilder sb = new StringBuilder("["); for (Iterator i = L.iterator(); i.hasNext(); ) { sb.append(i.next().toString()); if (i.hasNext()) sb.append(","); } sb.append("]"); return sb.toString(); } public int hashCode() { return L.hashCode(); } public boolean equals(Object x) { return x instanceof ArrayJson && ((ArrayJson)x).L.equals(L); } } static class ObjectJson extends Json { Map object = new HashMap(); ObjectJson() { } ObjectJson(Json e) { super(e); } public Json dup() { ObjectJson j = new ObjectJson(); for (Map.Entry e : object.entrySet()) { Json v = e.getValue().dup(); v.enclosing = j; j.object.put(e.getKey(), v); } return j; } public boolean has(String property) { return object.containsKey(property); } public boolean is(String property, Object value) { Json p = object.get(property); if (p == null) return false; else return p.equals(make(value)); } public Json at(String property) { return object.get(property); } public Json with(Json x) { if (!x.isObject()) throw new UnsupportedOperationException(); object.putAll(((ObjectJson)x).object); return this; } public Json set(String property, Json el) { if (property == null) throw new IllegalArgumentException("Null property names are not allowed, value is " + el); el.enclosing = this; object.put(property, el); return this; } public void replaceLinkMap(String key, char mode, Map jsonMap, Json jsonRoot, Pattern patFilter, String replaceFilter) { if(key.length() != 0) this.delAt(key); String[] entryKeys = new String[jsonMap.size()]; Json[] entryValues = new Json[jsonMap.size()]; int count = 0; for(Entry entry : jsonMap.entrySet()) { entryKeys[count] = entry.getKey(); entryValues[count] = entry.getValue(); count ++; } for(int i = 0; i < count; i ++) { String key1 = entryKeys[i]; // 如果存在复制前缀过滤,则首先对其进行过滤 if(patFilter != null) { Matcher m = patFilter.matcher(key1); if(!m.find()) continue; if(replaceFilter != null) key1 = m.replaceAll(replaceFilter); } if(this.has(key1)) { if(mode == 'I') continue; else if(mode == 'E') throw new RuntimeException(String.format("key [%s] has been found in map", key)); } Json value = entryValues[i]; if(value instanceof LinkMapJson) { Map jsonMap1 = value.getLinkOneTail(jsonRoot).asJsonMap(); replaceLinkMap(key1, mode, jsonMap1, jsonRoot, null, null); } else { this.set(key1, value.getLinkOneTail(jsonRoot)); } } } public Json atDel(String property) { Json el = object.remove(property); if (el != null) el.enclosing = null; return el; } public Json delAt(String property) { Json el = object.remove(property); if (el != null) el.enclosing = null; return this; } public Object getValue() { return asMap(); } public boolean isObject() { return true; } public Map asMap() { HashMap m = new HashMap(); for (Map.Entry e : object.entrySet()) m.put(e.getKey(), e.getValue().getValue()); return m; } @Override public Map asJsonMap() { return object; } public String toString() { StringBuilder sb = new StringBuilder("{"); for (Iterator> i = object.entrySet().iterator(); i.hasNext(); ) { Map.Entry x = i.next(); sb.append('"'); sb.append(escaper.escapeJsonString(x.getKey())); sb.append('"'); sb.append(":"); sb.append(x.getValue().toString()); if (i.hasNext()) sb.append(","); } sb.append("}"); return sb.toString(); } public int hashCode() { return object.hashCode(); } public boolean equals(Object x) { return x instanceof ObjectJson && ((ObjectJson)x).object.equals(object); } } // ------------------------------------------------------------------------ // Extra utilities, taken from around the internet: // ------------------------------------------------------------------------ /* * Copyright (C) 2008 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * A utility class that is used to perform JSON escaping so that ", <, >, etc. characters are * properly encoded in the JSON string representation before returning to the client code. * *

This class contains a single method to escape a passed in string value: *

	 *   String jsonStringValue = "beforeQuote\"afterQuote";
	 *   String escapedValue = Escaper.escapeJsonString(jsonStringValue);
	 * 

* * @author Inderjeet Singh * @author Joel Leitch */ static Escaper escaper = new Escaper(false); final static class Escaper { private static final char[] HEX_CHARS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; private static final Set JS_ESCAPE_CHARS; private static final Set HTML_ESCAPE_CHARS; static { Set mandatoryEscapeSet = new HashSet(); mandatoryEscapeSet.add('"'); mandatoryEscapeSet.add('\\'); JS_ESCAPE_CHARS = Collections.unmodifiableSet(mandatoryEscapeSet); Set htmlEscapeSet = new HashSet(); htmlEscapeSet.add('<'); htmlEscapeSet.add('>'); htmlEscapeSet.add('&'); htmlEscapeSet.add('='); htmlEscapeSet.add('\''); // htmlEscapeSet.add('/'); -- Removing slash for now since it causes some incompatibilities HTML_ESCAPE_CHARS = Collections.unmodifiableSet(htmlEscapeSet); } private final boolean escapeHtmlCharacters; Escaper(boolean escapeHtmlCharacters) { this.escapeHtmlCharacters = escapeHtmlCharacters; } public String escapeJsonString(CharSequence plainText) { StringBuilder escapedString = new StringBuilder(plainText.length() + 20); try { escapeJsonString(plainText, escapedString); } catch (IOException e) { throw new RuntimeException(e); } return escapedString.toString(); } private void escapeJsonString(CharSequence plainText, StringBuilder out) throws IOException { int pos = 0; // Index just past the last char in plainText written to out. int len = plainText.length(); for (int charCount, i = 0; i < len; i += charCount) { int codePoint = Character.codePointAt(plainText, i); charCount = Character.charCount(codePoint); if (!isControlCharacter(codePoint) && !mustEscapeCharInJsString(codePoint)) { continue; } out.append(plainText, pos, i); pos = i + charCount; switch (codePoint) { case '\b': out.append("\\b"); break; case '\t': out.append("\\t"); break; case '\n': out.append("\\n"); break; case '\f': out.append("\\f"); break; case '\r': out.append("\\r"); break; case '\\': out.append("\\\\"); break; case '/': out.append("\\/"); break; case '"': out.append("\\\""); break; default: appendHexJavaScriptRepresentation(codePoint, out); break; } } out.append(plainText, pos, len); } private boolean mustEscapeCharInJsString(int codepoint) { if (!Character.isSupplementaryCodePoint(codepoint)) { char c = (char) codepoint; return JS_ESCAPE_CHARS.contains(c) || (escapeHtmlCharacters && HTML_ESCAPE_CHARS.contains(c)); } return false; } private static boolean isControlCharacter(int codePoint) { // JSON spec defines these code points as control characters, so they must be escaped return codePoint < 0x20 || codePoint == 0x2028 // Line separator || codePoint == 0x2029 // Paragraph separator || (codePoint >= 0x7f && codePoint <= 0x9f); } private static void appendHexJavaScriptRepresentation(int codePoint, Appendable out) throws IOException { if (Character.isSupplementaryCodePoint(codePoint)) { // Handle supplementary unicode values which are not representable in // javascript. We deal with these by escaping them as two 4B sequences // so that they will round-trip properly when sent from java to javascript // and back. char[] surrogates = Character.toChars(codePoint); appendHexJavaScriptRepresentation(surrogates[0], out); appendHexJavaScriptRepresentation(surrogates[1], out); return; } out.append("\\u") .append(HEX_CHARS[(codePoint >>> 12) & 0xf]) .append(HEX_CHARS[(codePoint >>> 8) & 0xf]) .append(HEX_CHARS[(codePoint >>> 4) & 0xf]) .append(HEX_CHARS[codePoint & 0xf]); } } @SuppressWarnings("deprecation") public static class Reader { private static final Object OBJECT_END = new Object(); private static final Object ARRAY_END = new Object(); private static final Object COLON = new Object(); private static final Object COMMA = new Object(); public static final int FIRST = 0; public static final int CURRENT = 1; public static final int NEXT = 2; private static Map escapes = new HashMap(); static { escapes.put(new Character('\''), new Character('\'')); escapes.put(new Character('"'), new Character('"')); escapes.put(new Character('\\'), new Character('\\')); escapes.put(new Character('/'), new Character('/')); escapes.put(new Character('b'), new Character('\b')); escapes.put(new Character('f'), new Character('\f')); escapes.put(new Character('n'), new Character('\n')); escapes.put(new Character('r'), new Character('\r')); escapes.put(new Character('t'), new Character('\t')); escapes.put(new Character(','), new Character(',')); } private String _rootPath; private JsonCharacterIterator it; private char c; private Object token; private StringBuffer buf = new StringBuffer(); private IJsonParseExt _jsonParseExt = null; private Map _propMap = null; private Map _defineMap = null; private List _writeBackList = null; private List _parsePath = new ArrayList(); public void importDefineMap(String[] defineMap) { if(_defineMap == null) _defineMap = new HashMap(); for(int i = 0; i < defineMap.length; i += 2) { String key = defineMap[i + 0]; String value = defineMap[i + 1]; _defineMap.put(key, value); } } public void setRootPath(String rootPath) { _rootPath = rootPath; } private char next() { if (it.getIndex() == it.getEndIndex()) throw new RuntimeException("Reached end of input at the " + it.getIndex() + "th character."); c = it.next(); return c; } private char previous() { c = it.previous(); return c; } private void skipWhiteSpace() { do { if (Character.isWhitespace(c)) ; else if (c == '/') { next(); if (c == '*') { // skip multiline comments while (c != CharacterIterator.DONE) if (next() == '*' && next() == '/') break; if (c == CharacterIterator.DONE) throw new RuntimeException("Unterminated comment while parsing JSON string."); } else if (c == '/') while (c != '\n' && c != CharacterIterator.DONE) next(); else { previous(); break; } } else break; } while (next() != CharacterIterator.DONE); } private Object read(JsonCharacterIterator ci, int start) { it = ci; switch (start) { case FIRST: c = it.first(); break; case CURRENT: c = it.current(); break; case NEXT: c = it.next(); break; } return read(); } private Object read(JsonCharacterIterator it) { return read(it, NEXT); } public Object read(File file, IJsonParseExt jsonParseExt) { String jsonStr = readJsonFile(file); String rootPath = file.getAbsolutePath(); rootPath = rootPath.substring(0, rootPath.length() - file.getName().length()); this.setRootPath(rootPath); return this.read(jsonStr, jsonParseExt); } public Object read(String string, IJsonParseExt jsonParseExt) { try { this._jsonParseExt = jsonParseExt; Object ret = read(new JsonCharacterIterator(string), FIRST); if(_writeBackList != null) { for(int i = _writeBackList.size() - 1; i >= 0 ; i --) { WriteBackJson json = _writeBackList.get(i); json.writeBack((Json)ret, false); } } return ret; } catch(Exception ex) { throw new RuntimeException(String.format("parse error [%s]\n", ex.getMessage()) + it.getErrorLine(),ex); } } public void writeBackList(Object ret) { } @SuppressWarnings("unchecked") private T read() { skipWhiteSpace(); char ch = c; next(); switch (ch) { case '(': token = readLargeString();break; case '\'':token = readMacro();break; case '"': token = readString(); break; case '[': token = readArray(); break; case ']': token = ARRAY_END; break; case ',': token = COMMA; break; case '{': token = readObject(); break; case '}': token = OBJECT_END; break; case ':': token = COLON; break; case 't': if (c != 'r' || next() != 'u' || next() != 'e') throw new RuntimeException("Invalid JSON token: expected 'true' keyword."); next(); token = factory().bool(Boolean.TRUE); break; case'f': if (c != 'a' || next() != 'l' || next() != 's' || next() != 'e') throw new RuntimeException("Invalid JSON token: expected 'false' keyword."); next(); token = factory().bool(Boolean.FALSE); break; case 'n': if (c != 'u' || next() != 'l' || next() != 'l') throw new RuntimeException("Invalid JSON token: expected 'null' keyword."); next(); token = nil(); break; default: c = it.previous(); if (Character.isDigit(c) || c == '-') { token = readNumber(); } else throw new RuntimeException("Invalid JSON near position: " + it.getIndex()); } //System.out.println("token: " + token); // TODO1 enable this line to see the token stream return (T)token; } private String readObjectKey() { Object key = read(); if (key == null) throw new RuntimeException( "Missing object key (don't forget to put quotes!)."); else if (key != OBJECT_END) return ((Json)key).asString(); else return key.toString(); } private Json readObject() { Json ret = object(); String key = readObjectKey(); this._parsePath.add(0, key); while (token != OBJECT_END) { read(); // should be a colon if (token != OBJECT_END) { Json value = read(); if(value instanceof WriteBackJson) { if(key.length() == 0) key = UUID.randomUUID().toString(); ((WriteBackJson)value).setWriteBackParent(ret, key); } if((this._jsonParseExt == null || this._jsonParseExt.beforeAddMap(this, key, value)) && key.length() > 0) ret.set(key, value); if (read() == COMMA) { key = readObjectKey(); this._parsePath.set(0, key); } } } if(this._jsonParseExt != null) this._jsonParseExt.finishAddMap(this); this._parsePath.remove(0); return ret; } private Json readArray() { Json ret = array(); this._parsePath.add(0, String.format("#%d", ret.asJsonList().size())); Object value = read(); while (token != ARRAY_END) { if(value instanceof WriteBackJson) ((WriteBackJson)value).setWriteBackParent(ret, ret.asJsonList().size()); if(this._jsonParseExt == null || this._jsonParseExt.beforeAddArray(this, value)) ret.add((Json)value); if (read() == COMMA) { this._parsePath.set(0, String.format("#%d", ret.asJsonList().size())); value = read(); } else if (token != ARRAY_END) throw new RuntimeException("Unexpected token in array " + token); } if(this._jsonParseExt != null) this._jsonParseExt.finishAddArray(this); this._parsePath.remove(0); return ret; } private Json readNumber() { int length = 0; boolean isFloatingPoint = false; buf.setLength(0); if (c == '-') { add(); } length += addDigits(); if (c == '.') { add(); length += addDigits(); isFloatingPoint = true; } if (c == 'e' || c == 'E') { add(); if (c == '+' || c == '-') { add(); } addDigits(); isFloatingPoint = true; } String s = buf.toString(); Number n = isFloatingPoint ? (length < 17) ? Double.valueOf(s) : new BigDecimal(s) : (length < 20) ? Long.valueOf(s) : new BigInteger(s); return factory().number(n); } private int addDigits() { int ret; for (ret = 0; Character.isDigit(c); ++ret) { add(); } return ret; } public String getRooPath() { if(_rootPath == null) throw new RuntimeException("root json is not file mode"); return _rootPath; } public void addWriteBackJson(WriteBackJson jsonWB) { if(_writeBackList == null) _writeBackList = new ArrayList(); _writeBackList.add(jsonWB); } private Object readLargeString() { buf.setLength(0); while(true) { if(c != '\"') throw new RuntimeException("string start char must be '\"'"); next(); while (c != '"') { if (c == '\\') { next(); if (c == 'u') { add(unicode()); } else { Object value = escapes.get(new Character(c)); if (value != null) { add(((Character) value).charValue()); } } } else { add(); } } next(); skipWhiteSpace(); if(c == ')') break; if(c != '+') throw new RuntimeException("string join char must be '+'"); next(); skipWhiteSpace(); } next(); return factory().string(buf.toString()); } private Object readMacro() { List list = new ArrayList(); buf.setLength(0); while (c != '\'') { if (c == '\\') { next(); if (c == 'u') { add(unicode()); } else { Object value = escapes.get(new Character(c)); if (value != null) { add(((Character) value).charValue()); } } } else if(c == ',') { list.add(buf.toString()); buf.setLength(0); next(); } else { add(); } } list.add(buf.toString()); next(); boolean extParsed = false; if(this._jsonParseExt != null) { Object ret = this._jsonParseExt.parseMacro(this, list); if(ret == null) extParsed = false; else if(ret instanceof WriteBackJson) { addWriteBackJson((WriteBackJson)ret); return (WriteBackJson)ret; } else if(ret instanceof Json) return (Json)ret; else if(ret instanceof Boolean) extParsed = (Boolean)ret; else if(ret instanceof String) { this.insertJsonText((String)ret); extParsed = true; } else throw new RuntimeException("unknow extend property return type for command:" + list.get(0)); } String cmd = list.get(0); // if ext parsed this macro then skip inner macro if(extParsed) { } else if(cmd.charAt(0) == '@') { String defName = cmd.substring(1); if(_defineMap == null || !_defineMap.containsKey(defName)) throw new RuntimeException(String.format("unknow define name [%s]", defName)); Object[] args = new Object[list.size() - 1]; for(int i = 1; i < list.size(); i ++) { args[i - 1] = list.get(i); } return insertJsonText(SMTStatic.stringFormat(_defineMap.get(defName), args)); } // load text file to buufer for parse it // '#include_file,[file name],[json,array,map]?' else if("#include_file".equals(cmd)) { String fileName = convFileName(list.get(1)); try { // 对返回的字符串做转换 String mode = "J"; if(list.size() > 2) mode = list.get(2).toUpperCase(); // 需要对数据做处理 if(mode.charAt(0) != 'J') { String text = readTextFileForJson(new File(fileName), null, mode, null); return this.insertJsonText("null," + text); } else { String text = Json.readJsonFile(new File(fileName)); return this.insertJsonText(text); } } catch(Exception ex) { throw new RuntimeException(String.format("read json file [%s] error", fileName), ex); } } // search fold json file // '#search_file,[root path],[file regex],[tranBase Key],[json,array,map]' else if("#search_file".equals(cmd)) { File rootPath = new File(convFileName(list.get(1))); Pattern patFile = Pattern.compile(list.get(2)); String mode = list.get(3); // 取出后面的宏定义列表 String[] tranKeyList = null; if(list.size() > 4) { tranKeyList = new String[list.size() - 4]; for(int i = 0; i < tranKeyList.length; i ++) { tranKeyList[i] = list.get(i + 4); } } StringBuilder sbInsert = new StringBuilder(); searchTextFileWithFold(rootPath, rootPath.getAbsolutePath(), patFile, mode, sbInsert, tranKeyList); if(sbInsert.length() > 0) return insertJsonText("null," + sbInsert.toString()); else return insertJsonText("null"); } // load file to buufer for parse it // '#nick_prop,[property file name],[property nick]?' else if("#nick_prop".equals(cmd)) { String fileName = convFileName(list.get(1)); String fileNick = (list.size() < 3) ? fileName : list.get(2); try { Properties prop = new Properties(); prop.load(new InputStreamReader(new FileInputStream(fileName),"UTF-8")); if(_propMap == null) _propMap = new HashMap(); _propMap.put(fileNick, prop); return insertJsonText("null"); } catch(Exception ex) { throw new RuntimeException(String.format("read json file [%s] error", fileName), ex); } } // get value from property object and insert as json // '#prop_value, [[property_nick],[property key],[default value]?' else if("#prop_value".equals(cmd)) { String nick = list.get(1); if(_propMap == null || !_propMap.containsKey(nick)) throw new RuntimeException(String.format("read json property [%s] error", nick)); String key = list.get(2); String value = list.size() > 3 ? _propMap.get(nick).getProperty(key, list.get(3)) : _propMap.get(nick).getProperty(key); if(value == null) throw new RuntimeException(String.format("read json include property key [%s] error", key)); return insertJsonText(value); } // get value from property string and insert as json // '#prop_value, [[property_nick],[property key],[default value]?' else if("#prop_string".equals(cmd)) { String nick = list.get(1); if(_propMap == null || !_propMap.containsKey(nick)) throw new RuntimeException(String.format("read json property [%s] error", nick)); String key = list.get(2); String value = list.size() > 3 ? _propMap.get(nick).getProperty(key, list.get(3)) : _propMap.get(nick).getProperty(key); if(value == null) throw new RuntimeException(String.format("read json include property key [%s] error", key)); return insertJsonText(String.format("\"%s\"", SMTStatic.toCStr(value))); } // replace current node to linked node // '#link, [link path]' else if("#link".equals(cmd)) { String link = getJsonFullPath(list.get(1)); if(_writeBackList == null) _writeBackList = new ArrayList(); LinkOneJson json = new LinkOneJson(link); _writeBackList.add(json); return json; } // replac current node to linked node list // '#link_list, [link path]' else if("#link_list".equals(cmd)) { String link = getJsonFullPath(list.get(1)); LinkListJson json = new LinkListJson(link); addWriteBackJson(json); return json; } // replac current node to linked node map // '#link_map, [link path], [overwrite|ignore|error]?, [regex for filter]?, [replace for filter]?' else if("#link_map".equals(cmd)) { String link = getJsonFullPath(list.get(1)); char mode = 'I'; if(list.size() > 2) { mode = list.get(2).toUpperCase().charAt(0); if(mode != 'O' && mode != 'I' && mode != 'E') throw new RuntimeException(String.format("unknow link_map mode [%s]", list.get(2))); } String regFilter = (list.size() > 3) ? list.get(3) : null; String repFilter = (list.size() > 4) ? list.get(4) : null; LinkMapJson json = new LinkMapJson(link, mode, regFilter, repFilter); addWriteBackJson(json); return json; } // define new macro // '#define, [define_name], [define string]' else if("#define".equals(cmd)) { if(_defineMap == null) _defineMap = new HashMap(); _defineMap.put(list.get(1), list.get(2)); return Json.nil(); } else { throw new RuntimeException(String.format("unknow json macro [%s]", cmd)); } return this.read(); } private void searchTextFileWithFold(File fold, String rootPath, Pattern patFile, String mode, StringBuilder sbOut, String[] tranKeyList) { if(!fold.exists() || !fold.isDirectory()) return; for(File file : fold.listFiles()) { if(file.isDirectory()) { searchTextFileWithFold(file, rootPath, patFile, mode, sbOut, tranKeyList); } else if(file.isFile()) { if(patFile.matcher(file.getName()).find()) { String text = readTextFileForJson(file, rootPath, mode, tranKeyList); boolean onlySpace = true; for(int i = 0; i < text.length(); i ++) { char ch = text.charAt(i); if(ch == '\r' || ch == '\n' || ch == '\t' || ch == ' ') continue; onlySpace = false; break; } if(!onlySpace) { if(sbOut.length() > 0) sbOut.append(","); sbOut.append(text); } } } } } private String readTextFileForJson(File file, String rootPath, String mode, String[] tranKeyList) { // 对返回的字符串做转换 String text = Json.readJsonFile(file); // 需要对数据做处理 if(mode.charAt(0) != 'J') { int index; // 去掉头部的'{' for(index = 0; index < text.length(); index ++) { char ch = text.charAt(index); if(ch == '\r' || ch == '\n' || ch == '\t' || ch == ' ') continue; if(ch == '{' || ch == '[') break; } if(index >= text.length()) throw new RuntimeException(String.format("file [%s] is not mode start json file", file.getAbsoluteFile())); if(index > 0) text = text.substring(index + 1); else if(index == 0 && text.length() > 0 && text.charAt(0) == '{' || text.charAt(0) == '[') text = text.substring(1); // 去掉尾部的'}' for(index = text.length() - 1; index >= 0; index --) { char ch = text.charAt(index); if(ch == '\r' || ch == '\n' || ch == '\t' || ch == ' ') continue; if(ch == ']' || ch == '}') break; } if(index >= text.length()) throw new RuntimeException(String.format("file [%s] is not mode end json file", file.getAbsoluteFile())); if(index > 0) text = text.substring(0, index); // 去掉尾部的逗号 for(index = text.length() - 1; index >= 0; index --) { char ch = text.charAt(index); if(ch == '\r' || ch == '\n' || ch == '\t' || ch == ' ') continue; if(ch == ',') text = text.substring(0, index); break; } if(tranKeyList != null) { String fname = file.getName(); int pos = fname.lastIndexOf("."); if(pos > 0) fname = fname.substring(0, pos); String fpath = file.getAbsolutePath().substring(rootPath.length()).substring(1).replace("\\", "/"); pos = fpath.lastIndexOf("."); if(pos > 0) fpath = fpath.substring(0, pos); for(int i = 0; i < tranKeyList.length; i += 2) { text = text.replace(tranKeyList[i + 0], tranKeyList[i + 1] .replace("${JSP_NAME}", fname) .replace("${JSP_PATH}", fpath)); } } } return text; } public String getJsonFullPath(String path) { // 切分路径 String[] sp = path.split("/"); // 如果第一个路径是"."或"..", 则将基本路径复制过来 List fullPath = null; if(".".equals(sp[0]) || "..".equals(sp[0])) { // 如果是当前目录需要回退到倒数第二个,因为最后一个路径是map或索引本身 // 所以回退位置和长度正巧一致 int endPos = sp[0].length(); fullPath = new ArrayList(); for(int i = this._parsePath.size() - 1; i >= endPos; i --) { fullPath.add(this._parsePath.get(i)); } } // 如果第一个路径不是"."或"..",则此路径为全路径 else { return path; } // 计算以后的路径 for(int i = 1; i < sp.length; i ++) { if(".".equals(sp[i])) continue; else if("..".equals(sp[i])) { if(fullPath.size() == 0) throw new RuntimeException(String.format("can't calculate path [%s]", path)); fullPath.remove(fullPath.size() - 1); } else { fullPath.add(sp[i]); } } StringBuilder sb = new StringBuilder(); for(int i = 0; i < fullPath.size(); i ++) { if(sb.length() > 0) sb.append("/"); sb.append(fullPath.get(i)); } return sb.toString(); } public String convFileName(String fileName) { if(fileName.charAt(0) == '*') fileName = fileName.substring(1); else fileName = getRooPath() + fileName; return fileName; } public Json insertJsonText(String text) { it.insertText(text); c = it.current(); return this.read(); } private Json readString() { buf.setLength(0); while (c != '"') { if (c == '\\') { next(); if (c == 'u') { add(unicode()); } else { Object value = escapes.get(new Character(c)); if (value != null) { add(((Character) value).charValue()); } } } else { add(); } } next(); return factory().string(buf.toString()); } private void add(char cc) { buf.append(cc); next(); } private void add() { add(c); } private char unicode() { int value = 0; for (int i = 0; i < 4; ++i) { switch (next()) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': value = (value << 4) + c - '0'; break; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': value = (value << 4) + (c - 'a') + 10; break; case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': value = (value << 4) + (c - 'A') + 10; break; } } return (char) value; } } // END Reader /* public static void main(String [] argv) { Json j = object() .at("menu", object()) .set("id", "file") .set("value", "File") .at("popup", object()) .at("menuitem", array()) .add(object("value", "New", "onclick", "CreateNewDoc()")) .add(object("value", "Open", "onclick", "OpenDoc()")) .add(object("value", "Close", "onclick", "CloseDoc()")) .up() .up() .set("position", 0); System.out.println(j); } */ // public static void main(String[] args) // { // Json j = Json.object("label", "Does CTRL+Q show an \"OPEN\" Bulky Work Order number?"); // String evilJson = j.toString(); // System.out.println("To string: " + evilJson); // System.out.println("Parsed: " + Json.read(evilJson)); // } private static class JsonCharacterIterator implements CharacterIterator { private String text; private int begin; private int end; // invariant: begin <= pos <= end private int pos; /** * Constructs an iterator with an initial index of 0. */ public JsonCharacterIterator(String text) { this(text, 0); } /** * Constructs an iterator with the specified initial index. * * @param text The String to be iterated over * @param pos Initial iterator position */ public JsonCharacterIterator(String text, int pos) { this(text, 0, text.length(), pos); } /** * Constructs an iterator over the given range of the given string, with the * index set at the specified position. * * @param text The String to be iterated over * @param begin Index of the first character * @param end Index of the character following the last character * @param pos Initial iterator position */ public JsonCharacterIterator(String text, int begin, int end, int pos) { if (text == null) throw new NullPointerException(); this.text = text; if (begin < 0 || begin > end || end > text.length()) throw new IllegalArgumentException("Invalid substring range"); if (pos < begin || pos > end) throw new IllegalArgumentException("Invalid position"); this.begin = begin; this.end = end; this.pos = pos; } /** * Reset this iterator to point to a new string. This package-visible * method is used by other java.text classes that want to avoid allocating * new StringCharacterIterator objects every time their setText method * is called. * * @param text The String to be iterated over * @since 1.2 */ public void insertText(String text) { if (text == null) throw new NullPointerException(); this.text = this.text.substring(0, pos) + text + this.text.substring(pos); this.end += text.length(); } /** * Implements CharacterIterator.first() for String. * @see CharacterIterator#first */ public char first() { pos = begin; return current(); } /** * Implements CharacterIterator.last() for String. * @see CharacterIterator#last */ public char last() { if (end != begin) { pos = end - 1; } else { pos = end; } return current(); } /** * Implements CharacterIterator.setIndex() for String. * @see CharacterIterator#setIndex */ public char setIndex(int p) { if (p < begin || p > end) throw new IllegalArgumentException("Invalid index"); pos = p; return current(); } /** * Implements CharacterIterator.current() for String. * @see CharacterIterator#current */ public char current() { if (pos >= begin && pos < end) { return text.charAt(pos); } else { return DONE; } } /** * Implements CharacterIterator.next() for String. * @see CharacterIterator#next */ public char next() { if (pos < end - 1) { pos++; return text.charAt(pos); } else { pos = end; return DONE; } } /** * Implements CharacterIterator.previous() for String. * @see CharacterIterator#previous */ public char previous() { if (pos > begin) { pos--; return text.charAt(pos); } else { return DONE; } } /** * Implements CharacterIterator.getBeginIndex() for String. * @see CharacterIterator#getBeginIndex */ public int getBeginIndex() { return begin; } /** * Implements CharacterIterator.getEndIndex() for String. * @see CharacterIterator#getEndIndex */ public int getEndIndex() { return end; } /** * Implements CharacterIterator.getIndex() for String. * @see CharacterIterator#getIndex */ public int getIndex() { return pos; } /** * Compares the equality of two StringCharacterIterator objects. * @param obj the StringCharacterIterator object to be compared with. * @return true if the given obj is the same as this * StringCharacterIterator object; false otherwise. */ public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof StringCharacterIterator)) return false; JsonCharacterIterator that = (JsonCharacterIterator) obj; if (hashCode() != that.hashCode()) return false; if (!text.equals(that.text)) return false; if (pos != that.pos || begin != that.begin || end != that.end) return false; return true; } /** * Computes a hashcode for this iterator. * @return A hash code */ public int hashCode() { return text.hashCode() ^ pos ^ begin ^ end; } /** * Creates a copy of this iterator. * @return A copy of this */ public Object clone() { try { StringCharacterIterator other = (StringCharacterIterator) super.clone(); return other; } catch (CloneNotSupportedException e) { throw new InternalError(); } } public String getErrorLine() { return this.text.substring(0, pos) + "\n ======== ERROR ========= \n" + text.substring(pos); } } public interface IJsonParseExt { public Object parseMacro(Reader reader, List list); public boolean beforeAddArray(Reader reader, Object value); public boolean beforeAddMap(Reader reader, String key, Json child); public void finishAddArray(Reader reader); public void finishAddMap(Reader reader); } }