/******************************************************************************* * Copyright (c) 2012-2016 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.everrest.core.impl.provider.json; import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects.ToStringHelper; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import static com.google.common.collect.Sets.newHashSet; public final class JsonUtils { /** Known types. */ public enum Types { BYTE, SHORT, INT, LONG, FLOAT, DOUBLE, BOOLEAN, CHAR, STRING, NULL, ARRAY_BYTE, ARRAY_SHORT, ARRAY_INT, ARRAY_LONG, ARRAY_FLOAT, ARRAY_DOUBLE, ARRAY_BOOLEAN, ARRAY_CHAR, ARRAY_STRING, ARRAY_OBJECT, COLLECTION, MAP, ENUM, CLASS } /** Types of Json tokens. */ public enum JsonToken { object, array, key, value } /** Map of known types. */ private static final Map<String, Types> KNOWN_TYPES = new HashMap<>(); static { KNOWN_TYPES.put(Boolean.class.getName(), Types.BOOLEAN); KNOWN_TYPES.put(Byte.class.getName(), Types.BYTE); KNOWN_TYPES.put(Short.class.getName(), Types.SHORT); KNOWN_TYPES.put(Integer.class.getName(), Types.INT); KNOWN_TYPES.put(Long.class.getName(), Types.LONG); KNOWN_TYPES.put(Float.class.getName(), Types.FLOAT); KNOWN_TYPES.put(Double.class.getName(), Types.DOUBLE); KNOWN_TYPES.put(Character.class.getName(), Types.CHAR); KNOWN_TYPES.put(String.class.getName(), Types.STRING); KNOWN_TYPES.put(Class.class.getName(), Types.CLASS); KNOWN_TYPES.put("boolean", Types.BOOLEAN); KNOWN_TYPES.put("byte", Types.BYTE); KNOWN_TYPES.put("short", Types.SHORT); KNOWN_TYPES.put("int", Types.INT); KNOWN_TYPES.put("long", Types.LONG); KNOWN_TYPES.put("float", Types.FLOAT); KNOWN_TYPES.put("double", Types.DOUBLE); KNOWN_TYPES.put("char", Types.CHAR); KNOWN_TYPES.put("null", Types.NULL); KNOWN_TYPES.put(boolean[].class.getName(), Types.ARRAY_BOOLEAN); KNOWN_TYPES.put(byte[].class.getName(), Types.ARRAY_BYTE); KNOWN_TYPES.put(short[].class.getName(), Types.ARRAY_SHORT); KNOWN_TYPES.put(int[].class.getName(), Types.ARRAY_INT); KNOWN_TYPES.put(long[].class.getName(), Types.ARRAY_LONG); KNOWN_TYPES.put(double[].class.getName(), Types.ARRAY_DOUBLE); KNOWN_TYPES.put(float[].class.getName(), Types.ARRAY_FLOAT); KNOWN_TYPES.put(char[].class.getName(), Types.ARRAY_CHAR); KNOWN_TYPES.put(String[].class.getName(), Types.ARRAY_STRING); } /** * Transform Java String to JSON string. * * @param string * source String. * @return result. */ public static String getJsonString(String string) { if (string == null || string.length() == 0) { return "\"\""; } StringBuilder jsonString = new StringBuilder(); jsonString.append("\""); char[] charArray = string.toCharArray(); for (char c : charArray) { switch (c) { case '\n': jsonString.append("\\n"); break; case '\r': jsonString.append("\\r"); break; case '\t': jsonString.append("\\t"); break; case '\b': jsonString.append("\\b"); break; case '\f': jsonString.append("\\f"); break; case '\\': jsonString.append("\\\\"); break; case '"': jsonString.append("\\\""); break; default: if (c < '\u0010') { jsonString.append("\\u000").append(Integer.toHexString(c)); } else if ((c < '\u0020' && c > '\u0009') || (c >= '\u0080' && c < '\u00a0')) { jsonString.append("\\u00").append(Integer.toHexString(c)); } else if (c >= '\u2000' && c < '\u2100') { jsonString.append("\\u").append(Integer.toHexString(c)); } else { jsonString.append(c); } break; } } jsonString.append("\""); return jsonString.toString(); } /** * Check is given Class is known. * * @param clazz * Class. * @return true if Class is known, false otherwise. */ public static boolean isKnownType(Class<?> clazz) { return KNOWN_TYPES.get(clazz.getName()) != null; } /** * Get 'type' of Object. @see {@link #KNOWN_TYPES} . * * @param o * Object. * @return 'type'. */ public static Types getType(Object o) { if (o == null) { return Types.NULL; } if (KNOWN_TYPES.get(o.getClass().getName()) != null) { return KNOWN_TYPES.get(o.getClass().getName()); } if (o instanceof Enum) { return Types.ENUM; } if (o instanceof Object[]) { return Types.ARRAY_OBJECT; } if (o instanceof Collection) { return Types.COLLECTION; } if (o instanceof Map) { return Types.MAP; } return null; } /** * Get 'type' of Class. @see {@link #KNOWN_TYPES} . * * @param clazz * Class. * @return 'type'. */ public static Types getType(Class<?> clazz) { if (KNOWN_TYPES.get(clazz.getName()) != null) { return KNOWN_TYPES.get(clazz.getName()); } if (Enum.class.isAssignableFrom(clazz)) { return Types.ENUM; } if (clazz.isArray()) { return Types.ARRAY_OBJECT; } if (Collection.class.isAssignableFrom(clazz)) { return Types.COLLECTION; } if (Map.class.isAssignableFrom(clazz)) { return Types.MAP; } return null; } /** * Check fields in class which marked as 'transient' or annotated with * {@link JsonTransient} annotation . Transient fields will be not serialized * in JSON representation. * * @param aClass * the class. * @return set of fields which must be skipped. */ public static Set<String> getTransientFields(Class<?> aClass) { Set<String> transientFields = new HashSet<>(); Class<?> superClass = aClass; while (superClass != null && superClass != Object.class) { for (Field field : superClass.getDeclaredFields()) { if (Modifier.isTransient(field.getModifiers()) || field.isAnnotationPresent(JsonTransient.class)) { transientFields.add(field.getName()); } } superClass = superClass.getSuperclass(); } return transientFields; } @SuppressWarnings({"unchecked"}) public static <T> T createProxy(Class<T> anInterface) { if (anInterface == null) { throw new IllegalArgumentException(); } if (anInterface.isInterface()) { return (T)Proxy.newProxyInstance(anInterface.getClassLoader(), new Class[]{anInterface}, new ProxyObject(anInterface)); } throw new IllegalArgumentException(String.format("Type '%s' is not interface. ", anInterface.getSimpleName())); } static String getFieldName(Method method) { String methodName = method.getName(); Class<?> returnType = method.getReturnType(); String fieldName = null; if (methodName.startsWith("set") && methodName.length() > 3) { fieldName = methodName.substring(3); } else if (methodName.startsWith("get") && methodName.length() > 3) { fieldName = methodName.substring(3); } else if (methodName.startsWith("is") && methodName.length() > 2 && (returnType == Boolean.class || returnType == boolean.class)) { fieldName = methodName.substring(2); } if (fieldName != null) { fieldName = (fieldName.length() > 1) ? Character.toLowerCase(fieldName.charAt(0)) + fieldName.substring(1) : fieldName.toLowerCase(); } return fieldName; } private static final class ProxyObject implements InvocationHandler { private static final Set<String> IGNORED_METHODS = newHashSet("getClass", "getMetaClass", "setMetaClass"); private final Map<String, Object> values; private final Class<?> anInterface; private ProxyObject(Class<?> anInterface) { this.anInterface = anInterface; values = new HashMap<>(); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String key = getKey(method); if (key != null) { if (args == null) { return getValue(key, method); } values.put(key, args[0]); } else if ("toString".equals(method.getName()) && args == null) { return invokeToString(); } return null; } private Object invokeToString() { Method[] allMethods = anInterface.getMethods(); ToStringHelper toStringHelper = MoreObjects.toStringHelper(anInterface); for (Method method : allMethods) { String key; if ((key = getKey(method)) != null && method.getParameterTypes().length == 0) { toStringHelper.add(key, getValue(key, method)); } } return toStringHelper.toString(); } private Object getValue(String key, Method method) { Object value = values.get(key); if (value == null && method.getReturnType().isPrimitive()) { value = getDefaultValue(method.getReturnType()); } return value; } private Object getDefaultValue(Class<?> valueType) { if (Boolean.TYPE == valueType) { return false; } else if (Byte.TYPE == valueType) { return (byte)0; } else if (Short.TYPE == valueType) { return (short)0; } else if (Character.TYPE == valueType) { return (char)0; } else if (Integer.TYPE == valueType) { return 0; } else if (Long.TYPE == valueType) { return 0L; } else if (Float.TYPE == valueType) { return 0.0F; } else if (Double.TYPE == valueType) { return 0.0; } return null; } private String getKey(Method method) { if (isIgnoredMethod(method)) { return null; } return getFieldName(method); } private boolean isIgnoredMethod(Method method) { return IGNORED_METHODS.contains(method.getName()) || method.getParameterTypes().length > 1; } } private JsonUtils() { } }