001: /*
002: * Copyright 2004 Brian S O'Neill
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.cojen.util;
018:
019: import java.lang.ref.SoftReference;
020: import java.lang.reflect.Method;
021: import java.lang.reflect.Modifier;
022: import java.util.Collections;
023: import java.util.HashMap;
024: import java.util.Map;
025:
026: /**
027: * Alternative to the standard Bean introspector. One key difference is that
028: * this introspector ensures interface properties are properly
029: * discovered. Also, indexed properties can have an index of any type.
030: *
031: * @author Brian S O'Neill
032: */
033: public class BeanIntrospector {
034: // Weakly maps Class objects to softly referenced BeanProperty maps.
035: private static Map cPropertiesCache = new WeakIdentityMap();
036:
037: public static void main(String[] args) throws Exception {
038: System.out.println(getAllProperties(Class.forName(args[0])));
039: }
040:
041: /**
042: * Returns a Map of all the available properties on a given class including
043: * write-only and indexed properties.
044: *
045: * @return Map<String, BeanProperty> an unmodifiable mapping of property
046: * names (Strings) to BeanProperty objects.
047: *
048: */
049: public static Map getAllProperties(Class clazz) {
050: synchronized (cPropertiesCache) {
051: Map properties;
052: SoftReference ref = (SoftReference) cPropertiesCache
053: .get(clazz);
054: if (ref != null) {
055: properties = (Map) ref.get();
056: if (properties != null) {
057: return properties;
058: }
059: }
060: properties = createProperties(clazz);
061: cPropertiesCache.put(clazz, new SoftReference(properties));
062: return properties;
063: }
064: }
065:
066: private static Map createProperties(Class clazz) {
067: if (clazz == null || clazz.isPrimitive()) {
068: return Collections.EMPTY_MAP;
069: }
070:
071: Map properties = new HashMap();
072: fillInProperties(clazz, properties);
073:
074: // Properties defined in Object are also available to interfaces.
075: if (clazz.isInterface()) {
076: fillInProperties(Object.class, properties);
077: }
078:
079: // Ensure that all implemented interfaces are properly analyzed.
080: Class[] interfaces = clazz.getInterfaces();
081: for (int i = 0; i < interfaces.length; i++) {
082: fillInProperties(interfaces[i], properties);
083: }
084:
085: return Collections.unmodifiableMap(properties);
086: }
087:
088: /**
089: * @param clazz Class to introspect
090: * @param properties Receives properties as name->property entries
091: */
092: private static void fillInProperties(Class clazz, Map properties) {
093: Method[] methods = clazz.getMethods();
094:
095: Method method;
096: String name;
097: Class type;
098: Class[] params;
099: SimpleProperty property;
100: IndexedProperty indexedProperty;
101:
102: // Gather non-conflicting "get" accessors
103: for (int i = 0; i < methods.length; i++) {
104: method = methods[i];
105: if (Modifier.isStatic(method.getModifiers())
106: || (type = method.getReturnType()) == void.class
107: || method.getParameterTypes().length > 0
108: || (name = extractPropertyName(method, "get")) == null) {
109: continue;
110: }
111: if (properties.containsKey(name)) {
112: property = (SimpleProperty) properties.get(name);
113: if (type != property.getType()
114: || property.getReadMethod() != null) {
115: continue;
116: }
117: } else {
118: property = new SimpleProperty(name, type);
119: properties.put(name, property);
120: }
121: property.setReadMethod(method);
122: }
123:
124: // Gather non-conflicting "is" accessors.
125: for (int i = 0; i < methods.length; i++) {
126: method = methods[i];
127: if (Modifier.isStatic(method.getModifiers())
128: || (type = method.getReturnType()) != boolean.class
129: || method.getParameterTypes().length > 0
130: || (name = extractPropertyName(method, "is")) == null) {
131: continue;
132: }
133: if (properties.containsKey(name)) {
134: property = (SimpleProperty) properties.get(name);
135: if (type != property.getType()
136: || property.getReadMethod() != null) {
137: continue;
138: }
139: } else {
140: property = new SimpleProperty(name, type);
141: properties.put(name, property);
142: }
143: property.setReadMethod(method);
144: }
145:
146: // Gather non-conflicting mutators.
147: for (int i = 0; i < methods.length; i++) {
148: method = methods[i];
149: if (Modifier.isStatic(method.getModifiers())
150: || method.getReturnType() != void.class
151: || (params = method.getParameterTypes()).length != 1
152: || (name = extractPropertyName(method, "set")) == null) {
153: continue;
154: }
155: type = params[0];
156: if (properties.containsKey(name)) {
157: property = (SimpleProperty) properties.get(name);
158: if (type != property.getType()
159: || property.getWriteMethod() != null) {
160: continue;
161: }
162: } else {
163: property = new SimpleProperty(name, type);
164: properties.put(name, property);
165: }
166: property.setWriteMethod(method);
167: }
168:
169: // Gather non-conflicting indexed property accessors.
170: for (int i = 0; i < methods.length; i++) {
171: method = methods[i];
172: if (Modifier.isStatic(method.getModifiers())
173: || (type = method.getReturnType()) == void.class
174: || (params = method.getParameterTypes()).length != 1
175: || (name = extractPropertyName(method, "get")) == null) {
176: continue;
177: }
178: if (properties.containsKey(name)) {
179: property = (SimpleProperty) properties.get(name);
180: if (type != property.getType()) {
181: continue;
182: }
183: if (property instanceof IndexedProperty) {
184: indexedProperty = (IndexedProperty) property;
185: } else {
186: indexedProperty = new IndexedProperty(property);
187: properties.put(name, indexedProperty);
188: }
189: } else {
190: indexedProperty = new IndexedProperty(name, type);
191: properties.put(name, indexedProperty);
192: }
193: indexedProperty.addIndexedReadMethod(method);
194: }
195:
196: // Gather non-conflicting indexed property mutators.
197: for (int i = 0; i < methods.length; i++) {
198: method = methods[i];
199: if (Modifier.isStatic(method.getModifiers())
200: || method.getReturnType() != void.class
201: || (params = method.getParameterTypes()).length != 2
202: || (name = extractPropertyName(method, "set")) == null) {
203: continue;
204: }
205: type = params[1];
206: if (properties.containsKey(name)) {
207: property = (SimpleProperty) properties.get(name);
208: if (type != property.getType()) {
209: continue;
210: }
211: if (property instanceof IndexedProperty) {
212: indexedProperty = (IndexedProperty) property;
213: } else {
214: indexedProperty = new IndexedProperty(property);
215: properties.put(name, indexedProperty);
216: }
217: } else {
218: indexedProperty = new IndexedProperty(name, type);
219: properties.put(name, indexedProperty);
220: }
221: indexedProperty.addIndexedWriteMethod(method);
222: }
223: }
224:
225: /**
226: * Returns null if prefix pattern doesn't match
227: */
228: private static String extractPropertyName(Method method,
229: String prefix) {
230: String name = method.getName();
231: if (!name.startsWith(prefix)) {
232: return null;
233: }
234: if (name.length() == prefix.length()) {
235: return "";
236: }
237: name = name.substring(prefix.length());
238: if (!Character.isUpperCase(name.charAt(0))
239: || name.indexOf('$') >= 0) {
240: return null;
241: }
242:
243: // Decapitalize the name only if it doesn't begin with two uppercase
244: // letters.
245:
246: if (name.length() == 1
247: || !Character.isUpperCase(name.charAt(1))) {
248: char chars[] = name.toCharArray();
249: chars[0] = Character.toLowerCase(chars[0]);
250: name = new String(chars);
251: }
252:
253: return name.intern();
254: }
255:
256: private static class SimpleProperty implements BeanProperty {
257: private final String mName;
258: private final Class mType;
259:
260: private Method mReadMethod;
261: private Method mWriteMethod;
262:
263: SimpleProperty(String name, Class type) {
264: mName = name;
265: mType = type;
266: }
267:
268: public String getName() {
269: return mName;
270: }
271:
272: public Class getType() {
273: return mType;
274: }
275:
276: public Method getReadMethod() {
277: return mReadMethod;
278: }
279:
280: public Method getWriteMethod() {
281: return mWriteMethod;
282: }
283:
284: public int getIndexTypesCount() {
285: return 0;
286: }
287:
288: public Class getIndexType(int index) {
289: throw new IndexOutOfBoundsException();
290: }
291:
292: public Method getIndexedReadMethod(int index) {
293: throw new IndexOutOfBoundsException();
294: }
295:
296: public Method getIndexedWriteMethod(int index) {
297: throw new IndexOutOfBoundsException();
298: }
299:
300: public String toString() {
301: return "BeanProperty[name=" + getName() + ", type="
302: + getType().getName() + ']';
303: }
304:
305: void setReadMethod(Method method) {
306: mReadMethod = method;
307: }
308:
309: void setWriteMethod(Method method) {
310: mWriteMethod = method;
311: }
312: }
313:
314: private static class IndexedProperty extends SimpleProperty {
315: private Method[] mIndexedReadMethods;
316: private Method[] mIndexedWriteMethods;
317:
318: IndexedProperty(String name, Class type) {
319: super (name, type);
320: }
321:
322: IndexedProperty(BeanProperty property) {
323: super (property.getName(), property.getType());
324: setReadMethod(property.getReadMethod());
325: setWriteMethod(property.getWriteMethod());
326: }
327:
328: public int getIndexTypesCount() {
329: Method[] methods = mIndexedReadMethods;
330: if (methods != null) {
331: return methods.length;
332: }
333: methods = mIndexedWriteMethods;
334: return methods == null ? 0 : methods.length;
335: }
336:
337: public Class getIndexType(int index) {
338: Method[] methods = mIndexedReadMethods;
339: if (methods != null) {
340: Method method = methods[0];
341: if (method != null) {
342: return method.getParameterTypes()[0];
343: }
344: }
345: methods = mIndexedWriteMethods;
346: if (methods != null) {
347: Method method = methods[index];
348: if (method != null) {
349: return method.getParameterTypes()[0];
350: }
351: }
352: if (index >= getIndexTypesCount()) {
353: throw new IndexOutOfBoundsException();
354: }
355: return null;
356: }
357:
358: public Method getIndexedReadMethod(int index) {
359: return mIndexedReadMethods[index];
360: }
361:
362: public Method getIndexedWriteMethod(int index) {
363: return mIndexedWriteMethods[index];
364: }
365:
366: public String toString() {
367: StringBuffer buf = new StringBuffer();
368: buf.append("BeanProperty[name=");
369: buf.append(getName());
370: buf.append(", type=");
371: buf.append(getType().getName());
372: buf.append(", ");
373: int count = getIndexTypesCount();
374: for (int i = 0; i < count; i++) {
375: if (i > 0) {
376: buf.append(", ");
377: }
378: buf.append("indexType[");
379: buf.append(i);
380: buf.append("]=");
381: buf.append(getIndexType(0));
382: }
383: buf.append(']');
384: return buf.toString();
385: }
386:
387: void addIndexedReadMethod(Method method) {
388: Class indexType = method.getParameterTypes()[0];
389: int count = getIndexTypesCount();
390: int i;
391: for (i = 0; i < count; i++) {
392: if (getIndexType(i) == indexType) {
393: break;
394: }
395: }
396: if (i >= count) {
397: expandCapactity();
398: }
399: if (mIndexedReadMethods[i] == null) {
400: mIndexedReadMethods[i] = method;
401: }
402: }
403:
404: void addIndexedWriteMethod(Method method) {
405: Class indexType = method.getParameterTypes()[0];
406: int count = getIndexTypesCount();
407: int i;
408: for (i = 0; i < count; i++) {
409: if (getIndexType(i) == indexType) {
410: break;
411: }
412: }
413: if (i >= count) {
414: expandCapactity();
415: }
416: if (mIndexedWriteMethods[i] == null) {
417: mIndexedWriteMethods[i] = method;
418: }
419: }
420:
421: private void expandCapactity() {
422: int count = getIndexTypesCount();
423: Method[] methods = new Method[count + 1];
424: if (mIndexedReadMethods != null) {
425: System.arraycopy(mIndexedReadMethods, 0, methods, 0,
426: count);
427: }
428: mIndexedReadMethods = methods;
429: methods = new Method[count + 1];
430: if (mIndexedWriteMethods != null) {
431: System.arraycopy(mIndexedWriteMethods, 0, methods, 0,
432: count);
433: }
434: mIndexedWriteMethods = methods;
435: }
436: }
437: }
|