com.link_intersystems.lang.reflect.criteria.MemberCriteria.java Source code

Java tutorial

Introduction

Here is the source code for com.link_intersystems.lang.reflect.criteria.MemberCriteria.java

Source

/**
 * Copyright 2011 Link Intersystems GmbH <rene.link@link-intersystems.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.link_intersystems.lang.reflect.criteria;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;

import org.apache.commons.collections4.IteratorUtils;
import org.apache.commons.collections4.Predicate;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.AllPredicate;
import org.apache.commons.collections4.functors.NotPredicate;
import org.apache.commons.collections4.functors.OrPredicate;

import com.link_intersystems.lang.Assert;
import com.link_intersystems.lang.reflect.AccessType;
import com.link_intersystems.lang.reflect.MemberModifierPredicate;
import com.link_intersystems.lang.reflect.MemberModifierPredicate.Match;
import com.link_intersystems.lang.reflect.ReflectFacade;
import com.link_intersystems.util.ObjectFactory;
import com.link_intersystems.util.SerializableTemplateObjectFactory;

/**
 * Iterate over {@link Member}s or {@link AnnotatedElement}s that match the
 * criteria specified by a {@link MemberCriteria}.
 * <p>
 * <h2>Examples</h2>
 * <h3>Iterate over all public abstract methods, in method names order, whose
 * name's match the pattern &quot;add.*&quot; starting at class ArrayList.</h3>
 *
 * <pre>
 * MemberCriteria memberCriteria = new MemberCriteria();
 * memberCriteria.withAccess(AccessType.PUBLIC);
 * memberCriteria.withModifiers(Modifier.ABSTRACT);
 * memberCriteria.membersOfType(Method.class);
 * memberCriteria.setMemberIterateOrder(MemberNameComparator.INSTANCE);
 * memberCriteria.named(Pattern.compile(&quot;add.*&quot;));
 *
 * ClassCriteria classCriteria = new ClassCriteria();
 * Iterable&lt;Class&lt;?&gt;&gt; classIterable = classCriteria.getIterable(ArrayList.class);
 * Iterable&lt;Member&gt; memberIterable = memberCriteria.getIterable(classIterable);
 *
 * Iterator&lt;Member&gt; criteriaIterator = memberIterable.iterator();
 * </pre>
 *
 * <h3>Get the first public method that has the same signature as
 * List.subList(int, int) starting at ArrayList traversing only the class
 * hierarchy (omit interfaces).</h3>
 *
 * <pre>
 * MemberCriteria memberCriteria = new MemberCriteria();
 * memberCriteria.setResult(Selection.FIRST);
 * memberCriteria.membersOfType(Method.class);
 *
 * Method method = List.class.getDeclaredMethod(&quot;subList&quot;, int.class, int.class);
 *
 * memberCriteria.add(new SignaturePredicate(method));
 *
 * ClassCriteria classCriteria = new ClassCriteria();
 * classCriteria.setTraverseOrder(TraverseOrder.SUPERCLASSES_ONLY);
 * Iterable&lt;Class&lt;?&gt;&gt; classIterable = classCriteria.getIterable(ArrayList.class);
 * Iterable&lt;Member&gt; memberIterable = memberCriteria.getIterable(classIterable);
 *
 * Iterator&lt;Member&gt; criteriaIterator = memberIterable.iterator();
 * Member member = criteriaIterator.next();
 * Method firstMatch = (Method) member;
 * Method declaredMethod = AbstractList.class.getDeclaredMethod(&quot;subList&quot;,
 *       int.class, int.class);
 * assertEquals(declaredMethod, firstMatch);
 * assertFalse(criteriaIterator.hasNext());
 * </pre>
 *
 * </p>
 *
 * @author Ren Link <a
 *         href="mailto:rene.link@link-intersystems.com">[rene.link@link-
 *         intersystems.com]</a>
 * @since 1.0.0.0
 */
public class MemberCriteria<T extends Member> extends ElementCriteria<T> {

    /**
      *
      */
    private static final long serialVersionUID = 4870605392298539751L;

    public static final List<Class<?>> DEFAULT_MEMBER_TYPES;

    static {
        List<Class<?>> defaultMemberTypes = new ArrayList<Class<?>>();
        defaultMemberTypes.add(Constructor.class);
        defaultMemberTypes.add(Method.class);
        defaultMemberTypes.add(Field.class);
        DEFAULT_MEMBER_TYPES = Collections.unmodifiableList(defaultMemberTypes);
    }

    private List<Class<?>> memberTypes = DEFAULT_MEMBER_TYPES;

    private int modifiers = 0;

    private Pattern pattern;

    public MemberCriteria() {

    }

    public static MemberCriteria<? extends Member> forMemberTypes(Class<?>... memberTypes) {
        MemberCriteria<Member> memberCriteria = new MemberCriteria<Member>();
        memberCriteria.membersOfType(memberTypes);
        return memberCriteria;
    }

    /**
     * A non empty collection of {@link AccessType}s that this criteria should
     * match with.
     */
    private Collection<AccessType> accesses = Arrays.asList(AccessType.values());

    private String name;
    private Comparator<Member> iterateOrderComparator = ReflectFacade.getMemberNameComparator();

    /**
     * The {@link IterateStrategy} determines in which order
     * {@link AnnotatedElement}s are iterated when using an {@link Iterable} of
     * {@link AnnotatedElement}s.
     *
     * @author Ren Link <a
     *         href="mailto:rene.link@link-intersystems.com">[rene.link@link-
     *         intersystems.com]</a>
     * @since 1.0.0.0
     */
    public enum IterateStrategy {
        /**
         * Iterate only elements that are {@link Member}s.
         *
         * @since 1.0.0.0
         */
        MEMBERS_ONLY(JavaElementTraverseStrategies.MEMER_TRAVERSE_STRATEGY),

        /**
         * Iterate only elements that are {@link Class}es.
         *
         * @since 1.0.0.0
         */
        CLASSES_ONLY(JavaElementTraverseStrategies.CURRENT_CLASS_TRAVERSE_STRATEGY),
        /**
         * Iterate only elements that are {@link Package}s.
         *
         * @since 1.0.0.0
         */
        PACKAGES_ONLY(JavaElementTraverseStrategies.PACKAGE_TRAVERSE_STRATEGY),

        /**
         * Iterate elements that are {@link Class}es or {@link Member}s in that
         * order.
         *
         * @since 1.0.0.0
         */
        CLASS_MEMBERS(JavaElementTraverseStrategies.CURRENT_CLASS_TRAVERSE_STRATEGY,
                JavaElementTraverseStrategies.MEMER_TRAVERSE_STRATEGY),
        /**
         * Iterate elements that are {@link Member}s or {@link Class}es in that
         * order.
         *
         * @since 1.0.0.0
         */
        MEMBERS_CLASS(JavaElementTraverseStrategies.MEMER_TRAVERSE_STRATEGY,
                JavaElementTraverseStrategies.CURRENT_CLASS_TRAVERSE_STRATEGY),
        /**
         * Iterate elements that are {@link Package}s, {@link Class}es or
         * {@link Member}s in that order.
         *
         * @since 1.0.0.0
         */
        PACKAGE_CLASS_MEMBERS(JavaElementTraverseStrategies.PACKAGE_TRAVERSE_STRATEGY,
                JavaElementTraverseStrategies.CURRENT_CLASS_TRAVERSE_STRATEGY,
                JavaElementTraverseStrategies.MEMER_TRAVERSE_STRATEGY),
        /**
         * Iterate elements that are {@link Member}s, {@link Class}es or
         * {@link Package}s in that order.
         *
         * @since 1.0.0.0
         */
        MEMBERS_CLASS_PACKAGE(JavaElementTraverseStrategies.MEMER_TRAVERSE_STRATEGY,
                JavaElementTraverseStrategies.CURRENT_CLASS_TRAVERSE_STRATEGY,
                JavaElementTraverseStrategies.PACKAGE_TRAVERSE_STRATEGY);

        private final JavaElementTraverseStrategy javaElementTraverseStrategy;

        private IterateStrategy(JavaElementTraverseStrategy... javaElementTraverseStrategies) {
            if (javaElementTraverseStrategies.length == 1) {
                this.javaElementTraverseStrategy = javaElementTraverseStrategies[0];
            } else {
                this.javaElementTraverseStrategy = new CompositeJavaElementTraverseStrategy(
                        javaElementTraverseStrategies);
            }
        }

        private JavaElementTraverseStrategy getJavaElementTraverseStrategy() {
            return javaElementTraverseStrategy;
        }

    }

    /**
     * Set the order in which {@link Member} are iterated. The order is defined
     * by the {@link Comparator}. The {@link Member} order is determined per
     * declaring class. Therefore the member iterate order only determines how
     * {@link Member}s of the same declaring class are iterated. So it does NOT
     * affect the order of {@link Member}s of different classes (global order).
     *
     * @param iterateOrderComparator
     * @since 1.0.0.0
     */
    public void setMemberIterateOrder(Comparator<Member> iterateOrderComparator) {
        Assert.notNull("iterateOrderComparator", iterateOrderComparator);
        this.iterateOrderComparator = iterateOrderComparator;
    }

    /**
     * {@inheritDoc}
     * <p>
     * The {@link Predicate} must handle {@link Method} objects in it's
     * {@link Predicate#evaluate(Object)} method.
     * </p>
     *
     * @since 1.0.0.0
     */
    @Override
    public void add(Predicate<T> predicate) {
        /*
         * Just override the method to provide more specific javadoc
         */
        super.add(predicate);
    }

    /**
     * Add a criterion to only select {@link Member}s of the specified types.
     * Allowed types are
     * <ul>
     * <li> {@link Constructor}</li>
     * <li> {@link Method}</li>
     * <li> {@link Field}</li>
     * </ul>
     *
     * @param memberTypes
     *            the member types that this {@link MemberCriteria} should
     *            select.
     * @since 1.0.0.0
     */
    public final void membersOfType(Class<?>... memberTypes) {
        /*
         * we do not enforce member types by the generic definition
         * "Class<? extends Member> ... memberTypes" because this will cause
         * type unsafe warnings at the client side. For example if a client
         * calls this method with the parameters
         * "membersOfType(Field.class, Method.class)".
         */
        for (Class<?> memberType : memberTypes) {
            if (!(Constructor.class.equals(memberType)) && !(Method.class.equals(memberType))
                    && !(Field.class.equals(memberType))) {
                throw new IllegalArgumentException("Unsupported member type " + memberType
                        + ". Supported types are " + DEFAULT_MEMBER_TYPES + ".");
            }
        }
        this.memberTypes = Arrays.asList(memberTypes);
    }

    protected List<Member> getSortedMembers(Class<?> currentClass) {
        List<Member> memberList = new ArrayList<Member>();
        for (Class<?> memberType : memberTypes) {
            Member[] members = null;
            if (Constructor.class.equals(memberType)) {
                members = currentClass.getDeclaredConstructors();
            } else if (Method.class.equals(memberType)) {
                members = currentClass.getDeclaredMethods();
            } else if (Field.class.equals(memberType)) {
                members = currentClass.getDeclaredFields();
            }
            if (members != null) {
                List<Member> asList = Arrays.asList(members);
                memberList.addAll(asList);
            }
        }

        Collections.sort(memberList, iterateOrderComparator);
        return memberList;
    }

    /**
     * Set the criterion that selects only {@link Member}s that have one of the
     * defined {@link AccessType}s.
     *
     * @param accesses
     *            the {@link AccessType}s that this criteria should match with.
     *            Must contain at least one element.
     * @since 1.0.0.0
     */
    public void withAccess(AccessType... accesses) {
        Assert.notNull("accesses", accesses);
        if (accesses.length == 0) {
            throw new IllegalArgumentException("accesses must contain at least 1 access type");
        }
        this.accesses = Arrays.asList(accesses);
    }

    /**
     * Set the criterion that selects only {@link Member}s that exactly have the
     * specified modifiers. The access modifiers are set separately. Use
     * {@link #withAccess(AccessType...)} to set access modifiers.
     *
     * @param modifiers
     * @since 1.0.0.0
     */
    public void withModifiers(int modifiers) {
        if (Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers) || Modifier.isPrivate(modifiers)) {
            throw new IllegalArgumentException(
                    "access modifiers are not allowed as argument. Use withAccess() instead.");
        }
        int allowedModifiers = Modifier.ABSTRACT | Modifier.STATIC | Modifier.FINAL | Modifier.TRANSIENT
                | Modifier.VOLATILE | Modifier.SYNCHRONIZED | Modifier.NATIVE | Modifier.STRICT
                | Modifier.INTERFACE;

        if ((modifiers & allowedModifiers) == 0) {
            throw new IllegalArgumentException(
                    "modifiers must be one of [" + Modifier.toString(allowedModifiers) + "]");
        }

        this.modifiers = modifiers;
    }

    /**
     * Set the criterion that selects only {@link Member}s whose name matches
     * the specified pattern. If the pattern is null the criterion will be
     * removed.
     *
     * @param pattern
     * @since 1.0.0.0
     */
    public void named(Pattern pattern) {
        this.pattern = pattern;
    }

    /**
     * Set the criterion that selects only {@link Member}s whose name matches
     * the specified pattern. If the pattern is null the criterion will be
     * removed.
     *
     * @param pattern
     * @since 1.0.0.0
     */
    public void named(String name) {
        this.name = name;
    }

    /**
     *
     * @param classIterable
     *            the class {@link Iterable} that defines the classes that are
     *            iterated to look for members that match this
     *            {@link MemberCriteria}.
     * @return an {@link Iterable} that creates {@link Iterator}s that iterate
     *         through {@link AnnotatedElement}s as defined by this
     *         {@link MemberCriteria} using the default {@link IterateStrategy}
     *         ( {@link IterateStrategy#MEMBERS_CLASS_PACKAGE} ).
     * @since 1.0.0.0
     */
    public Iterable<? extends AnnotatedElement> getAnnotatedElementIterable(Iterable<Class<?>> classIterable) {
        Iterable<? extends AnnotatedElement> annotatedElementIterable = getAnnotatedElementIterable(classIterable,
                IterateStrategy.MEMBERS_CLASS_PACKAGE);
        return annotatedElementIterable;
    }

    /**
     *
     * @param classIterable
     *            the class {@link Iterable} that defines the classes that are
     *            iterated to look for members that match this
     *            {@link MemberCriteria}.
     * @param iterateStrategy
     *            the order in which the {@link AnnotatedElement} types selected
     *            by this {@link MemberCriteria} are iterated through. In
     *            contrast to the {@link #getIterable(Iterable)} method the
     *            {@link IterateStrategy} allows you to iterate also through
     *            elements that the selected {@link Member}s are declared on.
     * @return an {@link Iterable} that creates {@link Iterator}s that iterate
     *         through {@link AnnotatedElement}s as defined by this
     *         {@link MemberCriteria} using the given iterate strategy.
     * @since 1.0.0.0
     */
    public Iterable<? extends AnnotatedElement> getAnnotatedElementIterable(Iterable<Class<?>> classIterable,
            IterateStrategy iterateStrategy) {
        AnnotatedElementIterable annotatedElementIterable = new AnnotatedElementIterable(classIterable,
                iterateStrategy, getObjectFactory());
        return annotatedElementIterable;
    }

    /**
     * @param classIterable
     *            the class {@link Iterable} that defines the classes that are
     *            iterated to look for members that match this
     *            {@link MemberCriteria}
     * @return an {@link Iterable} that creates {@link Iterator}s that iterate
     *         through {@link Member}s as defined by this {@link MemberCriteria}
     *         .
     * @since 1.0.0.0
     */
    public Iterable<Member> getIterable(Iterable<Class<?>> classIterable) {
        return new MemberIterableWithClassIterable(classIterable, getObjectFactory());
    }

    @Override
    protected Iterator<T> applyElementFilter(Iterator<T> iterator) {
        iterator = super.applyElementFilter(iterator);
        iterator = applyAccessAndNamePredicates(iterator);
        return iterator;
    }

    @SuppressWarnings("unchecked")
    protected Iterator<T> applyAccessAndNamePredicates(Iterator<T> iterator) {
        List<Predicate<?>> predicates = new ArrayList<Predicate<?>>();

        int accessModifiers = 0;
        Collection<AccessType> accesses = getAccesses();
        for (AccessType access : accesses) {
            switch (access) {
            case PUBLIC:
                accessModifiers |= Modifier.PUBLIC;
                break;
            case PRIVATE:
                accessModifiers |= Modifier.PRIVATE;
                break;
            case PROTECTED:
                accessModifiers |= Modifier.PROTECTED;
                break;
            default:
                break;
            }
        }
        Predicate<Member> accessModifierPredicate = new MemberModifierPredicate(accessModifiers,
                Match.AT_LEAST_ONE);

        if (accesses.contains(AccessType.DEFAULT)) {
            accessModifierPredicate = OrPredicate.orPredicate(accessModifierPredicate,
                    NotPredicate.notPredicate(new MemberModifierPredicate(
                            Modifier.PRIVATE | Modifier.PUBLIC | Modifier.PROTECTED, Match.AT_LEAST_ONE)));
        }

        predicates.add(accessModifierPredicate);

        int modifiers = getModifiers();
        if (modifiers != 0) {
            predicates.add(new MemberModifierPredicate(modifiers, Match.AT_LEAST_ALL));
        }

        String name = getName();
        if (name != null) {
            predicates.add(ReflectFacade.getMemberNamePredicate(name));
        }

        Pattern pattern = getPattern();
        if (pattern != null) {
            predicates.add(ReflectFacade.getMemberNamePatternPredicate(pattern));
        }
        Predicate<T> allPredicate = AllPredicate.allPredicate((Collection<? extends Predicate<T>>) predicates);
        iterator = IteratorUtils.filteredIterator(iterator, allPredicate);
        return iterator;
    }

    /**
     *
     * @return a non empty list of {@link AccessType}s.
     */
    protected Collection<AccessType> getAccesses() {
        return accesses;
    }

    protected int getModifiers() {
        return modifiers;
    }

    protected String getName() {
        return name;
    }

    protected Pattern getPattern() {
        return pattern;
    }

    protected List<Class<?>> getMemberTypes() {
        return memberTypes;
    }

    private abstract static class AbstractCriteriaTransformer implements Transformer<Object, Object> {

        private final MemberCriteria<Member> memberCriteria;

        public AbstractCriteriaTransformer(MemberCriteria<Member> memberCriteria) {
            this.memberCriteria = memberCriteria;
        }

        protected MemberCriteria<Member> getMemberCriteria() {
            return memberCriteria;
        }

    }

    private static class MemberIteratorTransformer extends AbstractCriteriaTransformer {

        public MemberIteratorTransformer(MemberCriteria<Member> memberCriteria) {
            super(memberCriteria);
        }

        public Object transform(Object input) {
            if (input instanceof Class<?>) {
                final Class<?> currentClass = (Class<?>) input;
                List<Member> memberList = getMemberCriteria().getSortedMembers(currentClass);
                return memberList.iterator();
            }
            return input;
        }

    }

    private static class MemberIterableWithClassIterable implements Iterable<Member> {

        private final Iterable<Class<?>> classIterable;
        private final ObjectFactory<MemberCriteria<Member>> templateObjectFactory;

        public MemberIterableWithClassIterable(Iterable<Class<?>> classIterable,
                ObjectFactory<MemberCriteria<Member>> templateObjectFactory) {
            this.classIterable = classIterable;
            this.templateObjectFactory = templateObjectFactory;
        }

        @SuppressWarnings("unchecked")
        public Iterator<Member> iterator() {
            Iterator<Class<?>> classIterator = classIterable.iterator();
            MemberCriteria<Member> memberCriteria = templateObjectFactory.getObject();
            Transformer memberIteratorTransformer = new MemberIteratorTransformer(memberCriteria);
            Iterator<Member> memberIterator = IteratorUtils.objectGraphIterator(classIterator,
                    memberIteratorTransformer);
            memberIterator = memberCriteria.applyElementFilter(memberIterator);
            memberIterator = memberCriteria.applySelectionFilter(memberIterator);
            return memberIterator;
        }

    }

    private static class AnnotatedElementIterable implements Iterable<AnnotatedElement> {

        private final Iterable<Class<?>> classIterable;
        private final ObjectFactory<MemberCriteria<Member>> templateObjectFactory;
        private final IterateStrategy traverseStrategy;

        public AnnotatedElementIterable(Iterable<Class<?>> classIterable, IterateStrategy traverseStrategy,
                ObjectFactory<MemberCriteria<Member>> templateObjectFactory) {
            this.classIterable = classIterable;
            this.traverseStrategy = traverseStrategy;
            this.templateObjectFactory = templateObjectFactory;
        }

        @SuppressWarnings("unchecked")
        public Iterator<AnnotatedElement> iterator() {
            Iterator<Class<?>> classIterator = classIterable.iterator();
            MemberCriteria<Member> memberCriteria = templateObjectFactory.getObject();

            Transformer memberIteratorTransformer = new AnnotatedElementIteratorTransformer(traverseStrategy,
                    memberCriteria);
            Iterator<AnnotatedElement> iterator = IteratorUtils.objectGraphIterator(classIterator,
                    memberIteratorTransformer);
            return iterator;
        }

    }

    private static class AnnotatedElementIteratorTransformer extends AbstractCriteriaTransformer {

        private final IterateStrategy traverseStrategy;

        private final Collection<Class<?>> transformed = new ArrayList<Class<?>>();

        public AnnotatedElementIteratorTransformer(IterateStrategy traverseStrategy,
                MemberCriteria memberCriteria) {
            super(memberCriteria);
            this.traverseStrategy = traverseStrategy;
        }

        public Object transform(Object input) {
            if (input instanceof Class<?>) {
                final Class<?> currentClass = (Class<?>) input;
                boolean hasBeenTransformed = transformed.remove(currentClass);
                if (hasBeenTransformed) {
                    switch (traverseStrategy) {
                    case MEMBERS_ONLY:
                    case PACKAGES_ONLY:
                        return IteratorUtils.EMPTY_ITERATOR;
                    default:
                        return currentClass;
                    }
                }

                transformed.add(currentClass);

                MemberCriteria memberCriteria = getMemberCriteria();

                JavaElementTraverseStrategy javaElementTraverseStrategy = traverseStrategy
                        .getJavaElementTraverseStrategy();

                Iterator<?> iterator = javaElementTraverseStrategy.getIterator(currentClass, memberCriteria);

                return iterator;
            } else if (input instanceof Package) {
                return input;
            } else {
                return input;
            }
        }
    }

    /**
     * @return an object factory that uses this {@link MemberCriteria} as a
     *         template to create a new {@link MemberCriteria}.
     */
    public ObjectFactory<MemberCriteria<Member>> getObjectFactory() {
        return new SerializableTemplateObjectFactory(this);
    }
}