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

io.virtdata.reflection.ConstructorResolver Maven / Gradle / Ivy

There is a newer version: 2.12.15
Show newest version
package io.virtdata.reflection;

import io.virtdata.util.StringObjectPromoter;
import org.apache.commons.lang3.reflect.ConstructorUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Constructor;
import java.math.BigDecimal;
import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * Match (class,string,...) constructor signatures to a matching converted signature,
 * in Object[] form.
 */
public class ConstructorResolver {
    private final static Logger logger = LoggerFactory.getLogger(ConstructorResolver.class);

    private Exception lastException;

    public static  Optional resolveAndConstructOptional(String[] classAndArgs) {
        Optional> optionalDeferredConstructor =
                createOptionalDeferredConstructor(classAndArgs);
        return optionalDeferredConstructor.map(DeferredConstructor::construct);
    }


    public static  T resolveAndConstruct(String[] classAndArgs) {
        DeferredConstructor deferredConstructor = createDeferredConstructorRequired(classAndArgs);
        T constructed = deferredConstructor.construct();
        return constructed;
    }

    public static  DeferredConstructor resolve(String[] classAndArgs) {
        DeferredConstructor deferredConstructor = createDeferredConstructorRequired(classAndArgs);
        return deferredConstructor;
    }

    public static  DeferredConstructor resolve(Class clazz, String... args) {
        DeferredConstructor deferredConstructor = createDeferredConstructorRequired(clazz, args);
        return deferredConstructor;
    }

    public static  Optional> resolveOptional(Class clazz, String... args) {
        Optional> optionalResolved = createOptionalDeferredConstructor(clazz, args);
        return optionalResolved;
    }

    @SuppressWarnings("unchecked")
    public static  DeferredConstructor resolve(String className, String[] args) {
        Class clazz = null;
        try {
            clazz = (Class) Class.forName(className);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        DeferredConstructor deferredConstructor = createDeferredConstructorRequired(clazz, args);
        return deferredConstructor;
    }

    @SuppressWarnings("unchecked")
    private static  DeferredConstructor createDeferredConstructorRequired(String[] signature) {
        String className = signature[0];

        if (!className.contains(".")) {
            throw new RuntimeException(ConstructorResolver.class.getSimpleName() + " needs a fully qualified package name.");
        }
        Class clazz;
        try {
            clazz = (Class) Class.forName(className);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }

        return createDeferredConstructorRequired(clazz, Arrays.copyOfRange(signature, 1, signature.length));
    }

    private static Object[] specializeArgs(String[] raw, Class[] targetTypes) {
        Object[] made = new Object[raw.length];
        for (int paramidx = 0; paramidx < targetTypes.length; paramidx++) {
            Class ptype = targetTypes[paramidx];
            made[paramidx] = StringObjectPromoter.promote(raw[paramidx], ptype);
            if (!StringObjectPromoter.isAssignableForConstructor(made[paramidx].getClass(), ptype)) {
                return null;
            }
        }
        return made;
    }

    private static Predicate canAssignToConstructor(String[] args) {
        return new Predicate() {
            @Override
            public boolean test(Constructor ctor) {
                Object[] objects = specializeArgs(args, ctor.getParameterTypes());
                return objects != null;
            }
        };
    }

    private static  Optional>
    createOptionalDeferredConstructor(String... classAndArgs) {
        Class ctorClass = null;
        try {
            ctorClass = (Class) Class.forName(classAndArgs[0]);
        } catch (ClassNotFoundException ignored) {
        }
        if (ctorClass != null) {
            Optional> odc =
                    createOptionalDeferredConstructor(
                            ctorClass,
                            Arrays.copyOfRange(classAndArgs, 1, classAndArgs.length)
                    );
            return odc;
        }
        return Optional.empty();

    }

    private static  Optional> createOptionalDeferredConstructor(
            Class clazz, String... args) {

        List matchingConstructors = new ArrayList<>();

        for (Constructor constructor : clazz.getDeclaredConstructors()) {
            if (constructor.getParameterCount() == args.length) {
                matchingConstructors.add(constructor);
            }
        }

        matchingConstructors = matchingConstructors.stream()
                .filter(canAssignToConstructor(args)).collect(Collectors.toList());

        if (matchingConstructors.size() == 0) {
            logger.debug("no constructor found for " + clazz.getSimpleName() + " with " +
                    (args.length) + " parameters");
            return Optional.empty();
        }

        if (matchingConstructors.size() > 1) {
            List signatures = new ArrayList();
            for (Constructor matchingConstructor : matchingConstructors) {
                Class[] pt = matchingConstructor.getParameterTypes();
                signatures.add(
                        Arrays.stream(pt)
                                .map(Class::getSimpleName)
                                .collect(Collectors.joining(",", "(", ")"))
                );
            }
            String diagnosticList = signatures.stream().collect(Collectors.joining(",", "[", "]"));

            logger.error("Multiple constructors found for " + clazz.getSimpleName() + " with " +
                    (args.length) + " parameters:" + diagnosticList
            );
            return Optional.empty();
        }

        Constructor matchingConstructor = matchingConstructors.get(0);
        Object[] ctorArgs = specializeArgs(args, matchingConstructor.getParameterTypes());

        // sanity check
        // TODO: Reduce additional constructor invocations where possible
        try {
            ConstructorUtils.invokeConstructor(clazz, ctorArgs);
        } catch (Exception e) {
            logger.error("Unable to invoke constructor as sanity check for args:" + Arrays.toString(ctorArgs), e);
            return Optional.empty();
        }

        DeferredConstructor dc = new DeferredConstructor<>(clazz, ctorArgs);
        return Optional.of(dc);

    }

    private static  DeferredConstructor createDeferredConstructorRequired(Class clazz, String... args) {
        Optional> optional =
                createOptionalDeferredConstructor(clazz, args);
        return optional.orElseThrow(
                () -> new RuntimeException(
                        "Unable to create deferred constructor for class:"
                                + clazz.getCanonicalName() + " and args: "
                                + Arrays.toString(args))
        );
    }

    private static enum StringMapper {

        // HINT: Do NOT put more than primitives or very common types here

        STRING(String.class, null, (String i) -> i),
        INTEGER(Integer.class, int.class, Integer::valueOf),
        BIGDECIMAL(BigDecimal.class, null, (String i) -> BigDecimal.valueOf(Long.valueOf(i))),
        BOOLEAN(Boolean.class, boolean.class, Boolean::valueOf),
        SHORT(Short.class, short.class, Short::valueOf),
        BYTE(Byte.class, byte.class, Byte::valueOf),
        DOUBLE(Double.class, double.class, Double::valueOf),
        CHAR(Character.class, char.class, (String c) -> c.charAt(0)),
        FLOAT(Float.class, float.class, Float::valueOf),
        LONG(Long.class, long.class, Long::valueOf);

        private final Class targetClass;
        private final Class primitiveClass;
        private final Function mapperFunction;

         StringMapper(Class targetClass, Class primitiveName, Function mapperFunction) {
            this.targetClass = targetClass;
            this.mapperFunction = mapperFunction;
            this.primitiveClass = primitiveName;
        }

        public static Optional valueOf(Class targetClass) {
            for (StringMapper stringMapper : StringMapper.values()) {
                if (stringMapper.getTargetClass().equals(targetClass)) {
                    return Optional.of(stringMapper);
                }
                if (stringMapper.primitiveClass != null && stringMapper.primitiveClass.equals(targetClass)) {
                    return Optional.of(stringMapper);
                }
            }
            throw new RuntimeException("StringMapper could not match " + targetClass);
        }

        public static Object mapValue(String value, Class targetClass) {
            Optional mapper = StringMapper.valueOf(targetClass);
            if (mapper.isPresent()) {
                return mapper.get().getMapperFunction().apply(value);
            } else {
                throw new RuntimeException(
                        "Unable to find type mapper for String and class " + targetClass.getCanonicalName()
                );
            }

        }

        public Class getTargetClass() {
            return targetClass;
        }

        public Function getMapperFunction() {
            return mapperFunction;
        }


    }


}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy