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}