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

io.permazen.FollowPathScanner Maven / Gradle / Ivy

There is a newer version: 5.1.0
Show newest version

/*
 * Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
 */

package io.permazen;

import com.google.common.reflect.TypeParameter;
import com.google.common.reflect.TypeToken;

import io.permazen.annotation.FollowPath;
import io.permazen.annotation.JSetField;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.NavigableSet;
import java.util.Optional;

/**
 * Scans for {@link FollowPath @FollowPath} annotations.
 */
class FollowPathScanner extends AnnotationScanner {

    FollowPathScanner(JClass jclass) {
        super(jclass, FollowPath.class);
    }

    @Override
    protected boolean includeMethod(Method method, FollowPath followPath) {
        this.checkNotStatic(method);
        this.checkParameterTypes(method);
        this.checkReturnType(method, followPath.firstOnly() ? Optional.class : NavigableSet.class);
        return true;
    }

    @Override
    protected FollowPathMethodInfo createMethodInfo(Method method, FollowPath annotation) {
        return new FollowPathMethodInfo(method, annotation);
    }

    // This method exists solely to bind the generic type parameters
    @SuppressWarnings("serial")
    private static  TypeToken> buildNavigableSetType(Class elementType) {
        return new TypeToken>() { }.where(new TypeParameter() { }, elementType);
    }

    // This method exists solely to bind the generic type parameters
    @SuppressWarnings("serial")
    private static  TypeToken> buildOptionalType(Class elementType) {
        return new TypeToken>() { }.where(new TypeParameter() { }, elementType);
    }

// FollowPathMethodInfo

    class FollowPathMethodInfo extends MethodInfo {

        private final ReferencePath path;
        private final boolean inverse;

        FollowPathMethodInfo(Method method, FollowPath followPath) {
            super(method, followPath);

            // Check for conflict with method having both @JSetField and @FollowPath
            if (Util.getAnnotation(method, JSetField.class) != null) {
                throw new IllegalArgumentException(FollowPathScanner.this.getErrorPrefix(method)
                  + "method has conflicting annotations with both @" + JSetField.class.getSimpleName()
                  + " and @" + FollowPath.class.getSimpleName());
            }

            // Parse reference path
            final Class modelType = FollowPathScanner.this.jclass.type;
            this.inverse = followPath.startingFrom() != void.class;
            try {

                // Check for annotation property conflict
                if ((!this.inverse && (followPath.value().equals("") || !followPath.inverseOf().equals("")))
                  || (this.inverse && (!followPath.value().equals("") || followPath.inverseOf().equals("")))) {
                    throw new IllegalArgumentException(
                      "invalid property combination: either value() or both startingFrom() and inverseOf() should be specified");
                }

                // Parse reference path
                this.path = FollowPathScanner.this.jclass.jdb.parseReferencePath(
                  this.inverse ? followPath.startingFrom() : modelType,
                  this.inverse ? followPath.inverseOf() : followPath.value(), false);
            } catch (IllegalArgumentException e) {
                throw new IllegalArgumentException(FollowPathScanner.this.getErrorPrefix(method)
                  + "invalid reference path: " + e.getMessage(), e);
            }

            // Check method return type: element type should be a super-type of all possible target types
            if (this.inverse) {

                // Check return type
                final TypeToken expectedType = followPath.firstOnly() ?
                  FollowPathScanner.buildOptionalType(followPath.startingFrom()) :
                  FollowPathScanner.buildNavigableSetType(followPath.startingFrom());
                FollowPathScanner.this.checkReturnType(method, Arrays.asList(expectedType));

                // Check reference path can possibly end in 'this'
                boolean matched = false;
                for (Class targetType : this.path.getTargetTypes()) {
                    if (targetType.isAssignableFrom(modelType)) {
                        matched = true;
                        break;
                    }
                }
                if (!matched) {
                    throw new IllegalArgumentException(FollowPathScanner.this.getErrorPrefix(method)
                      + "inverted reference path can never terminate in an instance of " + modelType);
                }
            } else {

                // Check return type
                final TypeToken returnType = TypeToken.of(method.getGenericReturnType());
                final TypeToken returnElementType =
                  returnType.resolveType((followPath.firstOnly() ? Optional.class : NavigableSet.class).getTypeParameters()[0]);
                for (Class actualElementType : this.path.getTargetTypes()) {
                    if (!returnElementType.isSupertypeOf(actualElementType)) {
                        throw new IllegalArgumentException(FollowPathScanner.this.getErrorPrefix(method)
                          + "return type element type " + returnElementType + " is not compatible with " + actualElementType);
                    }
                }
            }
        }

        public ReferencePath getReferencePath() {
            return path;
        }

        public boolean isInverse() {
            return this.inverse;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == this)
                return true;
            if (!super.equals(obj))
                return false;
            final FollowPathScanner.FollowPathMethodInfo that = (FollowPathScanner.FollowPathMethodInfo)obj;
            return this.path.equals(that.path) && this.inverse == that.inverse;
        }

        @Override
        public int hashCode() {
            return super.hashCode() ^ this.path.hashCode() ^ Boolean.hashCode(this.inverse);
        }
    }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy