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

com.fasterxml.jackson.databind.jsontype.impl.ClassNameIdResolver Maven / Gradle / Ivy

There is a newer version: 2.17.0
Show newest version
package com.fasterxml.jackson.databind.jsontype.impl;

import java.io.IOException;
import java.util.*;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.util.ClassUtil;

/**
 * {@link com.fasterxml.jackson.databind.jsontype.TypeIdResolver} implementation
 * that converts between fully-qualified
 * Java class names and (JSON) Strings.
 */
public class ClassNameIdResolver
    extends TypeIdResolverBase
{
    private final static String JAVA_UTIL_PKG = "java.util.";

    public ClassNameIdResolver(JavaType baseType, TypeFactory typeFactory) {
        super(baseType, typeFactory);
    }

    @Override
    public JsonTypeInfo.Id getMechanism() { return JsonTypeInfo.Id.CLASS; }

    public void registerSubtype(Class type, String name) {
        // not used with class name - based resolvers
    }
    
    @Override
    public String idFromValue(Object value) {
        return _idFrom(value, value.getClass(), _typeFactory);
    }

    @Override
    public String idFromValueAndType(Object value, Class type) {
        return _idFrom(value, type, _typeFactory);
    }

    @Override
    public JavaType typeFromId(DatabindContext context, String id) throws IOException {
        return _typeFromId(id, context);
    }

    protected JavaType _typeFromId(String id, DatabindContext ctxt) throws IOException
    {
        /* 30-Jan-2010, tatu: Most ids are basic class names; so let's first
         *    check if any generics info is added; and only then ask factory
         *    to do translation when necessary
         */
        TypeFactory tf = ctxt.getTypeFactory();
        if (id.indexOf('<') > 0) {
            // note: may want to try combining with specialization (esp for EnumMap)?
            // 17-Aug-2017, tatu: As per [databind#1735] need to ensure assignment
            //    compatibility -- needed later anyway, and not doing so may open
            //    security issues.
            JavaType t = tf.constructFromCanonical(id);
            if (!t.isTypeOrSubTypeOf(_baseType.getRawClass())) {
                // Probably cleaner to have a method in `TypeFactory` but can't add in patch
                throw new IllegalArgumentException(String.format(
                        "Class %s not subtype of %s", t.getRawClass().getName(), _baseType));
            }
            return t;
        }
        Class cls;
        try {
            cls =  tf.findClass(id);
        } catch (ClassNotFoundException e) {
            // 24-May-2016, tatu: Ok, this is pretty ugly, but we should always get
            //   DeserializationContext, just playing it safe
            if (ctxt instanceof DeserializationContext) {
                DeserializationContext dctxt = (DeserializationContext) ctxt;
                // First: we may have problem handlers that can deal with it?
                return dctxt.handleUnknownTypeId(_baseType, id, this, "no such class found");
            }
            // ... meaning that we really should never get here.
            return null;
        } catch (Exception e) {
            throw new IllegalArgumentException("Invalid type id '"+id+"' (for id type 'Id.class'): "+e.getMessage(), e);
        }
        return tf.constructSpecializedType(_baseType, cls);
    }
    
    /*
    /**********************************************************
    /* Internal methods
    /**********************************************************
     */

    protected String _idFrom(Object value, Class cls, TypeFactory typeFactory)
    {
        // Need to ensure that "enum subtypes" work too
        if (Enum.class.isAssignableFrom(cls)) {
            if (!cls.isEnum()) { // means that it's sub-class of base enum, so:
                cls = cls.getSuperclass();
            }
        }
        String str = cls.getName();
        if (str.startsWith(JAVA_UTIL_PKG)) {
            // 25-Jan-2009, tatu: There are some internal classes that we cannot access as is.
            //     We need better mechanism; for now this has to do...

            // Enum sets and maps are problematic since we MUST know type of
            // contained enums, to be able to deserialize.
            // In addition, EnumSet is not a concrete type either
            if (value instanceof EnumSet) { // Regular- and JumboEnumSet...
                Class enumClass = ClassUtil.findEnumType((EnumSet) value);
                // not optimal: but EnumSet is not a customizable type so this is sort of ok
               str = typeFactory.constructCollectionType(EnumSet.class, enumClass).toCanonical();
            } else if (value instanceof EnumMap) {
                Class enumClass = ClassUtil.findEnumType((EnumMap) value);
                Class valueClass = Object.class;
                // not optimal: but EnumMap is not a customizable type so this is sort of ok
                str = typeFactory.constructMapType(EnumMap.class, enumClass, valueClass).toCanonical();
            } else {
                // 17-Feb-2010, tatus: Another such case: result of Arrays.asList() is
                // named like so in Sun JDK... Let's just plain old ArrayList in its place.
                // ... also, other similar cases exist...
                String suffix = str.substring(JAVA_UTIL_PKG.length());
                if (isJavaUtilCollectionClass(suffix, "List")) {
                    str = ArrayList.class.getName();
                } else if (isJavaUtilCollectionClass(suffix, "Map")){
                    str = HashMap.class.getName();
                } else if (isJavaUtilCollectionClass(suffix, "Set")){
                    str = HashSet.class.getName();
                }
            }
        } else if (str.indexOf('$') >= 0) {
            /* Other special handling may be needed for inner classes,
             * The best way to handle would be to find 'hidden' constructor; pass parent
             * value etc (which is actually done for non-anonymous static classes!),
             * but that is just not possible due to various things. So, we will instead
             * try to generalize type into something we will be more likely to be able
             * construct.
             */
            Class outer = ClassUtil.getOuterClass(cls);
            if (outer != null) {
                // one more check: let's actually not worry if the declared static type is
                // non-static as well; if so, deserializer does have a chance at figuring it all out.
                Class staticType = _baseType.getRawClass();
                if (ClassUtil.getOuterClass(staticType) == null) {
                    // Is this always correct? Seems like it should be...
                    cls = _baseType.getRawClass();
                    str = cls.getName();
                }
            }
        }
        return str;
    }

    @Override
    public String getDescForKnownTypeIds() {
        return "class name used as type id";
    }

    private static boolean isJavaUtilCollectionClass(String clz, String type)
    {
        if (clz.startsWith("Collections$")) {
            // 02-Jan-2017, tatu: As per [databind#1868], may need to leave Unmodifiable variants as is
            return (clz.indexOf(type) > 0)
                    && !clz.contains("Unmodifiable");
        }
        if (clz.startsWith("Arrays$")) {
            return (clz.indexOf(type) > 0);
        }
        return false;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy