package com.smtservlet.util;
|
/*
|
* 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.HashSet;
|
import java.util.Iterator;
|
import java.util.LinkedHashMap;
|
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;
|
|
|
|
/**
|
*
|
* <p>
|
* Represents a JSON (JavaScript Object Notation) entity. For more information about JSON, please see
|
* <a href="http://www.json.org" target="_">http://www.json.org</a>.
|
* </p>
|
*
|
* <p>
|
* 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 <code>Json</code>
|
* 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: <code>Json</code>.
|
* </p>
|
*
|
* <p>
|
* One can examine the concrete type of a <code>Json</code> with one of the <code>isXXX</code> methods:
|
* {@link #isObject()}, {@link #isArray()},{@link #isNumber()},{@link #isBoolean()},{@link #isString()},
|
* {@link #isNull()}.
|
* </p>
|
*
|
* <p>
|
* The underlying representation of a given <code>Json</code> instance can be obtained by calling
|
* the generic {@link #getValue()} method or one of the <code>asXXX</code> 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 <code>asXXX</code> 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()}.
|
* </p>
|
*
|
* <h3>Constructing and Modifying JSON Structures</h3>
|
*
|
* <p>
|
* There are several static factory methods in this class that allow you to create new
|
* <code>Json</code> instances:
|
* </p>
|
*
|
* <table>
|
* <tr><td>{@link #read(String)}</td>
|
* <td>Parse a JSON string and return the resulting <code>Json</code> instance. The syntax
|
* recognized is as defined in <a href="http://www.json.org">http://www.json.org</a>.
|
* </td>
|
* </tr>
|
* <tr><td>{@link #make(Object)}</td>
|
* <td>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 <code>Json</code> itself.</td>
|
* </tr>
|
* <tr><td>{@link #nil()}</td>
|
* <td>Return a <code>Json</code> instance representing JSON <code>null</code>.</td>
|
* </tr>
|
* <tr><td>{@link #object()}</td>
|
* <td>Create and return an empty JSON object.</td>
|
* </tr>
|
* <tr><td>{@link #object(Object...)}</td>
|
* <td>Create and return a JSON object populated with the key/value pairs
|
* passed as an argument sequence. Each even parameter becomes a key (via
|
* <code>toString</code>) and each odd parameter is converted to a <code>Json</code>
|
* value.</td>
|
* </tr>
|
* <tr><td>{@link #array()}</td>
|
* <td>Create and return an empty JSON array.</td>
|
* </tr>
|
* <tr><td>{@link #array(Object...)}</td>
|
* <td>Create and return a JSON array from the list of arguments.</td>
|
* </tr>
|
* </table>
|
*
|
* <p>
|
* 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.
|
* </p>
|
*
|
* <p>
|
* If a <code>Json</code> 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 <code>Json</code> 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
|
* <code>Json</code> 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.
|
* </p>
|
*
|
* <p>
|
* 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.
|
* </p>
|
*
|
* <p>
|
* 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.
|
* </p>
|
*
|
* <h3>Navigating JSON Structures</h3>
|
*
|
* <p>
|
* 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.
|
* </p>
|
*
|
* <p>
|
* 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))'.
|
* </p>
|
*
|
* <p>
|
* 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.
|
* </p>
|
*
|
* <p>
|
* The combination of method chaining when modifying <code>Json</code> 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:
|
* </p>
|
*
|
* <pre><code>
|
* {"menu": {
|
* "id": "file",
|
* "value": "File",
|
* "popup": {
|
* "menuitem": [
|
* {"value": "New", "onclick": "CreateNewDoc()"},
|
* {"value": "Open", "onclick": "OpenDoc()"},
|
* {"value": "Close", "onclick": "CloseDoc()"}
|
* ]
|
* }
|
* "position": 0
|
* }}
|
* </code></pre>
|
*
|
* <pre><code>
|
* 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();
|
* ...
|
* </code></pre>
|
*
|
* <p>
|
* If there's no danger of naming conflicts, a static import of the factory methods (<code>
|
* import static json.Json.*;</code>) would reduce typing even further and make the code more
|
* readable.
|
* </p>
|
*
|
* <h3>Converting to String</h3>
|
*
|
* <p>
|
* 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.
|
* </p>
|
*
|
* @author Borislav Iordanov
|
* @version 1.3
|
*/
|
public class Json
|
{
|
public static String _jsonPathSplit = "|";
|
|
/**
|
* <p>
|
* This interface defines how <code>Json</code> instances are constructed. There is a
|
* default implementation for each kind of <code>Json</code> value, but you can provide
|
* your own implementation. For example, you might want a different representation of
|
* an object than a regular <code>HashMap</code>. Or you might want string comparison to be
|
* case insensitive.
|
* </p>
|
*
|
* <p>
|
* In addition, the {@link #make(Object)} method allows you plug-in your own mapping
|
* of arbitrary Java objects to <code>Json</code> instances. You might want to implement
|
* a Java Beans to JSON mapping or any other JSON serialization that makes sense in your
|
* project.
|
* </p>
|
*
|
* <p>
|
* 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.
|
* </p>
|
*
|
* <p>
|
* The factory implementation used by the <code>Json</code> 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.
|
* </p>
|
*
|
* @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<Factory> threadFactory = new ThreadLocal<Factory>();
|
|
public static Factory factory()
|
{
|
Factory f = threadFactory.get();
|
return f != null ? f : globalFactory;
|
}
|
|
/**
|
* <p>
|
* Specify a global Json {@link Factory} to be used by all threads that don't have a
|
* specific thread-local factory attached to them.
|
* </p>
|
*
|
* @param factory
|
*/
|
public static void setGlobalFactory(Factory factory) { globalFactory = factory; }
|
|
/**
|
* <p>
|
* 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).
|
* </p>
|
*
|
* @param factory
|
*/
|
public static void attachFactory(Factory factory) { threadFactory.set(factory); }
|
|
/**
|
* <p>
|
* 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.
|
* </p>
|
*/
|
public static void dettachFactory() { threadFactory.remove(); }
|
|
/**
|
* <p>
|
* Parse a JSON entity from its string representation.
|
* </p>
|
*
|
* @param jsonAsString A valid JSON representation as per the <a href="http://www.json.org">json.org</a>
|
* grammar. Cannot be <code>null</code>.
|
* @return The JSON entity parsed: an object, array, string, number or boolean, or null. Note that
|
* this method will never return the actual Java <code>null</code>.
|
* @
|
*/
|
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", "");
|
}
|
|
|
/**
|
* <p>
|
* Parse a JSON entity from a {@link CharacterIterator}.
|
* </p>
|
* @see #read(String)
|
*/
|
public static Json read(JsonCharacterIterator it) { return (Json)new Reader().read(it); }
|
/**
|
* <p>Return the <code>null Json</code> instance.</p>
|
*/
|
public static Json nil() { return factory().nil(); }
|
/**
|
* <p>Return a newly constructed, empty JSON object.</p>
|
*/
|
public static Json object() { return factory().object(); }
|
/**
|
* <p>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
|
* <code>toString</code> method. Each value is first converted
|
* to a <code>Json</code> instance using the {@link #make(Object)} method.
|
* </p>
|
* @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;
|
}
|
|
/**
|
* <p>Return a new constructed, empty JSON array.</p>
|
*/
|
public static Json array() { return factory().array(); }
|
|
/**
|
* <p>Return a new JSON array filled up with the list of arguments.</p>
|
*
|
* @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;
|
}
|
|
/**
|
* <p>
|
* Convert an arbitrary Java instance to a {@link Json} instance.
|
* </p>
|
*
|
* <p>
|
* Maps, Collections and arrays are recursively copied where each of
|
* their elements concerted into <code>Json</code> instances as well. The keys
|
* of a {@link Map} parameter are normally strings, but anything with a meaningful
|
* <code>toString</code> implementation will work as well.
|
* </p>
|
*
|
* @param anything
|
* @return The <code>Json</code>. This method will never return <code>null</code>. It will
|
* throw an {@link IllegalArgumentException} if it doesn't know how to convert the argument
|
* to a <code>Json</code> 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<String,Json> safeGetJsonMap(String key)
|
{
|
if(!this.has(key))
|
return null;
|
|
return at(key).asJsonMap();
|
}
|
|
public List<Json> 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(jsonRoot.isArray())
|
{
|
int index = -1;
|
try
|
{
|
index = Integer.parseInt(subPath);
|
}
|
catch(Exception ex)
|
{
|
throw new Exception("array key find not number index : " + spPath[i]);
|
}
|
if(skipNFind && index >= jsonRoot.asJsonList().size())
|
return null;
|
jsonRoot = jsonRoot.at(index);
|
}
|
else if(jsonRoot.isObject())
|
{
|
if(skipNFind && !jsonRoot.has(spPath[i]))
|
return null;
|
jsonRoot = jsonRoot.getJson(spPath[i]);
|
}
|
else
|
{
|
if(skipNFind)
|
return null;
|
throw new Exception("can't move to key : " + 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;
|
|
Json jsonValue = at(key);
|
if(jsonValue.isNull())
|
return null;
|
|
return jsonValue.asString();
|
}
|
|
public int safeGetInt(String key, int defValue)
|
{
|
if(!this.has(key))
|
return defValue;
|
|
return at(key).asInteger();
|
}
|
|
public double safeGetDouble(String key, double defValue)
|
{
|
if(!this.has(key))
|
return defValue;
|
|
return at(key).asDouble();
|
}
|
|
public boolean safeGetBoolean(String key, boolean defValue)
|
{
|
if(!this.has(key))
|
return defValue;
|
|
return at(key).asBoolean();
|
}
|
|
/**
|
* <p>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.
|
* </p>
|
*
|
* @param enclosing The parent element.
|
*/
|
public void attachTo(Json enclosing) { this.enclosing = enclosing; }
|
|
/**
|
* <p>Return the <code>Json</code> entity, if any, enclosing this
|
* <code>Json</code>. The returned value can be <code>null</code> or
|
* a <code>Json</code> object or list, but not one of the primitive types.</p>
|
*/
|
public final Json up() { return enclosing; }
|
|
/**
|
* <p>Return a clone (a duplicate) of this <code>Json</code> 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.</p>
|
*/
|
public Json dup() { return this; }
|
|
/**
|
* <p>Return the <code>Json</code> element at the specified index of this
|
* <code>Json</code> array. This method applies only to Json arrays.
|
* </p>
|
*
|
* @param index The index of the desired element.
|
*/
|
public Json at(int index) { throw new UnsupportedOperationException(); }
|
|
/**
|
* <p>
|
* Return the specified property of a <code>Json</code> object or <code>null</code>
|
* if there's no such property. This method applies only to Json objects.
|
* </p>
|
*/
|
public Json at(String property) { throw new UnsupportedOperationException(); }
|
|
/**
|
* <p>
|
* Return the specified property of a <code>Json</code> object if it exists.
|
* If it doesn't, then create a new property with value the <code>def</code>
|
* parameter and return that parameter.
|
* </p>
|
*
|
* @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;
|
}
|
|
/**
|
* <p>
|
* Return the specified property of a <code>Json</code> object if it exists.
|
* If it doesn't, then create a new property with value the <code>def</code>
|
* parameter and return that parameter.
|
* </p>
|
*
|
* @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));
|
}
|
|
/**
|
* <p>
|
* Return true if this <code>Json</code> object has the specified property
|
* and false otherwise.
|
* </p>
|
*
|
* @param property The name of the property.
|
*/
|
public boolean has(String property) { throw new UnsupportedOperationException(); }
|
|
/**
|
* <p>
|
* Return <code>true</code> if and only if this <code>Json</code> object has a property with
|
* the specified value. In particular, if the object has no such property <code>false</code> is returned.
|
* </p>
|
*
|
* @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 <code>Json</code>, it is first converted to
|
* such an instance.
|
* @return
|
*/
|
public boolean is(String property, Object value) { throw new UnsupportedOperationException(); }
|
|
/**
|
* <p>
|
* Return <code>true</code> if and only if this <code>Json</code> array has an element with
|
* the specified value at the specified index. In particular, if the array has no element at
|
* this index, <code>false</code> is returned.
|
* </p>
|
*
|
* @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 <code>Json</code>, it is first converted to
|
* such an instance.
|
* @return
|
*/
|
public boolean is(int index, Object value) { throw new UnsupportedOperationException(); }
|
|
/**
|
* <p>
|
* Add the specified <code>Json</code> element to this array.
|
* </p>
|
*
|
* @return this
|
*/
|
public Json add(Json el) { throw new UnsupportedOperationException(); }
|
|
/**
|
* <p>
|
* Add an arbitrary Java object to this <code>Json</code> array. The object
|
* is first converted to a <code>Json</code> instance by calling the static
|
* {@link #make} method.
|
* </p>
|
*
|
* @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)); }
|
|
/**
|
* <p>
|
* Remove the specified property from a <code>Json</code> object and return
|
* that property.
|
* </p>
|
*
|
* @param property The property to be removed.
|
* @return The property value or <code>null</code> if the object didn't have such
|
* a property to begin with.
|
*/
|
public Json atDel(String property) { throw new UnsupportedOperationException(); }
|
|
/**
|
* <p>
|
* Remove the element at the specified index from a <code>Json</code> array and return
|
* that element.
|
* </p>
|
*
|
* @param index The index of the element to delete.
|
* @return The element value.
|
*/
|
public Json atDel(int index) { throw new UnsupportedOperationException(); }
|
|
/**
|
* <p>
|
* Delete the specified property from a <code>Json</code> object.
|
* </p>
|
*
|
* @param property The property to be removed.
|
* @return this
|
*/
|
public Json delAt(String property) { throw new UnsupportedOperationException(); }
|
|
/**
|
* <p>
|
* Remove the element at the specified index from a <code>Json</code> array.
|
* </p>
|
*
|
* @param index The index of the element to delete.
|
* @return this
|
*/
|
public Json delAt(int index) { throw new UnsupportedOperationException(); }
|
|
/**
|
* <p>
|
* Remove the specified element from a <code>Json</code> array.
|
* </p>
|
*
|
* @param el The element to delete.
|
* @return this
|
*/
|
public Json remove(Json el) { throw new UnsupportedOperationException(); }
|
|
/**
|
* <p>
|
* Remove the specified Java object (converted to a Json instance)
|
* from a <code>Json</code> array. This is equivalent to
|
* <code>remove({@link #make(anything)})</code>.
|
* </p>
|
*
|
* @param anything The object to delete.
|
* @return this
|
*/
|
public final Json remove(Object anything) { return remove(make(anything)); }
|
|
/**
|
* <p>
|
* Set a <code>Json</code> objects's property.
|
* </p>
|
*
|
* @param property The property name.
|
* @param value The value of the property.
|
* @return this
|
*/
|
public Json set(String property, Json value) { throw new UnsupportedOperationException(); }
|
|
/**
|
* <p>
|
* Set a <code>Json</code> objects's property.
|
* </p>
|
*
|
* @param property The property name.
|
* @param value The value of the property, converted to a <code>Json</code> representation
|
* with {@link #make}.
|
* @return this
|
*/
|
public final Json set(String property, Object value) { return set(property, make(value)); }
|
|
/**
|
* <p>
|
* Combine this object or array with the passed in object or array. The types of
|
* <code>this</code> and the <code>object</code> argument must match. If both are
|
* <code>Json</code> objects, all properties of the parameter are added to <code>this</code>.
|
* If both are arrays, all elements of the parameter are appended to <code>this</code>
|
* </p>
|
* @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(); }
|
|
/**
|
* <p>Return the underlying value of this <code>Json</code> 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.</p>
|
*/
|
public Object getValue() { throw new UnsupportedOperationException(); }
|
|
/**
|
* <p>Return the boolean value of a boolean <code>Json</code> instance. Call
|
* {@link #isBoolean()} first if you're not sure this instance is indeed a
|
* boolean.</p>
|
*/
|
public boolean asBoolean() { throw new UnsupportedOperationException(); }
|
|
/**
|
* <p>Return the string value of a string <code>Json</code> instance. Call
|
* {@link #isString()} first if you're not sure this instance is indeed a
|
* string.</p>
|
*/
|
public String asString() { throw new UnsupportedOperationException(); }
|
|
/**
|
* <p>Return the integer value of a number <code>Json</code> instance. Call
|
* {@link #isNumber()} first if you're not sure this instance is indeed a
|
* number.</p>
|
*/
|
public int asInteger() { throw new UnsupportedOperationException(); }
|
|
public int asHexInt() {throw new UnsupportedOperationException();}
|
|
/**
|
* <p>Return the float value of a float <code>Json</code> instance. Call
|
* {@link #isNumber()} first if you're not sure this instance is indeed a
|
* number.</p>
|
*/
|
public float asFloat() { throw new UnsupportedOperationException(); }
|
|
/**
|
* <p>Return the double value of a number <code>Json</code> instance. Call
|
* {@link #isNumber()} first if you're not sure this instance is indeed a
|
* number.</p>
|
*/
|
public double asDouble() { throw new UnsupportedOperationException(); }
|
|
/**
|
* <p>Return the long value of a number <code>Json</code> instance. Call
|
* {@link #isNumber()} first if you're not sure this instance is indeed a
|
* number.</p>
|
*/
|
public long asLong() { throw new UnsupportedOperationException(); }
|
|
/**
|
* <p>Return the short value of a number <code>Json</code> instance. Call
|
* {@link #isNumber()} first if you're not sure this instance is indeed a
|
* number.</p>
|
*/
|
public short asShort() { throw new UnsupportedOperationException(); }
|
|
/**
|
* <p>Return the byte value of a number <code>Json</code> instance. Call
|
* {@link #isNumber()} first if you're not sure this instance is indeed a
|
* number.</p>
|
*/
|
public byte asByte() { throw new UnsupportedOperationException(); }
|
|
/**
|
* <p>Return the first character of a string <code>Json</code> instance. Call
|
* {@link #isString()} first if you're not sure this instance is indeed a
|
* string.</p>
|
*/
|
public char asChar() { throw new UnsupportedOperationException(); }
|
|
/**
|
* <p>Return a map of the properties of an object <code>Json</code> 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
|
* <code>Json</code> object.</p>
|
*/
|
public Map<String, Object> asMap() { throw new UnsupportedOperationException(); }
|
|
/**
|
* <p>Return the underlying map of properties of a <code>Json</code> object. The returned
|
* map is the actual object representation so any modifications to it are modifications
|
* of the <code>Json</code> object itself. Call
|
* {@link #isObject()} first if you're not sure this instance is indeed a
|
* <code>Json</code> object.
|
* </p>
|
*/
|
public Map<String, Json> asJsonMap() { throw new UnsupportedOperationException(); }
|
|
/**
|
* <p>Return a list of the elements of a <code>Json</code> 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
|
* <code>Json</code> array.
|
* </p>
|
*/
|
public List<Object> asList() { throw new UnsupportedOperationException(); }
|
|
/**
|
* <p>Return the underlying {@link List} representation of a <code>Json</code> array.
|
* The returned list is the actual array representation so any modifications to it
|
* are modifications of the <code>Json</code> array itself. Call
|
* {@link #isArray()} first if you're not sure this instance is indeed a
|
* <code>Json</code> array.
|
* </p>
|
*/
|
public List<Json> asJsonList() { throw new UnsupportedOperationException(); }
|
|
/**
|
* <p>Return <code>true</code> if this is a <code>Json</code> null entity
|
* and <code>false</code> otherwise.
|
* </p>
|
*/
|
public boolean isNull() { return false; }
|
/**
|
* <p>Return <code>true</code> if this is a <code>Json</code> string entity
|
* and <code>false</code> otherwise.
|
* </p>
|
*/
|
public boolean isString() { return false; }
|
/**
|
* <p>Return <code>true</code> if this is a <code>Json</code> number entity
|
* and <code>false</code> otherwise.
|
* </p>
|
*/
|
public boolean isNumber() { return false; }
|
/**
|
* <p>Return <code>true</code> if this is a <code>Json</code> boolean entity
|
* and <code>false</code> otherwise.
|
* </p>
|
*/
|
public boolean isBoolean() { return false; }
|
/**
|
* <p>Return <code>true</code> if this is a <code>Json</code> array (i.e. list) entity
|
* and <code>false</code> otherwise.
|
* </p>
|
*/
|
public boolean isArray() { return false; }
|
/**
|
* <p>Return <code>true</code> if this is a <code>Json</code> object entity
|
* and <code>false</code> otherwise.
|
* </p>
|
*/
|
public boolean isObject(){ return false; }
|
/**
|
* <p>Return <code>true</code> if this is a <code>Json</code> primitive entity
|
* (one of string, number or boolean) and <code>false</code> otherwise.
|
* </p>
|
*/
|
public boolean isPrimitive() { return isString() || isNumber() || isBoolean(); }
|
|
/**
|
* <p>
|
* Json-pad this object as an argument to a callback function.
|
* </p>
|
*
|
* @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 <code>callback</code>
|
* 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<Object> asList() { return (List<Object>)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<Object> asList() { return (List<Object>)(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 int asHexInt() {return (int) Long.parseLong(val, 16);}
|
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<Object> asList() { return (List<Object>)(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<Object> asList() { return (List<Object>)(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<Json> L = new ArrayList<Json>();
|
|
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<Json> asJsonList() { return L; }
|
public List<Object> asList()
|
{
|
ArrayList<Object> A = new ArrayList<Object>();
|
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<Json> 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<Json> 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<Json> 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<Json> 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<String, Json> object = new LinkedHashMap<String, Json>();
|
|
ObjectJson() { }
|
ObjectJson(Json e) { super(e); }
|
|
public Json dup()
|
{
|
ObjectJson j = new ObjectJson();
|
for (Map.Entry<String, Json> 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<String, Json> 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<String, Json> 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<String, Json> 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<String, Object> asMap()
|
{
|
Map<String, Object> m = new LinkedHashMap<String, Object>();
|
for (Map.Entry<String, Json> e : object.entrySet())
|
m.put(e.getKey(), e.getValue().getValue());
|
return m;
|
}
|
@Override
|
public Map<String, Json> asJsonMap() { return object; }
|
|
public String toString()
|
{
|
StringBuilder sb = new StringBuilder("{");
|
for (Iterator<Map.Entry<String, Json>> i = object.entrySet().iterator(); i.hasNext(); )
|
{
|
Map.Entry<String, Json> 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.
|
*
|
* <p>This class contains a single method to escape a passed in string value:
|
* <pre>
|
* String jsonStringValue = "beforeQuote\"afterQuote";
|
* String escapedValue = Escaper.escapeJsonString(jsonStringValue);
|
* </pre></p>
|
*
|
* @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<Character> JS_ESCAPE_CHARS;
|
private static final Set<Character> HTML_ESCAPE_CHARS;
|
|
static {
|
Set<Character> mandatoryEscapeSet = new HashSet<Character>();
|
mandatoryEscapeSet.add('"');
|
mandatoryEscapeSet.add('\\');
|
JS_ESCAPE_CHARS = Collections.unmodifiableSet(mandatoryEscapeSet);
|
|
Set<Character> htmlEscapeSet = new HashSet<Character>();
|
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]);
|
}
|
}
|
|
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<Character, Character> escapes = new LinkedHashMap<Character, Character>();
|
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<String, Properties> _propMap = null;
|
private Map<String, String> _defineMap = null;
|
private List<WriteBackJson> _writeBackList = null;
|
private List<String> _parsePath = new ArrayList<String>();
|
|
public void importDefineMap(String[] defineMap)
|
{
|
if(_defineMap == null)
|
_defineMap = new LinkedHashMap<String, String>();
|
|
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> 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<WriteBackJson>();
|
_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<String> list = new ArrayList<String>();
|
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));
|
}
|
// get value from property object and insert as json
|
// '#include_prop, [[property_nick],[property key],[json,array,map]?'
|
else if("#include_prop".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 = _propMap.get(nick).getProperty(key);
|
if(value == null)
|
throw new RuntimeException(String.format("read json include property key [%s] error", key));
|
|
String fileName = convFileName(value);
|
try
|
{
|
// 对返回的字符串做转换
|
String mode = "J";
|
if(list.size() > 3)
|
mode = list.get(3).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);
|
}
|
}
|
// 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 LinkedHashMap<String, Properties>();
|
_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<WriteBackJson>();
|
|
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 LinkedHashMap<String, String>();
|
|
_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<String> fullPath = null;
|
if(".".equals(sp[0]) || "..".equals(sp[0]))
|
{
|
// 如果是当前目录需要回退到倒数第二个,因为最后一个路径是map或索引本身
|
// 所以回退位置和长度正巧一致
|
int endPos = sp[0].length();
|
fullPath = new ArrayList<String>();
|
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<String> 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);
|
}
|
}
|