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

com.fasterxml.jackson.databind.type.TypeParser Maven / Gradle / Ivy

There is a newer version: 2.17.1
Show newest version
package com.fasterxml.jackson.databind.type;

import java.util.*;

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.util.ClassUtil;

/**
 * Simple recursive-descent parser for parsing canonical {@link JavaType}
 * representations and constructing type instances.
 */
public class TypeParser
    implements java.io.Serializable
{
    private static final long serialVersionUID = 1L;

    /**
     * Maximum length of canonical type definition we will try to parse.
     * Used as protection for malformed generic type declarations.
     *
     * @since 2.16
     */
    protected static final int MAX_TYPE_LENGTH = 64_000;

    /**
     * Maximum levels of nesting allowed for parameterized types.
     * Used as protection for malformed generic type declarations.
     *
     * @since 2.16
     */
    protected static final int MAX_TYPE_NESTING = 1000;

    protected final TypeFactory _factory;

    public TypeParser(TypeFactory f) {
        _factory = f;
    }

    /**
     * @since 2.6.2
     */
    public TypeParser withFactory(TypeFactory f) {
        return (f == _factory) ? this : new TypeParser(f);
    }

    public JavaType parse(String canonical) throws IllegalArgumentException
    {
        if (canonical.length() > MAX_TYPE_LENGTH) {
            throw new IllegalArgumentException(String.format(
                    "Failed to parse type %s: too long (%d characters), maximum length allowed: %d",
                    _quoteTruncated(canonical),
                    canonical.length(),
                    MAX_TYPE_LENGTH));

        }
        MyTokenizer tokens = new MyTokenizer(canonical.trim());
        JavaType type = parseType(tokens, MAX_TYPE_NESTING);
        // must be end, now
        if (tokens.hasMoreTokens()) {
            throw _problem(tokens, "Unexpected tokens after complete type");
        }
        return type;
    }

    protected JavaType parseType(MyTokenizer tokens, int nestingAllowed)
        throws IllegalArgumentException
    {
        if (!tokens.hasMoreTokens()) {
            throw _problem(tokens, "Unexpected end-of-string");
        }
        Class base = findClass(tokens.nextToken(), tokens);

        // either end (ok, non generic type), or generics
        if (tokens.hasMoreTokens()) {
            String token = tokens.nextToken();
            if ("<".equals(token)) {
                List parameterTypes = parseTypes(tokens, nestingAllowed-1);
                TypeBindings b = TypeBindings.create(base, parameterTypes);
                return _factory._fromClass(null, base, b);
            }
            // can be comma that separates types, or closing '>'
            tokens.pushBack(token);
        }
        return _factory._fromClass(null, base, TypeBindings.emptyBindings());
    }

    protected List parseTypes(MyTokenizer tokens, int nestingAllowed)
        throws IllegalArgumentException
    {
        if (nestingAllowed < 0) {
            throw _problem(tokens, "too deeply nested; exceeds maximum of "
                    +MAX_TYPE_NESTING+" nesting levels");
        }
        ArrayList types = new ArrayList();
        while (tokens.hasMoreTokens()) {
            types.add(parseType(tokens, nestingAllowed));
            if (!tokens.hasMoreTokens()) break;
            String token = tokens.nextToken();
            if (">".equals(token)) return types;
            if (!",".equals(token)) {
                throw _problem(tokens, "Unexpected token '"+token+"', expected ',' or '>')");
            }
        }
        throw _problem(tokens, "Unexpected end-of-string");
    }

    protected Class findClass(String className, MyTokenizer tokens)
    {
        try {
            return _factory.findClass(className);
        } catch (Exception e) {
            ClassUtil.throwIfRTE(e);
            throw _problem(tokens, "Cannot locate class '"+className+"', problem: "+e.getMessage());
        }
    }

    protected IllegalArgumentException _problem(MyTokenizer tokens, String msg)
    {
        return new IllegalArgumentException(String.format("Failed to parse type %s (remaining: %s): %s",
                _quoteTruncated(tokens.getAllInput()),
                _quoteTruncated(tokens.getRemainingInput()),
                msg));
    }

    private static String _quoteTruncated(String str) {
        if (str.length() <= 1000) {
            return "'"+str+"'";
        }
        return String.format("'%s...'[truncated %d charaters]",
                str.substring(0, 1000), str.length() - 1000);
    }
    
    final static class MyTokenizer extends StringTokenizer
    {
        protected final String _input;

        protected int _index;

        protected String _pushbackToken;

        public MyTokenizer(String str) {
            super(str, "<,>", true);
            _input = str;
        }

        @Override
        public boolean hasMoreTokens() {
            return (_pushbackToken != null) || super.hasMoreTokens();
        }

        @Override
        public String nextToken() {
            String token;
            if (_pushbackToken != null) {
                token = _pushbackToken;
                _pushbackToken = null;
            } else {
                token = super.nextToken();
                _index += token.length();
                token = token.trim();
            }
            return token;
        }

        public void pushBack(String token) {
            _pushbackToken = token;
            // let's NOT change index for now, since token may have been trim()ed
        }

        public String getAllInput() { return _input; }
//        public String getUsedInput() { return _input.substring(0, _index); }
        public String getRemainingInput() { return _input.substring(_index); }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy