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}