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