| /* |
| * ========================================================================= |
| * Copyright (c) 2002-2014 Pivotal Software, Inc. All Rights Reserved. |
| * This product is protected by U.S. and international copyright |
| * and intellectual property laws. Pivotal products are covered by |
| * more patents listed at http://www.pivotal.io/patents. |
| * ======================================================================== |
| */ |
| package com.gemstone.gemfire.management.internal.cli.json; |
| |
| import java.io.IOException; |
| import java.io.StringWriter; |
| import java.io.Writer; |
| import java.lang.reflect.Array; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| |
| import com.gemstone.gemfire.cache.Region; |
| import com.gemstone.gemfire.cache.query.Struct; |
| import com.gemstone.gemfire.cache.query.internal.StructImpl; |
| import com.gemstone.gemfire.pdx.PdxInstance; |
| |
| /** |
| * A limited functionality JSON parser. Its a DSF based JSON parser. It does not |
| * create Object maps and serialize them like JSONObject. It just traverses an Object graph in |
| * depth first search manner and appends key values to a String writer. |
| * Hence we prevent creating a lot of garbage. |
| * |
| * Although it has limited functionality,still a simple use of add() method |
| * should suffice for most of the simple JSON use cases. |
| * |
| * @author rishim |
| * |
| */ |
| public class TypedJson { |
| |
| /** |
| * Limit of collection length to be serialized in JSON format. |
| */ |
| public static int DEFAULT_COLLECTION_ELEMENT_LIMIT = 100; |
| |
| public static final Object NULL = GfJsonObject.NULL; |
| |
| /** |
| * If Integer of Float is NAN |
| */ |
| static final String NONFINITE = "Non-Finite"; |
| |
| Map<Object,List<Object>> forbidden = new java.util.IdentityHashMap<Object,List<Object>>(); |
| |
| boolean commanate; |
| |
| private Map<String, List<Object>> map; |
| |
| private int queryCollectionsDepth; |
| |
| public TypedJson(String key, Object value, int queryCollectionsDepth) { |
| List<Object> list = new ArrayList<Object>(); |
| this.map = new LinkedHashMap<String, List<Object>>(); |
| if (value != null) { |
| list.add(value); |
| } |
| this.map.put(key, list); |
| this.queryCollectionsDepth = queryCollectionsDepth; |
| } |
| |
| public TypedJson(int queryCollectionsDepth) { |
| this.map = new LinkedHashMap<String, List<Object>>(); |
| this.queryCollectionsDepth = queryCollectionsDepth; |
| } |
| |
| public TypedJson() { |
| this.map = new LinkedHashMap<String, List<Object>>(); |
| this.queryCollectionsDepth = DEFAULT_COLLECTION_ELEMENT_LIMIT; |
| } |
| |
| public TypedJson(String key, Object value) { |
| List<Object> list = new ArrayList<Object>(); |
| this.map = new LinkedHashMap<String, List<Object>>(); |
| if (value != null) { |
| list.add(value); |
| } |
| this.map.put(key, list); |
| this.queryCollectionsDepth = DEFAULT_COLLECTION_ELEMENT_LIMIT; |
| } |
| |
| void bfs(Writer w, Object root) throws IOException { |
| if(root == null || isPrimitiveOrWrapper(root.getClass())){ |
| return; |
| } |
| LinkedList<Object> queue = new LinkedList<Object>(); |
| Map seen = new java.util.IdentityHashMap(); |
| |
| seen.put(root, null); |
| |
| // Adds to end of queue |
| queue.addFirst(root); |
| |
| while (!queue.isEmpty()) { |
| |
| // removes from front of queue |
| Object r = queue.pollFirst(); |
| List<Object> childrens = getChildrens(w, r); |
| // Visit child first before grand child |
| for (Object n : childrens) { |
| |
| if(n == null){ |
| continue; |
| } |
| if (!isPrimitiveOrWrapper(n.getClass())) { |
| if (!seen.containsKey(n)) { |
| queue.addFirst(n); |
| seen.put(n, null); |
| } else { |
| List<Object> list = forbidden.get(r); |
| if(list != null){ |
| list.add(n); |
| forbidden.put(r, list); |
| }else{ |
| List<Object> newList = new ArrayList<Object>(); |
| newList.add(n); |
| forbidden.put(r, newList); |
| } |
| |
| } |
| } |
| |
| } |
| } |
| } |
| |
| List<Object> getChildrens(Writer w, Object object) throws IOException { |
| if (isSpecialObject(object)) { |
| return this.visitSpecialObjects(w, object, false); |
| } else { |
| return this.visitChildrens(w, object, false); |
| } |
| } |
| |
| |
| /** |
| * |
| * User can build on this object by adding Objects against a key. |
| * |
| * TypedJson result = new TypedJson(); result.add(KEY,object); If users add |
| * more objects against the same key the newly added object will be appended |
| * to the existing key forming an array of objects. |
| * |
| * If the KEY is a new one then it will be a key map value. |
| * |
| * @param key |
| * Key against which an object will be added |
| * @param value |
| * Object to be added |
| * @return TypedJson object |
| */ |
| public TypedJson add(String key, Object value) { |
| List<Object> list = this.map.get(key); |
| if (list != null) { |
| list.add(value); |
| } else { |
| list = new ArrayList<Object>(); |
| list.add(value); |
| this.map.put(key, list); |
| } |
| return this; |
| } |
| |
| public String toString() { |
| StringWriter w = new StringWriter(); |
| synchronized (w.getBuffer()) { |
| try { |
| return this.write(w).toString(); |
| } catch (Exception e) { |
| return null; |
| } |
| } |
| } |
| |
| public int length() { |
| return this.map.size(); |
| } |
| |
| Writer write(Writer writer) throws GfJsonException { |
| try { |
| boolean addComma = false; |
| final int length = this.length(); |
| Iterator<String> keys = map.keySet().iterator(); |
| writer.write('{'); |
| |
| if (length == 1) { |
| Object key = keys.next(); |
| writer.write(quote(key.toString())); |
| writer.write(':'); |
| |
| writeList(writer, this.map.get(key)); |
| } else if (length != 0) { |
| while (keys.hasNext()) { |
| Object key = keys.next(); |
| if (addComma) { |
| writer.write(','); |
| } |
| writer.write(quote(key.toString())); |
| writer.write(':'); |
| |
| writeList(writer, this.map.get(key)); |
| commanate = false; |
| addComma = true; |
| } |
| |
| } |
| |
| writer.write('}'); |
| |
| return writer; |
| } catch (IOException exception) { |
| throw new GfJsonException(exception); |
| } |
| } |
| |
| Writer writeList(Writer writer, List<Object> myArrayList) throws GfJsonException { |
| try { |
| boolean addComma = false; |
| int length = myArrayList.size(); |
| |
| if (length == 0) { |
| writer.write(']'); |
| writeValue(writer, null); |
| writer.write(']'); |
| } |
| if (length == 1) { |
| writer.write('['); |
| writeValue(writer, myArrayList.get(0)); |
| writer.write(']'); |
| |
| } else if (length != 0) { |
| writer.write('['); |
| for (int i = 0; i < length; i += 1) { |
| if (addComma) { |
| writer.write(','); |
| } |
| writeValue(writer, myArrayList.get(i)); |
| commanate = false; |
| addComma = true; |
| } |
| writer.write(']'); |
| } |
| |
| return writer; |
| } catch (IOException e) { |
| throw new GfJsonException(e); |
| } |
| } |
| |
| static String quote(String string) { |
| StringWriter sw = new StringWriter(); |
| synchronized (sw.getBuffer()) { |
| try { |
| return quote(string, sw).toString(); |
| } catch (IOException ignored) { |
| // will never happen - we are writing to a string writer |
| return ""; |
| } |
| } |
| } |
| |
| static boolean shouldVisitChildren(Object object) { |
| Class type = object.getClass(); |
| if (isPrimitiveOrWrapper(type)) { |
| return false; |
| } |
| if (isSpecialObject(object)) { |
| return false; |
| } |
| return true; |
| } |
| |
| static boolean isPrimitiveOrWrapper(Class<?> klass) { |
| return klass.isAssignableFrom(Byte.class) || klass.isAssignableFrom(byte.class) |
| || klass.isAssignableFrom(Short.class) || klass.isAssignableFrom(short.class) |
| || klass.isAssignableFrom(Integer.class) || klass.isAssignableFrom(int.class) |
| || klass.isAssignableFrom(Long.class) || klass.isAssignableFrom(long.class) |
| || klass.isAssignableFrom(Float.class) || klass.isAssignableFrom(float.class) |
| || klass.isAssignableFrom(Double.class) || klass.isAssignableFrom(double.class) |
| || klass.isAssignableFrom(Boolean.class) || klass.isAssignableFrom(boolean.class) |
| || klass.isAssignableFrom(String.class) || klass.isAssignableFrom(char.class) |
| || klass.isAssignableFrom(Character.class) || klass.isAssignableFrom(java.sql.Date.class) |
| || klass.isAssignableFrom(java.util.Date.class) || klass.isAssignableFrom(java.math.BigDecimal.class); |
| } |
| |
| static boolean isSpecialObject(Object object) { |
| Class type = object.getClass(); |
| if (type.isArray() || type.isEnum()) { |
| return true; |
| } |
| if ((object instanceof Collection) || (object instanceof Map) || (object instanceof PdxInstance) |
| || (object instanceof Struct) || (object instanceof Region.Entry) ){ |
| return true; |
| } |
| return false; |
| } |
| |
| final void writeVal(Writer w, Object value) throws IOException { |
| w.write('{'); |
| addVal(w, value); |
| w.write('}'); |
| } |
| |
| void addVal(Writer w, Object object) { |
| if (object == null) { |
| return; |
| } |
| if (shouldVisitChildren(object)) { |
| visitChildrens(w, object, true); |
| } |
| |
| } |
| |
| void writeKeyValue(Writer w, Object key, Object value, Class type) throws IOException { |
| if (commanate) { |
| w.write(","); |
| } |
| |
| if (value == null || value.equals(null)) { |
| w.write(quote(key.toString())); |
| w.write(':'); |
| w.write("null"); |
| commanate = true; |
| return; |
| } |
| Class clazz = value.getClass(); |
| w.write(quote(key.toString())); |
| w.write(':'); |
| |
| if (type != null) { |
| writeType(w, type, value); |
| } |
| |
| if (isPrimitiveOrWrapper(clazz)) { |
| writePrimitives(w, value); |
| commanate = true; |
| } else if (isSpecialObject(value)) { |
| commanate = false; |
| visitSpecialObjects(w, value, true); |
| commanate = true; |
| } else { |
| commanate = false; |
| writeVal(w, value); |
| commanate = true; |
| } |
| endType(w, clazz); |
| return; |
| } |
| |
| void writePrimitives(Writer w, Object value) throws IOException { |
| if (value instanceof Number) { |
| w.write(numberToString((Number) value)); |
| return; |
| } |
| |
| if (value instanceof String || value instanceof Character || value instanceof java.sql.Date |
| || value instanceof java.util.Date) { |
| w.write(quote(value.toString())); |
| return; |
| } |
| w.write(value.toString()); |
| } |
| |
| void writeArray(Writer w, Object object) throws IOException { |
| |
| if (commanate) { |
| w.write(","); |
| } |
| w.write('['); |
| int length = Array.getLength(object); |
| int elements = 0; |
| for (int i = 0; i < length && elements < queryCollectionsDepth; i += 1) { |
| Object item = Array.get(object, i); |
| if (i != 0) { |
| w.write(","); |
| } |
| if(item != null){ |
| Class clazz = item.getClass(); |
| |
| if (isPrimitiveOrWrapper(clazz)) { |
| writePrimitives(w, item); |
| } else if (isSpecialObject(item)) { |
| visitSpecialObjects(w, item, true); |
| } else { |
| writeVal(w, item); |
| } |
| }else{ |
| w.write("null"); |
| } |
| elements++; |
| commanate = false; |
| } |
| w.write(']'); |
| commanate = true; |
| return; |
| } |
| |
| List<Object> getArrayChildren(Object object){ |
| List<Object> items = new ArrayList<Object>(); |
| int length = Array.getLength(object); |
| int elements = 0; |
| for (int i = 0; i < length && elements < queryCollectionsDepth; i += 1) { |
| Object item = Array.get(object, i); |
| items.add(item); |
| |
| } |
| return items; |
| } |
| |
| void writeEnum(Writer w, Object object) throws IOException { |
| if (commanate) { |
| w.write(","); |
| } |
| w.write(quote(object.toString())); |
| commanate = true; |
| return; |
| } |
| |
| void writeTypedJson(Writer w, TypedJson object) throws IOException { |
| |
| if (commanate) { |
| w.write(","); |
| } |
| w.write(quote(object.toString())); |
| commanate = true; |
| return; |
| } |
| |
| void writeValue(Writer w, Object value) { |
| try { |
| if (value == null || value.equals(null)) { |
| w.write("null"); |
| return; |
| } |
| this.bfs(w, value); |
| Class rootClazz = value.getClass(); |
| writeType(w, rootClazz, value); |
| |
| if (isPrimitiveOrWrapper(rootClazz)) { |
| writePrimitives(w, value); |
| } else if (isSpecialObject(value)) { |
| visitSpecialObjects(w, value, true); |
| } else { |
| writeVal(w, value); |
| } |
| endType(w, rootClazz); |
| } catch (IOException e) { |
| } |
| |
| } |
| |
| void startKey(Writer writer, String key) throws IOException { |
| if (key != null) { |
| writer.write('{'); |
| writer.write(quote(key.toString())); |
| writer.write(':'); |
| } |
| } |
| |
| void endKey(Writer writer, String key) throws IOException { |
| if (key != null) { |
| writer.write('}'); |
| } |
| |
| } |
| |
| List<Object> visitSpecialObjects(Writer w, Object object, boolean write) throws IOException { |
| |
| List<Object> elements = new ArrayList<Object>(); |
| |
| Class clazz = object.getClass(); |
| |
| if (clazz.isArray()) { |
| if (write) { |
| writeArray(w, object); |
| } else { |
| return getArrayChildren(object); |
| } |
| } |
| |
| if (clazz.isEnum()) { |
| if (write) { |
| writeEnum(w, object); |
| }else{ |
| elements.add(object); |
| } |
| return elements; |
| } |
| |
| if (object instanceof TypedJson) { |
| this.writeTypedJson(w, (TypedJson) object); |
| return elements; |
| } |
| |
| if (object instanceof Collection) { |
| Collection collection = (Collection) object; |
| Iterator iter = collection.iterator(); |
| int i = 0; |
| if(write)w.write('{'); |
| while (iter.hasNext() && i < queryCollectionsDepth) { |
| Object item = iter.next(); |
| if(write){ |
| writeKeyValue(w, i, item, item !=null ? item.getClass() : null); |
| }else{ |
| elements.add(item); |
| } |
| |
| i++; |
| } |
| if(write)w.write('}'); |
| return elements; |
| } |
| |
| if (object instanceof Map) { |
| Map map = (Map) object; |
| Iterator it = map.entrySet().iterator(); |
| int i = 0; |
| if(write)w.write('{'); |
| while (it.hasNext() && i < queryCollectionsDepth) { |
| Map.Entry e = (Map.Entry) it.next(); |
| Object value = e.getValue(); |
| if(write){ |
| writeKeyValue(w, e.getKey(), value, value !=null ? value.getClass(): null); |
| }else{ |
| elements.add(value); |
| } |
| |
| i++; |
| } |
| if(write)w.write('}'); |
| return elements; |
| } |
| |
| if (object instanceof PdxInstance) { |
| PdxInstance pdxInstance = (PdxInstance) object; |
| if(write)w.write('{'); |
| for (String field : pdxInstance.getFieldNames()) { |
| Object fieldValue = pdxInstance.getField(field); |
| if(write){ |
| writeKeyValue(w, field, fieldValue, fieldValue !=null ? fieldValue.getClass() : null); |
| }else{ |
| elements.add(fieldValue); |
| } |
| |
| |
| } |
| if(write)w.write('}'); |
| return elements; |
| } |
| |
| if (object instanceof Struct) { |
| StructImpl impl = (StructImpl) object; |
| String fields[] = impl.getFieldNames(); |
| Object[] values = impl.getFieldValues(); |
| |
| if(write)w.write('{'); |
| for (int i = 0; i < fields.length; i++) { |
| Object fieldValue = values[i]; |
| if(write){ |
| writeKeyValue(w, fields[i], fieldValue, fieldValue !=null ? fieldValue.getClass() : null); |
| }else{ |
| elements.add(fieldValue); |
| } |
| |
| } |
| if(write)w.write('}'); |
| return elements; |
| } |
| |
| |
| if (object instanceof Region.Entry) { |
| Region.Entry entry = (Region.Entry) object; |
| Object key = entry.getKey(); |
| Object value = entry.getValue(); |
| |
| |
| if(write){ |
| w.write('{'); |
| writeKeyValue(w, key, value, value !=null ? value.getClass() : null); |
| w.write('}'); |
| }else{ |
| elements.add(value); |
| } |
| |
| |
| return elements; |
| } |
| |
| |
| return elements; |
| } |
| |
| void writeType(Writer w, Class clazz, Object value) throws IOException { |
| if (clazz != TypedJson.class) { |
| w.write('['); |
| w.write(quote(internalToExternal(clazz, value))); |
| w.write(","); |
| } |
| } |
| /** |
| * Handle some special GemFire classes. We don't want to expose some of the internal classes. |
| * Hence corresponding interface or external classes should be shown. |
| * |
| */ |
| String internalToExternal(Class clazz, Object value){ |
| if(value != null && value instanceof Region.Entry){ |
| return Region.Entry.class.getCanonicalName(); |
| } |
| if(value != null && value instanceof PdxInstance){ |
| return PdxInstance.class.getCanonicalName(); |
| } |
| return clazz.getCanonicalName(); |
| } |
| |
| void endType(Writer w, Class clazz) throws IOException { |
| if (clazz != TypedJson.class) { |
| w.write(']'); |
| } |
| |
| } |
| |
| List<Object> visitChildrens(Writer w, Object object, boolean write) { |
| |
| List<Object> elements = new ArrayList<Object>(); |
| |
| Method[] methods = getMethods(object); |
| |
| for (int i = 0; i < methods.length; i += 1) { |
| try { |
| Method method = methods[i]; |
| if (Modifier.isPublic(method.getModifiers()) && !Modifier.isStatic(method.getModifiers())) { |
| String name = method.getName(); |
| String key = ""; |
| if (name.startsWith("get")) { |
| if ("getClass".equals(name) || "getDeclaringClass".equals(name)) { |
| key = ""; |
| } else { |
| key = name.substring(3); |
| } |
| } else if (name.startsWith("is")) { |
| key = name.substring(2); |
| } |
| if (key.length() > 0 && Character.isUpperCase(key.charAt(0)) && method.getParameterTypes().length == 0) { |
| if (key.length() == 1) { |
| key = key.toLowerCase(); |
| } else if (!Character.isUpperCase(key.charAt(1))) { |
| key = key.substring(0, 1).toLowerCase() + key.substring(1); |
| } |
| method.setAccessible(true); |
| Object result = method.invoke(object, (Object[]) null); |
| if(write){ |
| List<Object> forbiddenList = forbidden.get(object); |
| if(forbiddenList != null && forbiddenList.contains(result)){ |
| writeKeyValue(w, key, result.getClass().getCanonicalName(), method.getReturnType()); |
| }else{ |
| writeKeyValue(w, key, result, method.getReturnType()); |
| } |
| }else{ |
| elements.add(result); |
| } |
| |
| |
| } |
| } |
| } catch (Exception ignore) { |
| } |
| } |
| return elements; |
| } |
| |
| |
| /** |
| * This method returns method declared in a Class as well as all the super classes in the hierarchy. |
| * If class is a system class it wont include super class methods |
| */ |
| Method[] getMethods(Object object){ |
| Class klass = object.getClass(); |
| |
| // If klass is a System class then set includeSuperClass to false. |
| |
| boolean includeSuperClass = klass.getClassLoader() != null; |
| |
| Method[] decMethods = klass.getDeclaredMethods(); |
| Map<String, Method> decMethodMap = new HashMap<String,Method>(); |
| for(Method method : decMethods){ |
| decMethodMap.put(method.getName(), method); |
| } |
| |
| if(includeSuperClass){ |
| Method[] allMethods = klass.getMethods(); |
| List<Method> allMethodList = Arrays.asList(allMethods); |
| for(Method method : allMethodList){ |
| if(decMethodMap.get(method.getName()) != null){ |
| //skip. This will ensure overriden methods wont be added again. |
| }else{ |
| decMethodMap.put(method.getName(), method); |
| } |
| } |
| } |
| |
| Method[] methodArr = new Method[decMethodMap.size()]; |
| return decMethodMap.values().toArray(methodArr); |
| } |
| |
| |
| /** |
| * Produce a string from a Number. |
| * |
| * @param number |
| * A Number |
| * @return A String. |
| */ |
| public static String numberToString(Number number) { |
| if (number == null) { |
| return ""; |
| } |
| if (number != null) { |
| if (number instanceof Double) { |
| if (((Double) number).isInfinite() || ((Double) number).isNaN()) { |
| return "Non-Finite"; |
| } |
| } else if (number instanceof Float) { |
| if (((Float) number).isInfinite() || ((Float) number).isNaN()) { |
| return "Non-Finite"; |
| } |
| } |
| } |
| |
| // Shave off trailing zeros and decimal point, if possible. |
| |
| String string = number.toString(); |
| if (string.indexOf('.') > 0 && string.indexOf('e') < 0 && string.indexOf('E') < 0) { |
| while (string.endsWith("0")) { |
| string = string.substring(0, string.length() - 1); |
| } |
| if (string.endsWith(".")) { |
| string = string.substring(0, string.length() - 1); |
| } |
| } |
| return string; |
| } |
| |
| public static Writer quote(String string, Writer w) throws IOException { |
| if (string == null || string.length() == 0) { |
| w.write("\"\""); |
| return w; |
| } |
| |
| char b; |
| char c = 0; |
| String hhhh; |
| int i; |
| int len = string.length(); |
| |
| w.write('"'); |
| for (i = 0; i < len; i += 1) { |
| b = c; |
| c = string.charAt(i); |
| switch (c) { |
| case '\\': |
| case '"': |
| w.write('\\'); |
| w.write(c); |
| break; |
| case '/': |
| if (b == '<') { |
| w.write('\\'); |
| } |
| w.write(c); |
| break; |
| case '\b': |
| w.write("\\b"); |
| break; |
| case '\t': |
| w.write("\\t"); |
| break; |
| case '\n': |
| w.write("\\n"); |
| break; |
| case '\f': |
| w.write("\\f"); |
| break; |
| case '\r': |
| w.write("\\r"); |
| break; |
| default: |
| if (c < ' ' || (c >= '\u0080' && c < '\u00a0') || (c >= '\u2000' && c < '\u2100')) { |
| hhhh = "000" + Integer.toHexString(c); |
| w.write("\\u" + hhhh.substring(hhhh.length() - 4)); |
| } else { |
| w.write(c); |
| } |
| } |
| } |
| w.write('"'); |
| return w; |
| } |
| } |