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}