JSONSerialiser.java

438 lines | 17.986 kB Blame History Raw Download
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package br.ufrgs.inf.prosoft.jsonserialiser;

import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
 *
 * @author romulo
 */
public class JSONSerialiser {

    private final List<Object> visited;
    private static final boolean TRACER_SERIALISE_INTERNALS = System.getenv("TRACER_SERIALISE_INTERNALS") != null && System.getenv("TRACER_SERIALISE_INTERNALS").equals("true");
    private static final String TRACER_IGNORED_PACKAGES = System.getenv("TRACER_IGNORED_PACKAGES") != null ? System.getenv("TRACER_IGNORED_PACKAGES") : "./ignored";
    private static final String TRACER_WHITELIST = System.getenv("TRACER_WHITELIST") != null ? System.getenv("TRACER_WHITELIST") : "";
    private static final String TRACER_LOG = System.getenv("TRACER_LOG");

    private JSONSerialiser() {
        this.visited = new ArrayList<>();
    }

    public static String serialise(Object bean) {
        JSONSerialiser jsonSerialiser = new JSONSerialiser();
        StringBuilder stringBuilder = jsonSerialiser.wrap(bean);
        Utils.fixJson(stringBuilder);
        return stringBuilder.toString();
    }

    private static class Utils {

        private static void fixJson(StringBuilder stringBuilder) {
            int index = 0;
            while (true) {
                if (index + 1 >= stringBuilder.length()) {
                    break;
                }
                char thisChar = stringBuilder.charAt(index);
                char nextChar = stringBuilder.charAt(index + 1);
                if (thisChar == ',') {
                    if (nextChar == ',') {
                        stringBuilder.deleteCharAt(index);
                        continue;
                    }
                    if (nextChar == ']' || nextChar == '}') {
                        stringBuilder.deleteCharAt(index);
                    }
                } else if (thisChar == '[' || thisChar == '{') {
                    if (nextChar == ',') {
                        stringBuilder.deleteCharAt(index + 1);
                        continue;
                    }
                }
                index++;
            }
        }

        private static List<Field> getAllFields(List<Field> fields, Class<?> type) {
            for (Field field : type.getDeclaredFields()) {
                if (fields.contains(field)) {
                    continue;
                }
                if (!field.getName().startsWith("ajc$tjp_")) {
                    fields.add(field);
                }
            }
            if (type.getSuperclass() != null) {
                getAllFields(fields, type.getSuperclass());
            }
            return fields;
        }
    }

    private StringBuilder wrap(Object object) {
        try {
            if (object == null) {
                return new StringBuilder().append("null");
            }
            if (object instanceof Boolean) {
                return new StringBuilder().append("\"").append(String.valueOf(object)).append("\"");
            }
            if (object instanceof Character) {
                return serialiseString(String.valueOf(object));
            }
            if (object instanceof CharSequence) {
                CharSequence string = (CharSequence) object;
                return serialiseString(string);
            }
            if (object instanceof Number) {
                return new StringBuilder().append("\"").append(String.valueOf(object)).append("\"");
            }
            if (object instanceof Enum) {
                return serialiseString(((Enum<?>) object).name());
            }
            if (object instanceof HashSet) {
                HashSet<?> hashSet = (HashSet<?>) object;
                return serialiseHashSet(hashSet);
            }
            if (object instanceof Collection) {
                Collection<?> collection = (Collection<?>) object;
                return serialiseCollection(collection);
            }
            if (object.getClass().isArray()) {
                return serialiseArray(object);
            }
            if (object instanceof HashMap) {
                HashMap<?, ?> hashMap = (HashMap<?, ?>) object;
                return serialiseHashMap(hashMap);
            }
            if (object instanceof Map) {
                Map<?, ?> map = (Map<?, ?>) object;
                return serialiseMap(map);
            }
            if (object instanceof Date) {
                Date date = (Date) object;
                return new StringBuilder().append("\"").append(String.valueOf(date.getTime())).append("\"");
            }
            if (!TRACER_SERIALISE_INTERNALS) {
                Package objectPackage = object.getClass().getPackage();
                String objectPackageName = objectPackage != null ? objectPackage.getName() : "";
                List<String> ignoredPackages;
                try {
                    ignoredPackages = Files.readAllLines(Paths.get(TRACER_IGNORED_PACKAGES));
                } catch (IOException ex) {
                    ignoredPackages = new ArrayList<>();
                }
                String[] internals = {"java.", "org.ietf.", "org.omg.", "org.w3c.", "org.xml."};
                ignoredPackages.addAll(Arrays.asList(internals));
                if (ignoredPackages.stream().anyMatch(ignoredPackage -> objectPackageName.startsWith(ignoredPackage))) {
                    return getReference(object);
                }
                try {
                    List<String> whiteList = Files.readAllLines(Paths.get(TRACER_WHITELIST));
                    if (!objectPackageName.isEmpty() && whiteList.stream().noneMatch(ignoredPackage -> objectPackageName.startsWith(ignoredPackage))) {
                        log(System.currentTimeMillis() + " [JSONSerialiser] proceeding " + objectPackageName);
                    }
                } catch (IOException ex) {
                }
            }
            if (this.visited.stream().parallel().anyMatch(visited -> visited == object)) {
                return getReference(object);
            }
            int index = this.visited.size();
            this.visited.add(object);
            StringBuilder serialiseBean = serialiseBean(object);
            while (index < this.visited.size()) {
                this.visited.remove(index);
            }
            return serialiseBean;
        } catch (ConcurrentModificationException exception) {
            System.err.println("[JSONSerialiser] ConcurrentModificationException");
            return new StringBuilder().append("\"JSON_CONCURRENT_MODIFICATION\"");
        } catch (Exception exception) {
            System.err.println("[JSONSerialiser] wrap exception: " + exception);
            exception.printStackTrace();
            return new StringBuilder().append("\"JSON_SERIALISE_EXCEPTION\"");
        } catch (StackOverflowError exception) {
            System.err.println("[JSONSerialiser] stack overflow");
            return new StringBuilder().append("\"JSON_STACK_OVERFLOW\"");
        }
    }

    private StringBuilder serialiseBean(Object bean) {
        StringBuilder stringBuilder = new StringBuilder();
        if (bean == null) {
            stringBuilder.append("null");
            return stringBuilder;
        }
        Class<?> klass = bean.getClass();
        List<Field> fields = new ArrayList<>();
        fields = Utils.getAllFields(fields, klass);
        stringBuilder.append("{");
        for (Iterator<Field> it = fields.iterator(); it.hasNext();) {
            Field field = it.next();
            try {
                field.setAccessible(true);
                Object result = field.get(bean);
                if (result == null) {
                    continue;
                }
                stringBuilder.append("\"").append(field.getName()).append("\"");
                stringBuilder.append(":");
                StringBuilder wrap = wrap(result);
                stringBuilder.append(wrap);
            } catch (Exception ignore) {
                System.err.println("[JSONSerialiser] field exception: " + ignore);
                stringBuilder.append("\"").append("JSON-FIELD-EXCEPTION").append("\"");
            }
            if (it.hasNext()) {
                stringBuilder.append(",");
            }
        }
        stringBuilder.append("}");
        return stringBuilder;
    }

    private StringBuilder serialiseMap(Map map) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("{");
        try {
            for (Iterator<Map.Entry<?, ?>> iterator = map.entrySet().iterator(); iterator.hasNext();) {
                Map.Entry<?, ?> entry = iterator.next();
                Object key = entry.getKey();
                Object value = entry.getValue();
                if (key == null || value == null) {
                    continue;
                }
                StringBuilder serialisedKey;
                if (key instanceof String) {
                    serialisedKey = serialiseString((String) key);
                } else if (this.visited.stream().parallel().anyMatch(visited -> visited == key)) {
                    serialisedKey = serialiseString(getReference(key));
                } else {
                    serialisedKey = serialiseString(JSONSerialiser.serialise(key));
                }
                StringBuilder wrappedValue = wrap(value);
                stringBuilder.append(serialisedKey).append(":").append(wrappedValue);
                if (iterator.hasNext()) {
                    stringBuilder.append(",");
                }
            }
        } catch (Exception ex) {
            System.err.println("[JSONSerialiser] map serialise exception: " + ex);
            return new StringBuilder().append("{\"e\":\"JSON_MAP_EXCEPTION\"}");
        }
        return stringBuilder.append("}");
    }

    private StringBuilder serialiseHashMap(HashMap map) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("{");
        try {
            TreeMap<String, StringBuilder> serialisedMap = new TreeMap<>();
            for (Iterator<Map.Entry<?, ?>> iterator = map.entrySet().iterator(); iterator.hasNext();) {
                Map.Entry<?, ?> entry = iterator.next();
                Object key = entry.getKey();
                Object value = entry.getValue();
                if (key == null || value == null) {
                    continue;
                }
                String serialisedKey;
                if (key instanceof String) {
                    serialisedKey = serialiseString((String) key).toString();
                } else if (this.visited.stream().parallel().anyMatch(visited -> visited == key)) {
                    serialisedKey = serialiseString(getReference(key)).toString();
                } else {
                    serialisedKey = serialiseString(JSONSerialiser.serialise(key)).toString();
                }
                StringBuilder wrappedValue = wrap(value);
                serialisedMap.put(serialisedKey, wrappedValue);
            }
            for (Iterator<Map.Entry<String, StringBuilder>> iterator = serialisedMap.entrySet().iterator(); iterator.hasNext();) {
                Map.Entry<String, StringBuilder> entry = iterator.next();
                String key = entry.getKey();
                StringBuilder value = entry.getValue();
                stringBuilder.append(key).append(":").append(value);
                if (iterator.hasNext()) {
                    stringBuilder.append(",");
                }
            }
        } catch (Exception ex) {
            System.err.println("[JSONSerialiser] hashmap serialise exception: " + ex);
            return new StringBuilder().append("{\"e\":\"JSON_HASHMAP_EXCEPTION\"}");
        }
        return stringBuilder.append("}");
    }

    private StringBuilder serialiseArray(Object array) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("[");
        try {
            int length = Array.getLength(array);
            for (int i = 0; i < length;) {
                Object object = Array.get(array, i);
                StringBuilder wrap = wrap(object);
                stringBuilder.append(wrap);
                if (++i < length) {
                    stringBuilder.append(",");
                }
            }
        } catch (Exception ex) {
            System.err.println("[JSONSerialiser] array serialise exception: " + ex);
            return new StringBuilder().append("[\"JSON_ARRAY_EXCEPTION\"]");
        }
        return stringBuilder.append("]");
    }

    private StringBuilder serialiseCollection(Collection collection) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("[");
        try {
            for (Iterator iterator = collection.iterator(); iterator.hasNext();) {
                Object object = iterator.next();
                StringBuilder wrap = wrap(object);
                stringBuilder.append(wrap);
                if (iterator.hasNext()) {
                    stringBuilder.append(",");
                }
            }
        } catch (Exception ex) {
            String toString = ex.toString();
            if (!toString.startsWith("org.hibernate")) {
                System.err.println("[JSONSerialiser] collection serialise exception: " + toString);
                return new StringBuilder().append("[\"JSON_COLLECTION_HIBERNATE_EXCEPTION\"]");
            }
            return new StringBuilder().append("[\"JSON_COLLECTION_EXCEPTION\"]");
        }
        return stringBuilder.append("]");
    }

    private StringBuilder serialiseHashSet(HashSet hashSet) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("[");
        try {
            List<String> serialisedCollection = new ArrayList<>();
            for (Iterator iterator = hashSet.iterator(); iterator.hasNext();) {
                Object object = iterator.next();
                StringBuilder wrap = wrap(object);
                serialisedCollection.add(wrap.toString());
            }
            Collections.sort(serialisedCollection);
            for (Iterator<String> iterator = serialisedCollection.iterator(); iterator.hasNext();) {
                String next = iterator.next();
                stringBuilder.append(next);
                if (iterator.hasNext()) {
                    stringBuilder.append(",");
                }
            }
        } catch (Exception ex) {
            String toString = ex.toString();
            if (!toString.startsWith("org.hibernate")) {
                System.err.println("[JSONSerialiser] hashset serialise exception: " + toString);
                return new StringBuilder().append("[\"JSON_HASHSET_HIBERNATE_EXCEPTION\"]");
            }
            return new StringBuilder().append("[\"JSON_HASHSET_EXCEPTION\"]");
        }
        return stringBuilder.append("]");
    }

    private StringBuilder serialiseString(CharSequence string) {
        StringBuilder stringBuilder = new StringBuilder();
        if (string == null || string.length() == 0) {
            return stringBuilder.append("\"\"");
        }
        stringBuilder.append('"');
        try {
            char b;
            char c = 0;
            String hexadecimal;
            int i;
            int len = string.length();

            for (i = 0; i < len; i++) {
                b = c;
                c = string.charAt(i);
                switch (c) {
                    case '\\':
                    case '"':
                        stringBuilder.append('\\');
                        stringBuilder.append(c);
                        break;
                    case '/':
                        if (b == '<') {
                            stringBuilder.append('\\');
                        }
                        stringBuilder.append(c);
                        break;
                    case '\b':
                        stringBuilder.append("\\b");
                        break;
                    case '\t':
                        stringBuilder.append("\\t");
                        break;
                    case '\n':
                        stringBuilder.append("\\n");
                        break;
                    case '\f':
                        stringBuilder.append("\\f");
                        break;
                    case '\r':
                        stringBuilder.append("\\r");
                        break;
                    default:
                        if (c < ' ' || (c >= '\u0080' && c < '\u00a0')
                                || (c >= '\u2000' && c < '\u2100')) {
                            stringBuilder.append("\\u");
                            hexadecimal = Integer.toHexString(c);
                            stringBuilder.append("0000", 0, 4 - hexadecimal.length());
                            stringBuilder.append(hexadecimal);
                        } else {
                            stringBuilder.append(c);
                        }
                }
            }
        } catch (Exception ex) {
            System.err.println("[JSONSerialiser] string serialise exception: " + ex);
            return new StringBuilder().append("\"JSON_STRING_EXCEPTION\"");
        }
        return stringBuilder.append('"');
    }

    private void log(String message) {
        if (TRACER_LOG == null) {
            return;
        }
        try (FileWriter fileWriter = new FileWriter(TRACER_LOG, true)) {
            fileWriter.write(message + "\n");
        } catch (IOException ex) {
        }
    }

    private static StringBuilder getReference(Object object) {
        return new StringBuilder().append("{\"r\":\"@")
                .append(object.getClass().getName())
                .append("\"}");
    }
}