JSONSerialiser.java

370 lines | 12.326 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.IOException;
import java.io.Writer;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

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

    private static final Collection<Object> VISITED = new ArrayList<>();
    private static Writer WRITER;

    private static class StringBuilderWriter extends Writer {

        private final StringBuilder stringBuilder;

        public StringBuilderWriter(StringBuilder stringBuilder) {
            this.stringBuilder = stringBuilder;
        }

        @Override
        public void write(char[] cbuf, int off, int len) {
            stringBuilder.append(cbuf, off, len);
        }

        @Override
        public void flush() {
        }

        @Override
        public void close() {
        }

    }

    public static void serialise(Object bean, Writer writer, boolean cyclicFields) throws IOException {
        JSONSerialiser.WRITER = writer;
        serialiseBean(bean, cyclicFields);
        JSONSerialiser.WRITER = null;
    }

    public static void serialise(Object bean, Writer writer) throws IOException {
        serialise(bean, writer, true);
    }

    public static String serialise(Object bean, boolean cyclicFields) {
        StringBuilder stringBuilder = new StringBuilder();
        try {
            serialise(bean, new StringBuilderWriter(stringBuilder), cyclicFields);
        } catch (IOException ex) {
        }
        return stringBuilder.toString();
    }

    public static String serialise(Object bean) {
        return serialise(bean, true);
    }

    public static String serialiseCylicObject(Object bean) {
        resetCycleBreaker();
        String serialise = serialise(bean);
        resetCycleBreaker();
        return serialise;
    }

    public static String serialiseAcylicObject(Object bean) {
        resetCycleBreaker();
        String serialise = serialise(bean, false);
        resetCycleBreaker();
        return serialise;
    }
    
    public static void serialiseAcylicObject(Object bean, Writer writer) throws IOException {
        resetCycleBreaker();
        serialise(bean, writer, false);
        resetCycleBreaker();
    }

    public static void resetCycleBreaker() {
        JSONSerialiser.VISITED.clear();
    }

    private static void serialiseBean(Object bean, boolean cyclicFields) throws IOException {
        if (bean == null) {
            JSONSerialiser.WRITER.append("null");
            return;
        }
        Class<?> klass = bean.getClass();
        List<Field> fields = new ArrayList<>();
        fields = getAllFields(fields, klass);
        JSONSerialiser.WRITER.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;
                }
                if (!cyclicFields) {
                    resetCycleBreaker();
                }
                JSONSerialiser.WRITER.append("\"").append(field.getName()).append("\"");
                JSONSerialiser.WRITER.append(":");
                wrap(result);
            } catch (Exception ignore) {
                JSONSerialiser.WRITER.append("\"").append("JSON-FIELD-EXCEPTION").append("\"");
                System.err.println("[JSON-trace] field exception: " + ignore);
            }
            if (it.hasNext()) {
                JSONSerialiser.WRITER.append(",");
            }
        }
        JSONSerialiser.WRITER.append("}");
    }

    private static void serialiseBean(Object bean) throws IOException {
        serialiseBean(bean, true);
    }

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

    private static void wrap(Object object) throws IOException {
        try {
            if (object == null) {
                JSONSerialiser.WRITER.append("null");
                return;
            }
            if (object instanceof Boolean) {
                JSONSerialiser.WRITER.append("\"").append(String.valueOf(object)).append("\"");
                return;
            }
            if (object instanceof Character) {
                serialiseString(String.valueOf(object));
                return;
            }
            if (object instanceof String) {
                String string = (String) object;
                serialiseString(string);
                return;
            }
            if (object instanceof Number) {
                Number number = (Number) object;
                serialiseNumber(number);
                return;
            }
            if (object instanceof Enum) {
                serialiseString(((Enum<?>) object).name());
                return;
            }
            if (object instanceof Collection) {
                Collection<?> collection = (Collection<?>) object;
                serialiseCollection(collection);
                return;
            }
            if (object.getClass().isArray()) {
                serialiseArray(object);
                return;
            }
            if (object instanceof Map) {
                Map<?, ?> map = (Map<?, ?>) object;
                serialiseMap(map);
                return;
            }
            if (VISITED.stream().anyMatch((visited) -> (visited == object))) {
                try {
                    JSONSerialiser.WRITER.append("\"").append(String.valueOf(object)).append("\"");
                } catch (Exception e) {
                    JSONSerialiser.WRITER.append("\"").append(object.getClass().getName()).append("\"");
                } finally {
                    return;
                }
            }
            VISITED.add(object);
            serialiseBean(object);
        } catch (ConcurrentModificationException exception) {
            System.err.println("[JSONSerialiser] ConcurrentModificationException");
        } catch (Exception exception) {
            System.err.println("[JSON-java] serialise exception: " + exception);
            JSONSerialiser.WRITER.append("\"").append("JSON_SERIALISE_EXCEPTION").append("\"");
            exception.printStackTrace();
        }
    }

    private static void serialiseMap(Map map) throws IOException {
        try {
            map.keySet().iterator();
        } catch (Exception ex) {
            return;
        }
        JSONSerialiser.WRITER.append("{");
        for (Iterator<?> it = map.keySet().iterator(); it.hasNext();) {
            Object key = it.next();
            if (key == null || map.get(key) == null) {
                continue;
            }
            if (!(key instanceof String)) {
                JSONSerialiser.WRITER.append("\"");
                wrap(key);
                JSONSerialiser.WRITER.append("\"");
            } else {
                wrap(key);
            }
            JSONSerialiser.WRITER.append(":");
            wrap(map.get(key));
            if (it.hasNext()) {
                JSONSerialiser.WRITER.append(",");
            }
        }
        JSONSerialiser.WRITER.append("}");
    }

    private static void serialiseArray(Object array) throws IOException {
        JSONSerialiser.WRITER.append("[");
        int length = Array.getLength(array);
        for (int i = 0; i < length;) {
            Object object = Array.get(array, i);
            wrap(object);
            if (++i < length) {
                JSONSerialiser.WRITER.append(",");
            }
        }
        JSONSerialiser.WRITER.append("]");
    }

    private static void serialiseCollection(Collection collection) throws IOException {
        try {
            collection.iterator();
        } catch (Exception ex) {
            return;
        }
        JSONSerialiser.WRITER.append("[");
        for (Iterator it = collection.iterator(); it.hasNext();) {
            Object object = it.next();
            wrap(object);
            if (it.hasNext()) {
                JSONSerialiser.WRITER.append(",");
            }
        }
        JSONSerialiser.WRITER.append("]");
    }

    private static void serialiseString(String string) throws IOException {
        if (string == null || string.isEmpty()) {
            JSONSerialiser.WRITER.append("\"\"");
            return;
        }

        char b;
        char c = 0;
        String hexadecimal;
        int i;
        int len = string.length();

        JSONSerialiser.WRITER.append('"');
        for (i = 0; i < len; i++) {
            b = c;
            c = string.charAt(i);
            switch (c) {
                case '\\':
                case '"':
                    JSONSerialiser.WRITER.append('\\');
                    JSONSerialiser.WRITER.append(c);
                    break;
                case '/':
                    if (b == '<') {
                        JSONSerialiser.WRITER.append('\\');
                    }
                    JSONSerialiser.WRITER.append(c);
                    break;
                case '\b':
                    JSONSerialiser.WRITER.append("\\b");
                    break;
                case '\t':
                    JSONSerialiser.WRITER.append("\\t");
                    break;
                case '\n':
                    JSONSerialiser.WRITER.append("\\n");
                    break;
                case '\f':
                    JSONSerialiser.WRITER.append("\\f");
                    break;
                case '\r':
                    JSONSerialiser.WRITER.append("\\r");
                    break;
                default:
                    if (c < ' ' || (c >= '\u0080' && c < '\u00a0')
                            || (c >= '\u2000' && c < '\u2100')) {
                        JSONSerialiser.WRITER.append("\\u");
                        hexadecimal = Integer.toHexString(c);
                        JSONSerialiser.WRITER.append("0000", 0, 4 - hexadecimal.length());
                        JSONSerialiser.WRITER.append(hexadecimal);
                    } else {
                        JSONSerialiser.WRITER.append(c);
                    }
            }
        }
        JSONSerialiser.WRITER.append('"');
    }

    private static void serialiseNumber(Number number) throws IOException {
        // not all Numbers may match actual JSON Numbers. i.e. Fractions or Complex
        final String numberAsString = numberToString(number);
        try {
            // Use the BigDecimal constructor for it's parser to validate the format.
            @SuppressWarnings("unused")
            BigDecimal unused = new BigDecimal(numberAsString);
            // Close enough to a JSON number that we will return it unquoted
            JSONSerialiser.WRITER.append(numberAsString);
        } catch (NumberFormatException ex) {
            // The Number value is not a valid JSON number.
            // Instead we will quote it as a string
            serialiseString(numberAsString);
        }

    }

    private static String numberToString(Number number) {
        if (number instanceof Double) {
            if (((Double) number).isInfinite() || ((Double) number).isNaN()) {
                return String.valueOf(number);
            }
        } else if (number instanceof Float) {
            if (((Float) number).isInfinite() || ((Float) number).isNaN()) {
                return String.valueOf(number);
            }
        }

        // 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;
    }

}