ContainerFamily.java

package tools.jackson.datatype.fastutil.deser;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Supplier;

import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonParser;

import tools.jackson.databind.DatabindException;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.exc.ValueInstantiationException;
import tools.jackson.databind.type.TypeFactory;
import tools.jackson.databind.util.ClassUtil;
import tools.jackson.datatype.fastutil.util.OptionalTypes;

/**
 * Describes a family of fastutil container types that share the same element type
 * (like {@code IntCollection}, {@code IntList}, {@code IntSet}...) or the same key and
 * value types (like {@code Int2LongMap}, {@code Int2LongSortedMap}), and knows how to
 * construct an instance of any concrete or abstract member of that family during
 * deserialization.
 *<p>
 * Resolution rules, given the requested type:
 *<ol>
 * <li>One of the registered interfaces: the registered default implementation is used
 *   (for example {@code IntArrayList} for {@code IntList})</li>
 * <li>A concrete type with a public no-arguments constructor
 *   (for example {@code IntLinkedOpenHashSet}) is instantiated directly</li>
 * <li>Unmodifiable, synchronized, singleton and empty wrappers nested in fastutil
 *   helper classes (for example {@code IntLists.UnmodifiableList}, as found in type ids
 *   written with polymorphic typing) are rebuilt by deserializing into an
 *   order-preserving implementation and wrapping it using the matching
 *   {@code unmodifiable(...)} or {@code synchronize(...)} factory method</li>
 * <li>Concrete types with a public constructor that takes a member of the family
 *   (for example {@code IntImmutableList(IntList)}) are built from an
 *   order-preserving implementation</li>
 * <li>Other abstract types (like {@code AbstractIntSet}) are mapped to the default
 *   implementation of the most specific registered interface they implement
 *   (here {@code IntOpenHashSet} for {@code IntSet}), if it is assignable to them</li>
 *</ol>
 *
 * @param <C> Base type of all containers in the family
 */
public final class ContainerFamily<C>
{
    private final static String FASTUTIL_PACKAGE_PREFIX = "it.unimi.dsi.fastutil.";

    private final Class<?> _baseType;

    // Most specific interfaces first
    private final List<Kind<C>> _kinds = new ArrayList<>();

    // Lookups use reflection (with exceptions for missing constructors and methods), so
    // results are cached, including negative ones; `ClassValue` does not prevent unloading
    // of classes (unlike a `Map` keyed by class would)
    private final ClassValue<Optional<Creator<C>>> _creators = new ClassValue<>() {
        @Override
        protected Optional<Creator<C>> computeValue(Class<?> rawType) {
            return Optional.ofNullable(_findCreator(rawType));
        }
    };

    public ContainerFamily(Class<?> baseType) {
        _baseType = baseType;
    }

    /**
     * Registers an interface of this family; interfaces need to be registered from
     * the most specific to the least specific one, and all of them before the
     * first call to {@link #findCreator} (whose results are cached).
     *
     * @param iface Interface type
     * @param defaultImpl Factory for the implementation to use when deserializing
     *    into {@code iface}
     * @param orderedImpl Factory for an implementation that preserves insertion order,
     *    used as the intermediate value when deserializing into wrappers
     */
    public ContainerFamily<C> add(Class<?> iface,
            Supplier<? extends C> defaultImpl, Supplier<? extends C> orderedImpl) {
        // Instantiated once to find out the implementation type, so that resolving
        // abstract types need not create instances
        _kinds.add(new Kind<>(iface, defaultImpl.get().getClass(), defaultImpl, orderedImpl));
        return this;
    }

    /**
     * Calls {@link #add} through given callback, unless the types it uses are not
     * available (for example {@code ShortSet}, which {@code fastutil-core} does not
     * include, although it does include {@code ShortList}).
     */
    public ContainerFamily<C> addIfPresent(Consumer<ContainerFamily<C>> registration) {
        OptionalTypes.registerIfPresent(() -> registration.accept(this));
        return this;
    }

    public Class<?> getBaseType() {
        return _baseType;
    }

    public boolean handles(Class<?> rawType) {
        return _baseType.isAssignableFrom(rawType);
    }

    /**
     * @return Creator for given type, if it belongs to this family and can be
     *   constructed; {@code null} otherwise
     */
    public Creator<C> findCreator(Class<?> rawType)
    {
        return _creators.get(rawType).orElse(null);
    }

    private Creator<C> _findCreator(Class<?> rawType)
    {
        if (!handles(rawType)) {
            return null;
        }
        for (Kind<C> kind : _kinds) {
            // Registered default implementations (like `IntArrayList`), commonly used
            // as declared types, too, are created without reflection
            if (kind.iface() == rawType || kind.defaultImplType() == rawType) {
                return new Creator<>(rawType, kind.defaultImpl()::get, null);
            }
        }
        if (rawType.isInterface() || Modifier.isAbstract(rawType.getModifiers())) {
            // Only consider interfaces that the type implements: otherwise, for example,
            // `AbstractIntCollection` would be mapped to the first registered default
            // implementation, `IntRBTreeSet`, and so lose duplicates and ordering
            for (Kind<C> kind : _kinds) {
                if (kind.iface().isAssignableFrom(rawType)
                        && rawType.isAssignableFrom(kind.defaultImplType())) {
                    return new Creator<>(rawType, kind.defaultImpl()::get, null);
                }
            }
            return null;
        }
        // Wrappers are created using factory methods of their (public) enclosing
        // class, so wrapper classes themselves need not be public
        Creator<C> creator = _findWrapperCreator(rawType);
        if (creator == null && Modifier.isPublic(rawType.getModifiers())) {
            creator = _findDefaultConstructorCreator(rawType);
            if (creator == null) {
                creator = _findCopyConstructorCreator(rawType);
            }
        }
        return creator;
    }

    @SuppressWarnings("unchecked")
    private Creator<C> _findDefaultConstructorCreator(Class<?> rawType)
    {
        final Constructor<?> ctor;
        try {
            ctor = rawType.getConstructor();
        } catch (NoSuchMethodException e) {
            return null;
        }
        return new Creator<>(rawType, () -> (C) ctor.newInstance(), null);
    }

    @SuppressWarnings("unchecked")
    private Creator<C> _findWrapperCreator(Class<?> rawType)
    {
        Class<?> enclosing = rawType.getEnclosingClass();
        if (enclosing == null || !enclosing.getName().startsWith(FASTUTIL_PACKAGE_PREFIX)) {
            return null;
        }
        final String simpleName = rawType.getSimpleName();
        final String factoryName;
        if (simpleName.startsWith("Synchronized")) {
            factoryName = "synchronize";
        } else if (simpleName.startsWith("Unmodifiable")
                || simpleName.startsWith("Singleton")
                || simpleName.startsWith("Empty")) {
            factoryName = "unmodifiable";
        } else {
            return null;
        }
        for (Kind<C> kind : _kinds) {
            if (!kind.iface().isAssignableFrom(rawType)) {
                continue;
            }
            final Method factory;
            try {
                factory = enclosing.getMethod(factoryName, kind.iface());
            } catch (NoSuchMethodException e) {
                continue;
            }
            if (!Modifier.isStatic(factory.getModifiers())) {
                continue;
            }
            return new Creator<>(rawType, kind.orderedImpl()::get,
                    value -> (C) factory.invoke(null, value));
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    private Creator<C> _findCopyConstructorCreator(Class<?> rawType)
    {
        for (Kind<C> kind : _kinds) {
            if (!kind.iface().isAssignableFrom(rawType)) {
                continue;
            }
            Constructor<?> ctor;
            try {
                ctor = rawType.getConstructor(kind.iface());
            } catch (NoSuchMethodException e) {
                try {
                    ctor = rawType.getConstructor(_baseType);
                } catch (NoSuchMethodException e2) {
                    continue;
                }
            }
            final Constructor<?> copyCtor = ctor;
            return new Creator<>(rawType, kind.orderedImpl()::get,
                    value -> (C) copyCtor.newInstance(value));
        }
        return null;
    }

    private record Kind<C>(Class<?> iface, Class<?> defaultImplType,
            Supplier<? extends C> defaultImpl, Supplier<? extends C> orderedImpl)
    {
    }

    /**
     * Creates the intermediate container to populate, and converts the populated
     * container into the final value.
     *<p>
     * Failures (of constructors or factory methods called by reflection) are reported
     * the same way as for other types by {@code jackson-databind}, as
     * {@link tools.jackson.databind.exc.ValueInstantiationException}s.
     */
    public static final class Creator<C>
    {
        @FunctionalInterface
        interface Factory<C> {
            C create() throws ReflectiveOperationException;
        }

        @FunctionalInterface
        interface Finisher<C> {
            C finish(C intermediate) throws ReflectiveOperationException;
        }

        // Type requested, for error messages
        private final Class<?> _type;
        private final Factory<C> _intermediate;
        // null if the intermediate value is returned as-is
        private final Finisher<C> _finish;

        Creator(Class<?> type, Factory<C> intermediate, Finisher<C> finish) {
            _type = type;
            _intermediate = intermediate;
            _finish = finish;
        }

        /**
         * @param ctxt Context for reporting failures; may be {@code null} if not
         *    available (in which case location of the problem is not included)
         */
        public C create(DeserializationContext ctxt) throws JacksonException {
            try {
                return _intermediate.create();
            } catch (ReflectiveOperationException e) {
                throw _creationFailure(ctxt, e);
            }
        }

        /**
         * @param ctxt Context for reporting failures; may be {@code null} if not
         *    available (in which case location of the problem is not included)
         */
        public C finish(DeserializationContext ctxt, C intermediate) throws JacksonException {
            if (_finish == null) {
                return intermediate;
            }
            try {
                return _finish.finish(intermediate);
            } catch (ReflectiveOperationException e) {
                throw _creationFailure(ctxt, e);
            }
        }

        /**
         * @return New empty container (as used for "empty" values, like with
         *    {@code Nulls.AS_EMPTY})
         */
        public C createEmpty(DeserializationContext ctxt) throws JacksonException {
            return finish(ctxt, create(ctxt));
        }

        private DatabindException _creationFailure(DeserializationContext ctxt,
                ReflectiveOperationException e)
        {
            // Exception thrown by the constructor or factory method itself, if any
            Throwable cause = (e instanceof InvocationTargetException) ? e.getCause() : e;
            if (ctxt != null) {
                return ctxt.instantiationException(_type, cause);
            }
            return ValueInstantiationException.from((JsonParser) null,
                    String.format("Cannot construct instance of %s, problem: %s",
                            ClassUtil.nameOf(_type), ClassUtil.exceptionMessage(cause)),
                    TypeFactory.unsafeSimpleType(_type), cause);
        }
    }
}