All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.nustaq.kson.KsonDeserializer Maven / Gradle / Ivy

Go to download

A fast java serialization drop in-replacement and some serialization based utils such as Structs and OffHeap Memory.

There is a newer version: 3.0.4-jdk17
Show newest version
/*
 * Copyright 2014 Ruediger Moeller.
 *
 * 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.
 */

package org.nustaq.kson;


import org.nustaq.serialization.FSTClazzInfo;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.*;

/**
 * parses kson format as well as json. somewhat quick&dirty, anyway targeted for easy mapping of config files/data and
 * to connect kontraktor's actors to slow-end tech like webservices & jscript front ends.
 *
 * Note: this is pretty fuzzy code (typeguessing, best effort parsing ..)
 */
public class KsonDeserializer {

    public static boolean DEBUG_STACK = false; // turn on for parsestack on error, slows down !!

    protected KsonCharInput in;
    protected KsonTypeMapper mapper;
    protected Stack stack;
    protected boolean supportJSon = true;

    private KsonArgTypesResolver argTypesRessolver;

    static class ParseStep {
        KsonCharInput in;
        int position;
        String action;

        ParseStep(KsonCharInput in, int position, String action) {
            this.in = in;
            this.position = position;
            this.action = action;
        }

        ParseStep(String action, KsonCharInput in) {
            this(in, in.position(),action);
        }

        @Override
        public String toString() {
            return ""+action+" at pos:"+position;
        }
    }


    public KsonDeserializer(KsonCharInput in, KsonTypeMapper mapper) {
        this.in = in;
        this.mapper = mapper;
        if (DEBUG_STACK) {
            stack = new Stack<>();
            if ( in instanceof KsonStringCharInput ) {
                ((KsonStringCharInput) in).stack = stack;
            }
        }
    }

    public boolean isSupportJSon() {
        return supportJSon;
    }

    public KsonDeserializer supportJSon(boolean supportJSon) {
        this.supportJSon = supportJSon;
        return this;
    }

    public void skipWS() {
        int ch = in.readChar();
        while (ch >= 0 && Character.isWhitespace(ch)) {
            ch = in.readChar();
        }
        if (ch == '#') {
            ch = in.readChar();
            while (ch >= 0 && ch != '\n') {
                ch = in.readChar();
            }
            skipWS();
        } else if (ch > 0)
            in.back(1);
    }

    public Object readObject(Class expect, Class genericKeyType, Class genericValueType) throws Exception {
        if ( expect == Object.class )
            expect = null;
        if ( genericKeyType == Object.class )
            genericKeyType = null;
        if ( genericValueType == Object.class )
            genericValueType = null;
        try {
            int position = in.position();
            skipWS();
            if (in.isEof())
                return null;
            String type = readId();
            Object literal = mapper.mapLiteral(type);
            if (literal != null ) {
                return literal == mapper.NULL_LITERAL ? null:literal;
            }
            skipWS();
            Class mappedClass = null;
            if ( supportJSon && "".equals(type)) {
                String tp = scanJSonType();
                if ( tp != null && mapper.getType(tp) != null ) {
                    type = tp;
                }
            }
            if ("".equals(type)) {
                mappedClass = expect;
            } else {
                mappedClass = mapper.getType(type);
            }
            if (mappedClass == null) {
                if ( expect != null ) {
                    mappedClass = expect;
                } else {
                    if ( in.position() == position ) {
                        throw new KsonParseException("could not evaluate type ", in);
                    }
                    return type; // assume string
                }
            }
            if (mappedClass == List.class || mappedClass == Collection.class)
                mappedClass = ArrayList.class;
            if (mappedClass == Map.class)
                mappedClass = HashMap.class;
            if (mappedClass == Set.class)
                mappedClass = HashSet.class;
            FSTClazzInfo clInfo = Kson.conf.getCLInfoRegistry().getCLInfo(mappedClass, Kson.conf);
            if (DEBUG_STACK) {
                if ( clInfo != null ) {
                    stack.push(new ParseStep("try reading type " + clInfo.getClazz().getName(), in));
                } else
                    stack.push(new ParseStep("try reading unknown object type", in));
            }
            int ch = in.readChar();
            if (ch != '{' && ch != '[') {
                throw new KsonParseException("expected '{' or '['", in);
            }
            Object res = null;
            if (Map.class.isAssignableFrom(clInfo.getClazz())) {
                if ( clInfo.getClazz() == HashMap.class ) {
                    // newInstance delivers errorneous initialized hashmap, constructorForSerializable ..
                    // should switch to other instantiation method as this is not a serialization ..
                    res = new HashMap<>();
                }
                else
                    res = clInfo.newInstance(true);
                if (DEBUG_STACK) {
                    stack.push(new ParseStep("read map " + clInfo.getClazz().getName() + "<" + genericKeyType + "," + genericValueType + ">", in));
                }
                List keyVals = readList(genericKeyType, genericValueType);
                for (int i = 0; i < keyVals.size(); i += 2) {
                    Object fi = keyVals.get(i);
                    Object val = keyVals.get(i + 1);
                    ((Map) res).put(fi, val);
                }
                if (DEBUG_STACK) {
                    stack.pop();
                }
            } else if (Collection.class.isAssignableFrom(clInfo.getClazz())) {
                List keyVals = readList(genericKeyType, genericKeyType);
                if (clInfo.getClazz() == ArrayList.class) { // default constructor fails ...
                    res = new ArrayList<>(keyVals.size());
                } else if (clInfo.getClazz() == HashSet.class) { // default constructor fails ...
                    res = new HashSet<>(keyVals.size());
                } else {
                    res = clInfo.newInstance(true);
                }
                if (DEBUG_STACK) {
                    stack.push(new ParseStep("read list " + clInfo.getClazz().getName()+"<"+genericKeyType+"|"+genericValueType+">", in));
                }
                for (int i = 0; i < keyVals.size(); i++) {
                    Object o = keyVals.get(i);
                    ((Collection) res).add(o);
                }
                if (DEBUG_STACK) {
                    stack.pop();
                }
            } else if (clInfo.getClazz().isArray()) {
                Class componentType = clInfo.getClazz().getComponentType();
                if (componentType.isArray())
                    throw new KsonParseException("nested arrays not supported", in);
                if (DEBUG_STACK) {
                    stack.push(new ParseStep("read array of type " + clInfo.getClazz().getComponentType().getName(), in));
                }
                List keyVals = readList(componentType, componentType);
                res = Array.newInstance(componentType, keyVals.size());
                for (int i = 0; i < keyVals.size(); i++) {
                    Array.set(res, i, keyVals.get(i));
                }
                if (DEBUG_STACK) {
                    stack.pop();
                }
            } else {
                try {
                    res = clInfo.getClazz().newInstance(); // first try empty constructor to keep default values
                } catch (Throwable th) {}
                if ( res == null )
                    res = clInfo.newInstance(true);
                if (res==null) {
                    throw new RuntimeException(clInfo.getClazz().getName()+" misses a default constructor. Instantiation failed.");
                }
                List keyVals = readObjectFields(clInfo);

                for (int i = 0; i < keyVals.size(); i += 2) {
                    String fi = (String) keyVals.get(i);
                    Object val = keyVals.get(i + 1);
                    Field field = clInfo.getFieldInfo(fi, null).getField();
                    if ( field.getType().isEnum() && val instanceof String) {
                        val = Enum.valueOf( (Class)field.getType(), (String) val);
                    }
                    field.set(res, val);
                }
            }
            if (DEBUG_STACK) {
                stack.pop();
            }
            return res;
        } catch (Exception ex) {
            throw new KsonParseException("unexpected error, tried reading object", in, ex);
        }
    }

    protected String scanJSonType() {
        int position = in.position();
        skipWS();
        int ch;
        // just scan first sttribute and expect a string value which is taken as mapped class name
        do {
            skipWS();
            ch = in.readChar();
            if ( ch == '{' ) {
                skipWS();
                String key = readString();
                if ( "_type".equals(key) ) {
                    skipWS();
                    if ( in.readChar() != ':' ) {
                        in.back(in.position()-position);
                        return null;
                    }
                    skipWS();
                    String res = readString();
                    in.back(in.position()-position);
                    return res;
                } else {
                    in.back(in.position()-position);
                    return null;
                }
            }
        } while ( ch != ':' && ch != '}' && ch != '[');

        in.back(in.position()-position);
        return null;
    }

    private String readString() {
        return readString(in.peekChar() == '\"' || in.peekChar() == '\'');
    }

    protected List readObjectFields(FSTClazzInfo targetClz) throws Exception {

        ArrayList result = new ArrayList();
        skipWS();

        if (DEBUG_STACK) {
            stack.push(new ParseStep("read object of type "+targetClz.getClazz().getName(),in));
        }
        while (in.peekChar() > 0 && in.peekChar() != '}' && in.peekChar() != ']') {

            if (in.peekChar() == ':' || in.peekChar() == ',') {
                in.readChar(); // skip
                skipWS();
            }
            String field = (String) readValue(String.class, null, null);
            if ( "_type".equals(field)) {
                skipWS();
                in.readChar(); // ':'
                skipWS();
                readString();
                skipWS();
                continue;
            }
            result.add(field);
            skipWS();
            if (in.peekChar() == ':' || in.peekChar() == ',') {
                in.readChar(); // skip
                skipWS();
            }
            FSTClazzInfo.FSTFieldInfo fieldInfo = targetClz.getFieldInfo(field, null);
            Class type = fieldInfo == null ? null : fieldInfo.getType();

            // special to parse argument lists using reflected class types
            if ( argTypesRessolver != null && type == Object[].class && fieldInfo.getField().getAnnotation(ArgTypes.class) != null) {
                Class argTypes[] = argTypesRessolver.getArgTypes(targetClz.getClazz(), result);
                if (argTypes != null ) {
                    skipWS();
                    int ch = in.readChar();
                    if (ch != '{' && ch != '[' ) {
                        throw new KsonParseException("expected { or [ ",in);
                    }
                    result.add(readList(argTypes, argTypes).toArray());
                    skipWS();
//                    consumed by readList
//                    ch = in.readChar();
//                    if (ch != '}' && ch != ']' ) {
//                        throw new KsonParseException("expected } or ] ",in);
//                    }
//                    skipWS();
                    continue;
                }
            }

            if (fieldInfo != null) {
                if (DEBUG_STACK) {
                    stack.push(new ParseStep("read field '"+fieldInfo.getName()+"' of type "+type.getName(),in));
                }
                result.add(readValue(type, Kson.fumbleOutGenericKeyType(fieldInfo.getField()), Kson.fumbleOutGenericValueType(fieldInfo.getField())));
                if (DEBUG_STACK) {
                    stack.pop();
                }
            } else {
                System.out.println("No such field '" + field + "' on class " + targetClz.getClazz().getName());
            }
            skipWS();
        }
        in.readChar(); // consume }
        if (DEBUG_STACK) {
            stack.pop();
        }
        return result;
    }

    public KsonArgTypesResolver getArgTypesRessolver() {
        return argTypesRessolver;
    }

    public KsonDeserializer setArgTypesRessolver(KsonArgTypesResolver argTypesRessolver) {
        this.argTypesRessolver = argTypesRessolver;
        return this;
    }

    protected List readList(Class[] keyType, Class[] valueType) throws Exception {
        ArrayList result = new ArrayList();
        skipWS();
        boolean expectKey = true;
        int index = 0;
        while (in.peekChar() > 0 && in.peekChar() != '}' && in.peekChar() != ']') {
            skipWS();
            if (expectKey) {
                result.add(readValue(keyType[index], null, null));
                expectKey = !expectKey;
            } else {
                if (in.peekChar() == ':' || in.peekChar() == ',') {
                    in.readChar(); // skip
                    skipWS();
                }
                result.add(readValue(valueType[index], null, null));
                expectKey = !expectKey;
                // just ignore unnecessary stuff
                skipWS();
                if (in.peekChar() == ':' || in.peekChar() == ',') {
                    in.readChar(); // skip
                }
            }
            skipWS();
            index++;
//            if ( index >= valueType.length ) {
//                break;
//            }
        }
        in.readChar(); // consume }
        return result;
    }

    protected List readList(Class keyType, Class valueType) throws Exception {
        ArrayList result = new ArrayList();
        skipWS();
        boolean expectKey = true;
        while (in.peekChar() > 0 && in.peekChar() != '}' && in.peekChar() != ']') {
            skipWS();
            if (expectKey) {
                result.add(readValue(keyType, null, null));
                expectKey = !expectKey;
            } else {
                if (in.peekChar() == ':' || in.peekChar() == ',') {
                    in.readChar(); // skip
                    skipWS();
                }
                if (DEBUG_STACK) {
                    stack.push(new ParseStep("read value for key '" + result.get(result.size() - 1) + "'", in));
                }
                result.add(readValue(valueType, null, null));
                if (DEBUG_STACK) {
                    stack.pop();
                }
                expectKey = !expectKey;
                // just ignore unnecessary stuff
                skipWS();
                if (in.peekChar() == ':' || in.peekChar() == ',') {
                    in.readChar(); // skip
                }
            }
            skipWS();
        }
        in.readChar(); // consume }
        return result;
    }

    protected Object readValue(Class expected, Class genericKeyType, Class genericValueType) throws Exception {
        skipWS();
        int ch = in.peekChar();
        if (ch == '"' || ch == '\'' || isFromStringValue(expected)) {
            // string
            return mapper.coerceReading(expected, readString(ch == '"' || ch == '\''));
        } else if (Character.isLetter(ch) || ch == '{' || ch == '[') {
            if ( ch == '[' && ! isContainer(expected) && (expected == null || expected == Object.class || expected.isInterface())) {
                in.readChar();
                if ( expected != null &&
                    ! Map.class.isAssignableFrom(expected) &&
                    Collection.class.isAssignableFrom(expected) &&
                    genericValueType == null )
                {
                    // default vlaueType to keyType for nnon-maps
                    genericValueType = genericKeyType;
                }
                return readList(genericKeyType, genericValueType);
            } else {
                // object
                return readObject(expected, genericKeyType, genericValueType);
            }
        } else if (Character.isDigit(ch) || ch == '+' || ch == '-' || ch == '.') {
            Class type = expected;
            if (type == float.class || type == double.class) {
                // .. extra slow for float&double. fixme: optimize this
                String num = readNums();
                double val = Double.parseDouble(num);
                if (type == double.class) {
                    return val;
                } else if (type == float.class) {
                    return (float) val;
                } else if (type == String.class) {
                    return "" + val;
                } else {
                    throw new KsonParseException("cannot assign floating point to " + type.getName(), in);
                }
            } else {
                // num
                boolean neg = false;
                if (ch == '+') {
                    in.readChar();
                } else if (ch == '-') {
                    neg = true;
                    in.readChar();
                }
                long l = readLong() * (neg ? -1 : 1);
                if (in.peekChar()=='.') {
                    // untyped floating point. FIXME: very slow
                    final String dotValue = readString(false);
                    if ( type == float.class || type == Float.class ) {
                        return Float.parseFloat(l+dotValue);
                    }
                    return Double.parseDouble(l+dotValue);
                }
                if (type == boolean.class) {
                    return l != 0;
                } else if (type == byte.class || type == Byte.class) {
                    byte b = (byte) (((l + 256) & 0xff) - 256);
                    return b;
                } else if (type == char.class || type == Character.class) {
                    return (char) l;
                } else if (type == short.class || type == Short.class) {
                    return (short) l;
                } else if (type == int.class || type == Integer.class) {
                    return (int) l;
                } else if (type == long.class || type == Long.class) {
                    return l;
                } else if (type == String.class) {
                    return "" + l;
                } else {
                    return l;
                }
            }
        } else if (Character.isJavaIdentifierStart(ch)) { // last resort string
            return readString(false);
        }
        throw new KsonParseException("value expected", in);
    }

    private boolean isContainer(Class expected) {
        return expected != null && (expected.isArray() || Collection.class.isAssignableFrom(expected));
    }

    protected boolean isFromStringValue(Class type) {
        return type == String.class;
    }

    protected long readLong() {
        int read=0;
        long res = 0;
        long fak = 1;
        int ch = in.readChar();
        boolean empty = true;
        while (Character.isDigit(ch) || ch == '_') {
            if ( ch == '_' )
                ch = in.readChar();
            read++;
            empty = false;
            res += (ch - '0') * fak;
            fak *= 10;
            ch = in.readChar();
        }
        in.back(1);
        long reverse = 0;
        while (read-- != 0) {
            reverse = reverse * 10 + (res % 10);
            res = res / 10;
        }
        if (empty)
            throw new KsonParseException("expected int type number",in);
        return reverse;
    }

    protected String readString(boolean quoted) {
        StringBuilder b = new StringBuilder(15);
        int end = quoted ? in.readChar() : ' '; // " or '
        int ch = in.readChar();
        while ((quoted && ch != end) ||
                (!quoted && ch > 32 && ch != '#' && ch != '}' && ch != ']' && ch != ':' && ch != ',' && !Character.isWhitespace(ch))) {
            if (ch == '\\') {
                ch = in.readChar();
                switch (ch) {
                    case '\\':
                        b.append(ch);
                        break;
                    case '"':
                        b.append('"');
                        break;
                    case '/':
                        b.append('/');
                        break;
                    case 'b':
                        b.append('\b');
                        break;
                    case 'f':
                        b.append('\f');
                        break;
                    case 'n':
                        b.append('\n');
                        break;
                    case 'r':
                        b.append('\r');
                        break;
                    case 't':
                        b.append('\t');
                        break;
                    case 'u':
                        b.append("\\u").append((char) in.readChar()).append((char) in.readChar()).append((char) in.readChar()).append((char) in.readChar());
                        break;
                    default:
                        throw new RuntimeException("unknown escape " + (char) ch + " in " + in.position());
                }
            } else {
                b.append((char) ch);
            }
            ch = in.readChar();
        }
        if (!quoted) {
            in.back(1);
        }
        return b.toString();
    }

    protected String readNums() {
        skipWS();
        int pos = in.position();
        int ch = in.readChar();
        while (Character.isDigit(ch) || ch == '.' || ch == 'E' || ch == 'e' || ch == '+' || ch == '-' || ch == '_') {
            ch = in.readChar();
        }
        in.back(1);
        return in.getString(pos, in.position() - pos).replace("_","");
    }

    protected String readId() {
        skipWS();
        int pos = in.position();
        int ch = in.readChar();
        while (isIdPart(ch) && ch != ':' && ch != ',') {
            ch = in.readChar();
        }
        in.back(1);
        return in.getString(pos, in.position() - pos);
    }

    protected boolean isIdPart(int ch) {
        return Character.isLetterOrDigit(ch) || ch == '$' || ch == '#' || ch == '_' || ch == '.';
    }

    protected boolean isIdStart(int ch) {
        return Character.isLetter(ch) || ch == '$' || ch == '#' || ch == '_';
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy