001: /*
002: * Copyright (c) 1998-2006 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: * Free SoftwareFoundation, Inc.
023: * 59 Temple Place, Suite 330
024: * Boston, MA 02111-1307 USA
025: *
026: * @author Scott Ferguson
027: */
028:
029: package com.caucho.es.wrapper;
030:
031: import com.caucho.server.util.CauchoSystem;
032: import com.caucho.util.WeakLruCache;
033:
034: import java.beans.BeanInfo;
035: import java.beans.IntrospectionException;
036: import java.beans.Introspector;
037: import java.beans.MethodDescriptor;
038: import java.beans.PropertyDescriptor;
039: import java.lang.ref.SoftReference;
040: import java.lang.reflect.Field;
041: import java.lang.reflect.Method;
042: import java.lang.reflect.Modifier;
043:
044: /**
045: * Analyzes the class from a JavaScript perspective.
046: *
047: * <p>Each class Foo searches for its "FooEcmaWrap" to see if there are
048: * any method changes.
049: */
050: public class ESIntrospector {
051: /**
052: * Cache of analyzed classes to avoid duplication.
053: */
054: static WeakLruCache<Class, SoftReference<ESBeanInfo>> _beanMap = new WeakLruCache<Class, SoftReference<ESBeanInfo>>(
055: 256);
056:
057: static Integer NULL = new Integer(0);
058: final static int METHOD = 1;
059: final static int PROPERTY = 2;
060: final static int MASK = 3;
061:
062: static String[] path = new String[] { "", "com.caucho.eswrap" };
063:
064: /**
065: * Analyzes the class, returning the calculated ESBeanInfo.
066: *
067: * @param cl the class to be analyzed.
068: * @return the analyzed bean info.
069: */
070: public static ESBeanInfo getBeanInfo(Class cl)
071: throws IntrospectionException {
072: ESBeanInfo info;
073:
074: SoftReference<ESBeanInfo> infoRef = _beanMap.get(cl);
075:
076: info = infoRef != null ? infoRef.get() : null;
077:
078: if (info != null)
079: return info;
080:
081: info = new ESBeanInfo(cl);
082:
083: getMethods(info, cl);
084:
085: _beanMap.put(cl, new SoftReference<ESBeanInfo>(info));
086:
087: return info;
088: }
089:
090: /**
091: * Analyzes a Java method, converting it to any equivalent JavaScript
092: * properties.
093: *
094: * <pre>
095: * keys() -> for (item in obj) {
096: * getFoo() -> obj.foo
097: * getFoo(int) -> obj.foo[i]
098: * getFoo(String) -> obj.foo["name"]
099: * remove(String) -> delete obj.foo
100: * </pre>
101: *
102: * @param info the bean info to be filled
103: * @param cl the bean's class
104: * @param md the method descriptor
105: * @param overwrite if true, this overwrites any previous definition
106: */
107: private static void analyzeProperty(ESBeanInfo info, Class cl,
108: ESMethodDescriptor md, boolean overwrite)
109: throws IntrospectionException {
110: Method method = md.getMethod();
111: int modifiers = method.getModifiers();
112:
113: if (!Modifier.isPublic(modifiers))
114: return;
115:
116: String name = method.getName();
117: String propName;
118: Class returnType = method.getReturnType();
119: String returnName = returnType.getName();
120: Class[] params = md.getParameterTypes();
121:
122: if (name.equals("keys")
123: && params.length == 0
124: && (returnName.equals("java.util.Iterator") || (returnName
125: .equals("java.util.Enumeration")))) {
126: info.iterator = md;
127: } else if (name.equals("iterator")
128: && params.length == 0
129: && (returnName.equals("java.util.Iterator") || (returnName
130: .equals("java.util.Enumeration")))) {
131: // keys has priority over iterator
132: if (info.iterator == null)
133: info.iterator = md;
134: } else if (name.startsWith("get") && !name.equals("get")) {
135: propName = Introspector.decapitalize(name.substring(3));
136:
137: // kill match?
138: if (returnName.equals("void") && params.length < 2) {
139: // XXX: props.put(propName, BAD);
140: return;
141: }
142:
143: // name keys
144: if (params.length == 0
145: && (propName.endsWith("Keys")
146: && !propName.equals("Keys") || propName
147: .endsWith("Names")
148: && !propName.equals("Names"))
149: && (returnName.equals("java.util.Iterator") || (returnName
150: .equals("java.util.Enumeration")))) {
151: if (propName.endsWith("Keys"))
152: info.addNamedProp(propName.substring(0, propName
153: .length() - 4), null, null, null, md);
154: else
155: info.addNamedProp(propName.substring(0, propName
156: .length() - 5), null, null, null, md);
157: }
158: // index length
159: else if (params.length == 0
160: && (propName.endsWith("Size")
161: && !propName.equals("Size") || propName
162: .endsWith("Length")
163: && !propName.equals("Length"))
164: && (returnName.equals("int"))) {
165: info.addProp(propName, null, md, null);
166: if (propName.endsWith("Size"))
167: info.addIndexedProp(propName.substring(0, propName
168: .length() - 4), null, null, md);
169: else
170: info.addIndexedProp(propName.substring(0, propName
171: .length() - 6), null, null, md);
172: } else if (params.length == 0)
173: info.addProp(propName, null, md, null);
174: else if (params.length == 1
175: && params[0].getName().equals("java.lang.String")) {
176: info.addNamedProp(propName, md, null, null, null);
177: } else if (params.length == 1
178: && params[0].getName().equals("int")) {
179: info.addIndexedProp(propName, md, null, null);
180: } else if (params.length == 1) {
181: // xxx: add bad?
182: }
183: } else if (name.startsWith("set") && !name.equals("set")) {
184: propName = Introspector.decapitalize(name.substring(3));
185:
186: if (params.length == 0)
187: return;
188:
189: // kill match?
190: if (!returnType.getName().equals("void")
191: && params.length < 3) {
192: // XXX: add bad? props.put(propName, NULL);
193: return;
194: }
195:
196: if (params.length == 1)
197: info.addProp(propName, null, null, md);
198: else if (params.length == 2
199: && params[0].getName().equals("java.lang.String")) {
200: info.addNamedProp(propName, null, md, null, null);
201: } else if (params.length == 2
202: && params[0].getName().equals("int")) {
203: info.addIndexedProp(propName, null, md, null);
204: } else if (params.length == 2) {
205: // XXX: props.put(propName, NULL);
206: }
207: } else if (name.startsWith("remove") && !name.equals("remove")
208: || name.startsWith("delete") && !name.equals("remove")) {
209: propName = Introspector.decapitalize(name.substring(6));
210:
211: if (params.length == 0)
212: return;
213:
214: // kill match?
215: if (!returnType.getName().equals("void")
216: && params.length < 2) {
217: // XXX: add bad? props.put(propName, NULL);
218: return;
219: }
220:
221: //if (params.length == 0)
222: // info.addProp(propName, null, null, md);
223: if (params.length == 1
224: && params[0].getName().equals("java.lang.String")) {
225: info.addNamedProp(propName, null, null, md, null);
226: }
227: /*
228: else if (params.length == 2 &&
229: params[0].getName().equals("int")) {
230: info.addIndexedProp(propName, null, md, null);
231: } else if (params.length == 2) {
232: // XXX: props.put(propName, NULL);
233: }
234: */
235: }
236: }
237:
238: /**
239: * Add methods from a FooEcmaWrap class.
240: */
241: static void addEcmaMethods(ESBeanInfo info, Class cl, Class wrapCl,
242: int mask) throws IntrospectionException {
243: Method[] methods = wrapCl.getDeclaredMethods();
244:
245: for (int i = 0; i < methods.length; i++) {
246: int modifiers = methods[i].getModifiers();
247:
248: if (!Modifier.isStatic(modifiers))
249: continue;
250:
251: ESMethodDescriptor md = info.createMethodDescriptor(
252: methods[i], true);
253:
254: if ((mask & METHOD) != 0)
255: info.addMethod(md);
256: if ((mask & PROPERTY) != 0)
257: analyzeProperty(info, cl, md, true);
258: }
259: }
260:
261: /**
262: * Search for FooEcmaWrap classes.
263: *
264: * @param info the bean info to be filled
265: * @param cl the bean being analyzed
266: * @param mask the replacement mask.
267: */
268: static void addEcmaWrap(ESBeanInfo info, Class cl, int mask)
269: throws IntrospectionException {
270: String name = cl.getName();
271: int lastDot = name.lastIndexOf('.');
272: String prefix = lastDot == -1 ? "" : name.substring(0, lastDot);
273: String tail = lastDot == -1 ? name : name
274: .substring(lastDot + 1);
275:
276: ClassLoader loader = cl.getClassLoader();
277:
278: for (int i = 0; i < path.length; i++) {
279: String testName;
280:
281: if (path[i].equals(""))
282: testName = name + "EcmaWrap";
283: else
284: testName = path[i] + "." + name + "EcmaWrap";
285:
286: try {
287: Class wrapCl = CauchoSystem.loadClass(testName, false,
288: loader);
289:
290: if (wrapCl != null) {
291: addEcmaMethods(info, cl, wrapCl, mask);
292: return;
293: }
294: } catch (ClassNotFoundException e) {
295: }
296:
297: if (path[i].equals(""))
298: testName = tail + "EcmaWrap";
299: else
300: testName = path[i] + "." + tail + "EcmaWrap";
301:
302: try {
303: Class wrapCl = CauchoSystem.loadClass(testName, false,
304: loader);
305:
306: if (wrapCl != null) {
307: addEcmaMethods(info, cl, wrapCl, mask);
308: return;
309: }
310: } catch (ClassNotFoundException e) {
311: }
312: }
313: }
314:
315: /**
316: * Fills information for a FooBeanInfo class.
317: *
318: * @param info the result information object
319: * @param cl the bean class
320: *
321: * @return a mask
322: */
323: static int getBeanInfo(ESBeanInfo info, Class cl) {
324: try {
325: String name = cl.getName() + "BeanInfo";
326:
327: Class beanClass = CauchoSystem.loadClass(name, false, cl
328: .getClassLoader());
329: if (beanClass == null)
330: return MASK;
331: BeanInfo beanInfo = (BeanInfo) beanClass.newInstance();
332:
333: MethodDescriptor[] mds = beanInfo.getMethodDescriptors();
334: if (mds == null)
335: return MASK;
336:
337: for (int i = 0; i < mds.length; i++) {
338: Method method = mds[i].getMethod();
339: int modifiers = method.getModifiers();
340:
341: if (!Modifier.isStatic(modifiers)
342: && !method.getDeclaringClass()
343: .isAssignableFrom(cl))
344: continue;
345:
346: info.addMethod(mds[i], true);
347: }
348:
349: return MASK & ~METHOD;
350: } catch (Exception e) {
351: return MASK;
352: }
353: }
354:
355: static void getPropBeanInfo(ESBeanInfo info, Class cl) {
356: try {
357: String name = cl.getName() + "BeanInfo";
358: Class beanClass = CauchoSystem.loadClass(name, false, cl
359: .getClassLoader());
360: if (beanClass == null)
361: return;
362: BeanInfo beanInfo = (BeanInfo) beanClass.newInstance();
363:
364: PropertyDescriptor[] props = beanInfo
365: .getPropertyDescriptors();
366:
367: for (int i = 0; props != null && i < props.length; i++) {
368: ESMethodDescriptor read;
369: ESMethodDescriptor write;
370:
371: read = new ESMethodDescriptor(props[i].getReadMethod(),
372: false, false);
373: write = new ESMethodDescriptor(props[i]
374: .getWriteMethod(), false, false);
375:
376: info.addProp(props[i].getName(), null, read, write,
377: true);
378: }
379: } catch (Exception e) {
380: }
381: }
382:
383: private static void getMethods(ESBeanInfo info, Class cl)
384: throws IntrospectionException {
385: if (!cl.isPrimitive() && !cl.isArray()
386: && cl.getName().indexOf('.') < 0)
387: info.addNonPkgClass(cl.getName());
388:
389: getPropBeanInfo(info, cl);
390:
391: int mask = getBeanInfo(info, cl);
392:
393: if (mask == 0)
394: return;
395:
396: Class[] interfaces = cl.getInterfaces();
397: for (int i = 0; i < interfaces.length; i++) {
398: ESBeanInfo subInfo = getBeanInfo(interfaces[i]);
399:
400: if ((mask & METHOD) != 0)
401: info.addMethods(subInfo);
402: if ((mask & PROPERTY) != 0)
403: info.addProps(subInfo);
404: }
405:
406: Class super Class = cl.getSuperclass();
407: if (super Class != null) {
408: ESBeanInfo subInfo = getBeanInfo(super Class);
409:
410: if ((mask & METHOD) != 0)
411: info.addMethods(subInfo);
412: if ((mask & PROPERTY) != 0)
413: info.addProps(subInfo);
414: }
415:
416: int modifiers = cl.getModifiers();
417: if (Modifier.isPublic(modifiers)) {
418: Method[] methods = cl.getDeclaredMethods();
419: int len = methods == null ? 0 : methods.length;
420: for (int i = 0; i < len; i++) {
421: ESMethodDescriptor md = info.createMethodDescriptor(
422: methods[i], false);
423:
424: if ((mask & METHOD) != 0)
425: info.addMethod(md);
426: if ((mask & PROPERTY) != 0)
427: analyzeProperty(info, cl, md, false);
428: }
429:
430: Field[] fields = cl.getDeclaredFields();
431: for (int i = 0; fields != null && i < fields.length; i++) {
432: info.addField(fields[i]);
433: }
434: }
435:
436: addEcmaWrap(info, cl, mask);
437: }
438: }
|