Source code: echopoint/stylesheet/StyleSheetIntrospector.java
1 package echopoint.stylesheet;
2 /*
3 * This file is part of the Echo Point Project. This project is a collection
4 * of Components that have extended the Echo Web Application Framework.
5 *
6 * EchoPoint is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * EchoPoint is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with Echo Point; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21 import java.beans.IntrospectionException;
22 import java.lang.reflect.Modifier;
23 import java.util.ArrayList;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Stack;
28
29 import echopoint.util.collections.ConcurrentReaderHashMap;
30
31 /**
32 * The <code>StyleSheetIntrospector</code> is used to introspect on a given
33 * Component class and find out style attribute information on that class.
34 * <p>
35 * The <code>StyleSheetIntrospector</code> looks for a Components's "style info"
36 * support class that implements <code>StyleInfo</code>
37 * <p>
38 * In much the same way as the java.beans.Introspector looks for BeanInfo classes
39 * the <code>StyleSheetIntrospector</code> will first look for a class named
40 * <i>componentClass</i>StyleInfo in the same package as component which implements
41 * the echopoint.stylesheet.StyleInfo interface.
42 * <p>
43 * If it cant find that class, it will look for the first public static
44 * nested class inside the Component class that implements the
45 * echopoint.stylesheet.StyleInfo interface.
46 * <p>
47 * So for example if you component class is call "x.y.z.BlueBell" then the
48 * <code>StyleSheetIntrospector</code> will look for a class
49 * called "x.y.z.BlueBellStyleInfo".
50 * <p>
51 * As in:
52 * <code>
53 * public class BlueBellStyleInfo implements echopoint.stylesheet.StyleInfo {<br>
54 * ...<br>
55 * }<br>
56 * </code>
57 * If it cannot that, it will look for a nested class within
58 * "x.y.z.BlueBell" that is public, static and that
59 * implements <code>echopoint.stylesheet.StyleInfo</code>.
60 * <p>
61 * As in:
62 * <code>
63 * public class BlueBell extends nextapp.echo.Component {<br>
64 * public static NestedStyleInfo implements echopoint.stylesheet.StyleInfo {<br>
65 * ...<br>
66 * }<br>
67 * <br>
68 * ...<br>
69 * }<br>
70 * </code>
71 * <p>
72 * After the above processing is complete, the introspector will examine the interfaces that the class
73 * implements. It will introspect inside all of its interfaces for public nested static
74 * class that implements <code>echopoint.stylesheet.StyleInfo</code>. The union of these
75 * StyleInfo implementing classes will then be returned for that component class.
76 * <p>
77 * For example given :
78 * <code><br>
79 * public class MyComponent extends Component implements Slip, Slop, Slap {<br>
80 * ...<br>
81 * </code>
82 * The introspector will examine the Slip, Slop Slap interfaces to see if they
83 * have nested public static StyleInfo classes. Any sub-interfaces that these
84 * interfaces may themselves extended will also be searched. The union of these
85 * StyleInfo objects will be returned as the StyleInfo for the original component
86 *
87 *
88 */
89 public class StyleSheetIntrospector {
90
91 /** this hold our global cache of Class --> StyleInfo */
92 private static Map styleInfoCache = new ConcurrentReaderHashMap();
93
94
95 /**
96 * Adds a StyleInfo object for the specified clazz into the global cache. Once
97 * added it can be quickly gathered again. In general you will not need
98 * to call this function because it all results of the getStyleInfo()
99 * method are always globally cached.
100 *
101 * @param clazz - the class that the StyleInfo applies
102 * @param styleInfo - the styleInfo in question
103 */
104 public static void addCachedStyleInfo(Class clazz, StyleInfo styleInfo) {
105 if (styleInfo != null && clazz != null)
106 styleInfoCache.put(clazz,styleInfo);
107 }
108
109 /**
110 * Flushes the global cache of StyleInfo information.
111 *
112 */
113 public static void flushCachedStyleInfo() {
114 styleInfoCache.clear();
115 }
116 /**
117 * Introspects a class and learns about the style attributes it supports.
118 *
119 * @param searchClass - the component class that is to be introspected
120 * @return StyleInfo - a StyleInfo object or null if no info can be found
121 * @throws IntrospectionException - if an exception occurs during introspection.
122 */
123 public static StyleInfo getStyleInfo(Class searchClass) throws IntrospectionException {
124 if (searchClass == null)
125 throw new IntrospectionException("The component class must be non null");
126
127 StyleInfo styleInfo = null;
128 styleInfo = (StyleInfo) styleInfoCache.get(searchClass);
129 if (styleInfo != null)
130 return styleInfo;
131
132 // find class style info
133 styleInfo = findClassStyleInfo(searchClass);
134 // and possibly combine it with interface style info
135 styleInfo = combineInterfaceStyleInfo(searchClass,styleInfo);
136 // and add to our cache
137 addCachedStyleInfo(searchClass,styleInfo);
138 return styleInfo;
139 }
140
141 /**
142 * Does the multistage lookup of StyleInfo data.
143 */
144 private static StyleInfo findClassStyleInfo(Class searchClass) {
145 String styleInfoClassName = searchClass.getName() + "StyleInfo";
146 Class styleInfoClass = null;
147
148 if (! searchClass.isInterface()) {
149 // look for top level xxxStyleInfo class
150 try {
151 styleInfoClass = Class.forName(styleInfoClassName);
152 int mods = styleInfoClass.getModifiers();
153 if (!Modifier.isPublic(mods))
154 return null;
155 } catch (ClassNotFoundException e) {
156 styleInfoClass = null;
157 }
158 if (styleInfoClass != null && StyleInfo.class.isAssignableFrom(styleInfoClass)) {
159 try {
160 return (StyleInfo) styleInfoClass.newInstance();
161 } catch (InstantiationException e1) {
162 return null;
163 } catch (IllegalAccessException e1) {
164 return null;
165 }
166 }
167 }
168 //
169 // okay. lets look for nested static inner classes
170 // look for the any nested StyleInfo implementing class
171 //
172 StyleInfo styleInfo = lookForNestedStyleInifo(searchClass);
173 if (styleInfo != null)
174 return styleInfo;
175
176 return null;
177 }
178
179 /**
180 * Called to get an interface StyleInfo. If the there are
181 * no StyleInfo classes in the interfaces then the original
182 * classStyleInfo object is return.
183 */
184 private static StyleInfo combineInterfaceStyleInfo(Class searchClass, StyleInfo classStyleInfo) {
185 //
186 // check the interfaces that the class might implement
187 //
188 StyleAttrDescriptor[] descs;
189 StyleInfo styleInfo;
190 Class[] classes = searchClass.getInterfaces();
191 int descriptorCount = 0;
192 List descriptorList = new ArrayList();
193 Stack stack = new Stack();
194 stack.push(classes);
195 while (! stack.isEmpty()) {
196 classes = (Class[]) stack.pop();
197 for (int i = 0; i < classes.length; i++) {
198 styleInfo = lookForNestedStyleInifo(classes[i]);
199 if (styleInfo != null) {
200 descs = styleInfo.getStyleDescriptors();
201 if (descs != null) {
202 descriptorCount += descs.length;
203 descriptorList.add(descs);
204 }
205 }
206 // find child interfaces
207 if (classes[i].getInterfaces().length > 0)
208 stack.push(classes[i].getInterfaces());
209 }
210 }
211 //
212 // there are no interfaces with StyleInfo
213 if (descriptorCount == 0)
214 return classStyleInfo;
215
216 // combine our previous class StyleInfo
217 if (classStyleInfo != null) {
218 descs = classStyleInfo.getStyleDescriptors();
219 if (descs != null) {
220 descriptorCount += descs.length;
221 descriptorList.add(descs);
222 }
223 }
224 // need to convert the union of all the interfaces with descriptors into the one
225 // StyleInfo object;
226 int i = 0;
227 final StyleAttrDescriptor[] combinedDescs = new StyleAttrDescriptor[descriptorCount];
228 for (Iterator iter = descriptorList.iterator(); iter.hasNext();) {
229 StyleAttrDescriptor[] descArr = (StyleAttrDescriptor[]) iter.next();
230 for (int j = 0; j < descArr.length; j++) {
231 combinedDescs[i] = descArr[j];
232 i++;
233 }
234 }
235 styleInfo = new StyleInfo() {
236 public StyleAttrDescriptor[] getStyleDescriptors() {
237 return combinedDescs;
238 }
239 };
240 return styleInfo;
241 }
242
243 /**
244 * Looks inside a class for all the nested StyleInfo classes
245 */
246 private static StyleInfo lookForNestedStyleInifo(Class searchClass) {
247 StyleInfo styleInfo = null;
248 Class[] classes = searchClass.getDeclaredClasses();
249 for (int i = 0; i < classes.length; i++) {
250 styleInfo = isNestedStyleInfo(classes[i]);
251 if (styleInfo != null) {
252 return styleInfo;
253 }
254 }
255 return null;
256 }
257
258 /**
259 * Test a given class as to whether it implements StyleInfo
260 * and whether its public and static.
261 *
262 * @param searchClass
263 * @return
264 */
265 private static StyleInfo isNestedStyleInfo(Class searchClass) {
266 int mods = searchClass.getModifiers();
267 if (StyleInfo.class.isAssignableFrom(searchClass) && Modifier.isPublic(mods) && Modifier.isStatic(mods)) {
268 try {
269 return (StyleInfo) searchClass.newInstance();
270 } catch (InstantiationException e1) {
271 return null;
272 } catch (IllegalAccessException e1) {
273 return null;
274 }
275 }
276 return null;
277 }
278 }