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

com.maxifier.mxcache.transform.TransformGeneratorFactoryImpl Maven / Gradle / Ivy

/*
 * Copyright (c) 2008-2014 Maxifier Ltd. All Rights Reserved.
 */
package com.maxifier.mxcache.transform;

import com.maxifier.mxcache.CacheFactory;

import javax.annotation.Nonnull;

import javax.annotation.Nullable;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static com.maxifier.mxcache.transform.InvocationType.*;

/**
 * @author Alexander Kochurov ([email protected])
 */
@SuppressWarnings("deprecation")
public final class TransformGeneratorFactoryImpl implements TransformGeneratorFactory {
    private static final TransformGeneratorFactoryImpl INSTANCE = new TransformGeneratorFactoryImpl();

    private TransformGeneratorFactoryImpl() {}

    public static TransformGeneratorFactoryImpl getInstance() {
        return INSTANCE;
    }

    @Override
    @Nonnull
    public TransformGenerator forMethod(Method method) throws InvalidTransformAnnotations {
        try {
            Class[] params = method.getParameterTypes();
            switch (params.length) {
                case 0:
                    return TransformGenerator.NO_TRANSFORM;
                case 1:
                    return forArgument(method.getParameterAnnotations()[0], params[0]);
                default:
                    return createMultiParam(method.getParameterAnnotations(), params);
            }
        } catch (InvalidTransformAnnotations e) {
            throw new InvalidTransformAnnotations("Cannot create transform generator for " + method, e);
        }
    }

    private abstract static class TransformGeneratorRef {
        final A annotation;

        final Class paramType;

        abstract TransformGenerator get(Annotation[] allAnnotations);

        TransformGeneratorRef(A annotation, Class paramType) {
            this.annotation = annotation;
            this.paramType = paramType;
        }

        @Override
        public String toString() {
            return annotation.toString();
        }
    }

    private static class ConstTransformGeneratorRef extends TransformGeneratorRef {
        private final TransformGenerator generator;

        public ConstTransformGeneratorRef(T annotation, Class argType, TransformGenerator generator) {
            super(annotation, argType);
            this.generator = generator;
        }

        @Override
        TransformGenerator get(Annotation[] allAnnotations) {
            return generator;
        }
    }

    private class SimpleTransformGeneratorRef extends TransformGeneratorRef {
        SimpleTransformGeneratorRef(Transform annotation, Class paramType) {
            super(annotation, paramType);
        }

        @Override
        TransformGenerator get(Annotation[] allAnnotations) {
            return getTransformator(paramType, annotation);
        }
    }

    private final class IndirectTransformGeneratorRef extends TransformGeneratorRef {
        private final SimpleTransformGeneratorRef ref;

        IndirectTransformGeneratorRef(Annotation annotation, Class paramType, Transform transform) {
            super(annotation, paramType);
            this.ref = new SimpleTransformGeneratorRef(transform, paramType);
        }

        @Override
        TransformGenerator get(Annotation[] allAnnotations) {
            return ref.get(allAnnotations);
        }
    }

    private final class IndirectReversibleTransformGeneratorRef extends TransformGeneratorRef {
        private final ReversibleTransformGeneratorRef ref;

        IndirectReversibleTransformGeneratorRef(Annotation annotation, Class paramType, ReversibleTransform transform) {
            super(annotation, paramType);
            this.ref = new ReversibleTransformGeneratorRef(transform, paramType);
        }

        @Override
        TransformGenerator get(Annotation[] allAnnotations) {
            return ref.get(allAnnotations);
        }
    }

    private static final class CustomTranformGeneratorRef extends TransformGeneratorRef {
        private final CustomTransformAnnotation customTransform;

        CustomTranformGeneratorRef(Annotation annotation, Class paramType, CustomTransformAnnotation customTransform) {
            super(annotation, paramType);
            this.customTransform = customTransform;
        }

        @Override
        TransformGenerator get(Annotation[] allAnnotations) {
            Class generatorClass = customTransform.transformGenerator();
            //noinspection unchecked
            return CacheFactory.getDefaultContext().getInstanceProvider().forClass(generatorClass).create(customTransform, allAnnotations, paramType);
        }
    }

    private final class ReversibleTransformGeneratorRef extends TransformGeneratorRef {
        ReversibleTransformGeneratorRef(ReversibleTransform annotation, Class paramType) {
            super(annotation, paramType);
        }

        @Override
        TransformGenerator get(Annotation[] allAnnotations) {
            TransformGenerator forward = getTransformator(paramType, annotation.forward());
            TransformGenerator backward = getTransformator(forward.getTransformedType(paramType), annotation.backward());
            return new CompositeTransformGenerator(forward, backward);
        }
    }

    @Override
    @Nonnull
    public TransformGenerator forArgument(Annotation[] annotations, Class argType) throws InvalidTransformAnnotations {
        List refs = new ArrayList();
        for (Annotation annotation : annotations) {
            TransformGeneratorRef res = forAnnotation(argType, annotation);
            if (res != null) {
                refs.add(res);
            }
        }
        if (refs.isEmpty()) {
            return TransformGenerator.NO_TRANSFORM;
        }
        if (refs.size() > 1) {
            throw new InvalidTransformAnnotations("Too many transform annotations: " + refs);
        }
        return refs.get(0).get(annotations);
    }

    @Nullable
    private TransformGeneratorRef forAnnotation(Class argType, Annotation annotation) {
        if (annotation instanceof Ignore) {
            return new ConstTransformGeneratorRef((Ignore)annotation, argType, TransformGenerator.IGNORE_TRANSFORM);
        }
        if (annotation instanceof Transform) {
            return new SimpleTransformGeneratorRef((Transform) annotation, argType);
        }
        if (annotation instanceof ReversibleTransform) {
            return new ReversibleTransformGeneratorRef((ReversibleTransform) annotation, argType);
        }
        return forCustomTransformAnnotation(argType, annotation);
    }

    @Nullable
    private TransformGeneratorRef forCustomTransformAnnotation(Class argType, Annotation annotation) {
        CustomTransformAnnotation a = annotation.annotationType().getAnnotation(CustomTransformAnnotation.class);
        Transform t = annotation.annotationType().getAnnotation(Transform.class);
        ReversibleTransform r = annotation.annotationType().getAnnotation(ReversibleTransform.class);
        if (a != null) {
            if (t != null) {
                throw new InvalidTransformAnnotations(annotation.annotationType() + " has both @CustomTransformAnnotation and @Transform");
            }
            if (r != null) {
                throw new InvalidTransformAnnotations(annotation.annotationType() + " has both @CustomTransformAnnotation and @ReversibleTransform");
            }
            return new CustomTranformGeneratorRef(annotation, argType, a);
        }
        if (t != null) {
            if (r != null) {
                throw new InvalidTransformAnnotations(annotation.annotationType() + " has both @Transform and @ReversibleTransform");
            }
            return new IndirectTransformGeneratorRef(annotation, argType, t);
        }
        if (r != null) {
            return new IndirectReversibleTransformGeneratorRef(annotation, argType, r);
        }
        return null;
    }

    @Nonnull
    TransformGenerator createMultiParam(Annotation[][] paramAnnotations, Class[] params) throws InvalidTransformAnnotations {
        boolean onlyNoTransforms = true;
        TransformGenerator[] transformGenerators = new TransformGenerator[params.length];
        Class[] transformedParams = Arrays.copyOf(params, params.length);
        int outParams = 0;
        for (int i = 0; i < paramAnnotations.length; i++) {
            Annotation[] paramAnnotation = paramAnnotations[i];
            Class paramType = params[i];
            TransformGenerator transformGenerator = forArgument(paramAnnotation, paramType);
            if (transformGenerator == TransformGenerator.NO_TRANSFORM) {
                transformedParams[outParams++] = paramType;
            } else {
                onlyNoTransforms = false;
                transformGenerators[i] = transformGenerator;
                if (transformGenerator != TransformGenerator.IGNORE_TRANSFORM) {
                    transformedParams[outParams++] = transformGenerator.getTransformedType(paramType);
                }
            }
        }
        if (onlyNoTransforms) {
            return TransformGenerator.NO_TRANSFORM;
        }

        transformedParams = outParams != transformedParams.length ? Arrays.copyOf(transformedParams, outParams) : transformedParams;
        return new TupleTransformGenerator(transformGenerators, params, transformedParams);
    }

    @Override
    public TransformGenerator getTransformator(Class param, @Nonnull Transform key) {
        Class owner = key.owner();
        String name = key.method();
        return getTransformator(param, owner == Transform.KEY_ITSELF.class ? null : owner, name.equals(Transform.ONLY_PUBLIC_METHOD) ? null : name);
    }

    /**
     * Note: params cannot be non-null while name is null.
     * @param paramType type of parameter to convert
     * @param owner owner of converter method; if null, method is looked up in key itself
     * @param name name of method; if null, the only public method of transformator is used
     * @return transform generator which converts parameter of type paramType with method
     * owner.name(params)
     */
    @Override
    public TransformGenerator getTransformator(Class paramType, Class owner, String name) {
        boolean keyInstanceMethods = owner == null;
        if (keyInstanceMethods) {
            owner = paramType;
        }
        Method method = findMethod(owner, name, paramType, keyInstanceMethods);
        InvocationType invocationType = getKeyInvocationType(owner, method, keyInstanceMethods);
        return new ExternalTransformGenerator(invocationType, owner, method);
    }

    private InvocationType getKeyInvocationType(Class owner, Method forwardMethod, boolean keyInstanceMethods) {
        switch (forwardMethod.getParameterTypes().length) {
            case 0:
                return getZeroArgKeyInvocationType(owner, forwardMethod, keyInstanceMethods);
            case 1:
                return getSingleArgKeyInvocationType(owner, forwardMethod, keyInstanceMethods);
            default:
                throw new IllegalArgumentException("Invalid transform found: " + forwardMethod);
        }
    }

    private InvocationType getZeroArgKeyInvocationType(Class owner, Method forwardMethod, boolean keyInstanceMethods) {
        if (!keyInstanceMethods) {
            throw new IllegalArgumentException("Invalid transform found: " + forwardMethod);
        }
        return owner.isInterface() ? KEY_INTERFACE : KEY_VIRTUAL;
    }

    private InvocationType getSingleArgKeyInvocationType(Class owner, Method forwardMethod, boolean keyInstanceMethods) {
        if (Modifier.isStatic(forwardMethod.getModifiers())) {
            return STATIC;
        }
        if (keyInstanceMethods) {
            // Key non-static methods are not evaluated
            throw new IllegalArgumentException("Invalid transform found: " + forwardMethod);
        }
        return owner.isInterface() ? INTERFACE : VIRTUAL;
    }

    static Method findMethod(Class owner, String name, Class paramType, boolean keyInstanceMethods) {
        Method[] methods = owner.getMethods();
        List suitable = getSuitableMethods(name, paramType, keyInstanceMethods, methods);
        if (suitable.isEmpty()) {
            String ref = owner + "." + (name == null ? "" : name);
            throw new IllegalArgumentException("No such public method " + ref + "(? super " + paramType.getCanonicalName() + (keyInstanceMethods ? ") or " + ref + "()" : ")"));
        }
        if (suitable.size() > 1) {
            String ref = owner + "." + (name == null ? "" : name);
            throw new IllegalArgumentException("Too many public methods " + ref + "(? super " + paramType.getCanonicalName() + (keyInstanceMethods ? ") or " + ref + "(): " : "): ") + suitable);
        }
        return suitable.get(0);
    }

    private static List getSuitableMethods(String name, Class paramType, boolean keyInstanceMethods, Method[] methods) {
        List suitable = new ArrayList();
        for (Method method : methods) {
            if (nameMatches(name, method) && isParamsSuites(paramType, keyInstanceMethods, method)) {
                suitable.add(method);
            }
        }
        return suitable;
    }

    private static boolean isParamsSuites(Class paramType, boolean keyInstanceMethods, Method method) {
        Class[] params = method.getParameterTypes();
        switch (params.length) {
            case 0:
                return keyInstanceMethods && !Modifier.isStatic(method.getModifiers());
            case 1:
                // only zero-arg key methods may be transformer
                return (!keyInstanceMethods || Modifier.isStatic(method.getModifiers())) && params[0].isAssignableFrom(paramType);
            default:
                // transformer method can't have more than 1 argument
                return false;
        }
    }

    private static boolean nameMatches(String name, Method method) {
        return name == null ? !isObjectMethod(method) : method.getName().equals(name);
    }

    private static boolean isObjectMethod(Method method) {
        String name = method.getName();
        Class[] parameters = method.getParameterTypes();
        for (Method o : Object.class.getMethods()) {
            if (name.equals(o.getName()) && Arrays.equals(parameters, o.getParameterTypes())) {
                return true;
            }
        }
        return false;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy