    001// Licensed under the Apache License, Version 2.0 (the "License");
    002// you may not use this file except in compliance with the License.
    003// You may obtain a copy of the License at
    004//
    005//     http://www.apache.org/licenses/LICENSE-2.0
    006//
    007// Unless required by applicable law or agreed to in writing, software
    008// distributed under the License is distributed on an "AS IS" BASIS,
    009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    010// See the License for the specific language governing permissions and
    011// limitations under the License.
    012
    013package org.apache.tapestry5.ioc.internal.util;
    014
    015import org.apache.tapestry5.plastic.PlasticUtils;
    016
    017import java.util.Iterator;
    018import java.util.LinkedList;
    019import java.util.Set;
    020
    021/**
    022 * Used to search from a particular class up the inheritance hierarchy of extended classes and implemented interfaces.
    023 *
    024 * The search starts with the initial class (provided in the constructor). It progresses up the inheritance chain, but
    025 * skips java.lang.Object.
    026 *
    027 * Once classes are exhausted, the inheritance hierarchy is searched. This is a breadth-first search, rooted in the
    028 * interfaces implemented by the initial class at its super classes.
    029 *
    030 * Once all interfaces are exhausted, java.lang.Object is returned (it is always returned last).
    031 *
    032 * Two minor tweak to normal inheritance rules: <ul> <li> Normally, the parent class of an <em>object</em> array is
    033 * java.lang.Object, which is odd because FooService[] is assignable to Object[]. Thus, we tweak the search so that the
    034 * effective super class of FooService[] is Object[]. <li> The "super class" of a primtive type is its <em>wrapper type</em>,
    035 * with the exception of void, whose "super class" is left at its normal value (Object.class) </ul>
    036 *
    037 * This class implements the {@link Iterable} interface, so it can be used directly in a for loop: <code> for (Class
    038 * search : new InheritanceSearch(startClass)) { ... } </code>
    039 *
    040 * This class is not thread-safe.
    041 */
    042public class InheritanceSearch implements Iterator<Class>, Iterable<Class>
    043{
    044    private Class searchClass;
    045
    046    private final Set<Class> addedInterfaces = CollectionFactory.newSet();
    047
    048    private final LinkedList<Class> interfaceQueue = CollectionFactory.newLinkedList();
    049
    050    private enum State
    051    {
    052        CLASS, INTERFACE, DONE
    053    }
    054
    055    private State state;
    056
    057    public InheritanceSearch(Class searchClass)
    058    {
    059        this.searchClass = searchClass;
    060
    061        queueInterfaces(searchClass);
    062
    063        state = searchClass == Object.class ? State.INTERFACE : State.CLASS;
    064    }
    065
    066    private void queueInterfaces(Class searchClass)
    067    {
    068        for (Class intf : searchClass.getInterfaces())
    069        {
    070            if (addedInterfaces.contains(intf)) continue;
    071
    072            interfaceQueue.addLast(intf);
    073            addedInterfaces.add(intf);
    074        }
    075    }
    076
    077    @Override
    078    public Iterator<Class> iterator()
    079    {
    080        return this;
    081    }
    082
    083    @Override
    084    public boolean hasNext()
    085    {
    086        return state != State.DONE;
    087    }
    088
    089    @Override
    090    public Class next()
    091    {
    092        switch (state)
    093        {
    094            case CLASS:
    095
    096                Class result = searchClass;
    097
    098                searchClass = parentOf(searchClass);
    099
    100                if (searchClass == null) state = State.INTERFACE;
    101                else queueInterfaces(searchClass);
    102
    103                return result;
    104
    105            case INTERFACE:
    106
    107                if (interfaceQueue.isEmpty())
    108                {
    109                    state = State.DONE;
    110                    return Object.class;
    111                }
    112
    113                Class intf = interfaceQueue.removeFirst();
    114
    115                queueInterfaces(intf);
    116
    117                return intf;
    118
    119            default:
    120                throw new IllegalStateException();
    121        }
    122
    123    }
    124
    125    /**
    126     * Returns the parent of the given class. Tweaks inheritance for object arrays. Returns null instead of
    127     * Object.class.
    128     */
    129    private Class parentOf(Class clazz)
    130    {
    131        if (clazz != void.class && clazz.isPrimitive()) return PlasticUtils.toWrapperType(clazz);
    132
    133        if (clazz.isArray() && clazz != Object[].class)
    134        {
    135            Class componentType = clazz.getComponentType();
    136
    137            while (componentType.isArray()) componentType = componentType.getComponentType();
    138
    139            if (!componentType.isPrimitive()) return Object[].class;
    140        }
    141
    142        Class parent = clazz.getSuperclass();
    143
    144        return parent != Object.class ? parent : null;
    145    }
    146
    147    /**
    148     * @throws UnsupportedOperationException
    149     *         always
    150     */
    151    @Override
    152    public void remove()
    153    {
    154        throw new UnsupportedOperationException();
    155    }
    156
    157}