/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * Free SoftwareFoundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.es.wrapper; import com.caucho.server.util.CauchoSystem; import com.caucho.util.WeakLruCache; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.MethodDescriptor; import java.beans.PropertyDescriptor; import java.lang.ref.SoftReference; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; /** * Analyzes the class from a JavaScript perspective. * * <p>Each class Foo searches for its "FooEcmaWrap" to see if there are * any method changes. */ public class ESIntrospector { /** * Cache of analyzed classes to avoid duplication. */ static WeakLruCache<Class,SoftReference<ESBeanInfo>> _beanMap = new WeakLruCache<Class,SoftReference<ESBeanInfo>>(256); static Integer NULL = new Integer(0); final static int METHOD = 1; final static int PROPERTY = 2; final static int MASK = 3; static String []path = new String[] {"", "com.caucho.eswrap"}; /** * Analyzes the class, returning the calculated ESBeanInfo. * * @param cl the class to be analyzed. * @return the analyzed bean info. */ public static ESBeanInfo getBeanInfo(Class cl) throws IntrospectionException { ESBeanInfo info; SoftReference<ESBeanInfo> infoRef = _beanMap.get(cl); info = infoRef != null ? infoRef.get() : null; if (info != null) return info; info = new ESBeanInfo(cl); getMethods(info, cl); _beanMap.put(cl, new SoftReference<ESBeanInfo>(info)); return info; } /** * Analyzes a Java method, converting it to any equivalent JavaScript * properties. * * <pre> * keys() -> for (item in obj) { * getFoo() -> obj.foo * getFoo(int) -> obj.foo[i] * getFoo(String) -> obj.foo["name"] * remove(String) -> delete obj.foo * </pre> * * @param info the bean info to be filled * @param cl the bean's class * @param md the method descriptor * @param overwrite if true, this overwrites any previous definition */ private static void analyzeProperty(ESBeanInfo info, Class cl, ESMethodDescriptor md, boolean overwrite) throws IntrospectionException { Method method = md.getMethod(); int modifiers = method.getModifiers(); if (! Modifier.isPublic(modifiers)) return; String name = method.getName(); String propName; Class returnType = method.getReturnType(); String returnName = returnType.getName(); Class []params = md.getParameterTypes(); if (name.equals("keys") && params.length == 0 && (returnName.equals("java.util.Iterator") || (returnName.equals("java.util.Enumeration")))) { info.iterator = md; } else if (name.equals("iterator") && params.length == 0 && (returnName.equals("java.util.Iterator") || (returnName.equals("java.util.Enumeration")))) { // keys has priority over iterator if (info.iterator == null) info.iterator = md; } else if (name.startsWith("get") && ! name.equals("get")) { propName = Introspector.decapitalize(name.substring(3)); // kill match? if (returnName.equals("void") && params.length < 2) { // XXX: props.put(propName, BAD); return; } // name keys if (params.length == 0 && (propName.endsWith("Keys") && ! propName.equals("Keys") || propName.endsWith("Names") && ! propName.equals("Names")) && (returnName.equals("java.util.Iterator") || (returnName.equals("java.util.Enumeration")))) { if (propName.endsWith("Keys")) info.addNamedProp(propName.substring(0, propName.length() - 4), null, null, null, md); else info.addNamedProp(propName.substring(0, propName.length() - 5), null, null, null, md); } // index length else if (params.length == 0 && (propName.endsWith("Size") && ! propName.equals("Size") || propName.endsWith("Length") && ! propName.equals("Length")) && (returnName.equals("int"))) { info.addProp(propName, null, md, null); if (propName.endsWith("Size")) info.addIndexedProp(propName.substring(0, propName.length() - 4), null, null, md); else info.addIndexedProp(propName.substring(0, propName.length() - 6), null, null, md); } else if (params.length == 0) info.addProp(propName, null, md, null); else if (params.length == 1 && params[0].getName().equals("java.lang.String")) { info.addNamedProp(propName, md, null, null, null); } else if (params.length == 1 && params[0].getName().equals("int")) { info.addIndexedProp(propName, md, null, null); } else if (params.length == 1) { // xxx: add bad? } } else if (name.startsWith("set") && ! name.equals("set")) { propName = Introspector.decapitalize(name.substring(3)); if (params.length == 0) return; // kill match? if (! returnType.getName().equals("void") && params.length < 3) { // XXX: add bad? props.put(propName, NULL); return; } if (params.length == 1) info.addProp(propName, null, null, md); else if (params.length == 2 && params[0].getName().equals("java.lang.String")) { info.addNamedProp(propName, null, md, null, null); } else if (params.length == 2 && params[0].getName().equals("int")) { info.addIndexedProp(propName, null, md, null); } else if (params.length == 2) { // XXX: props.put(propName, NULL); } } else if (name.startsWith("remove") && ! name.equals("remove") || name.startsWith("delete") && ! name.equals("remove")) { propName = Introspector.decapitalize(name.substring(6)); if (params.length == 0) return; // kill match? if (! returnType.getName().equals("void") && params.length < 2) { // XXX: add bad? props.put(propName, NULL); return; } //if (params.length == 0) // info.addProp(propName, null, null, md); if (params.length == 1 && params[0].getName().equals("java.lang.String")) { info.addNamedProp(propName, null, null, md, null); } /* else if (params.length == 2 && params[0].getName().equals("int")) { info.addIndexedProp(propName, null, md, null); } else if (params.length == 2) { // XXX: props.put(propName, NULL); } */ } } /** * Add methods from a FooEcmaWrap class. */ static void addEcmaMethods(ESBeanInfo info, Class cl, Class wrapCl, int mask) throws IntrospectionException { Method []methods = wrapCl.getDeclaredMethods(); for (int i = 0; i < methods.length; i++) { int modifiers = methods[i].getModifiers(); if (! Modifier.isStatic(modifiers)) continue; ESMethodDescriptor md = info.createMethodDescriptor(methods[i], true); if ((mask & METHOD) != 0) info.addMethod(md); if ((mask & PROPERTY) != 0) analyzeProperty(info, cl, md, true); } } /** * Search for FooEcmaWrap classes. * * @param info the bean info to be filled * @param cl the bean being analyzed * @param mask the replacement mask. */ static void addEcmaWrap(ESBeanInfo info, Class cl, int mask) throws IntrospectionException { String name = cl.getName(); int lastDot = name.lastIndexOf('.'); String prefix = lastDot == -1 ? "" : name.substring(0, lastDot); String tail = lastDot == -1 ? name : name.substring(lastDot + 1); ClassLoader loader = cl.getClassLoader(); for (int i = 0; i < path.length; i++) { String testName; if (path[i].equals("")) testName = name + "EcmaWrap"; else testName = path[i] + "." + name + "EcmaWrap"; try { Class wrapCl = CauchoSystem.loadClass(testName, false, loader); if (wrapCl != null) { addEcmaMethods(info, cl, wrapCl, mask); return; } } catch (ClassNotFoundException e) { } if (path[i].equals("")) testName = tail + "EcmaWrap"; else testName = path[i] + "." + tail + "EcmaWrap"; try { Class wrapCl = CauchoSystem.loadClass(testName, false, loader); if (wrapCl != null) { addEcmaMethods(info, cl, wrapCl, mask); return; } } catch (ClassNotFoundException e) { } } } /** * Fills information for a FooBeanInfo class. * * @param info the result information object * @param cl the bean class * * @return a mask */ static int getBeanInfo(ESBeanInfo info, Class cl) { try { String name = cl.getName() + "BeanInfo"; Class beanClass = CauchoSystem.loadClass(name, false, cl.getClassLoader()); if (beanClass == null) return MASK; BeanInfo beanInfo = (BeanInfo) beanClass.newInstance(); MethodDescriptor []mds = beanInfo.getMethodDescriptors(); if (mds == null) return MASK; for (int i = 0; i < mds.length; i++) { Method method = mds[i].getMethod(); int modifiers = method.getModifiers(); if (! Modifier.isStatic(modifiers) && ! method.getDeclaringClass().isAssignableFrom(cl)) continue; info.addMethod(mds[i], true); } return MASK & ~METHOD; } catch (Exception e) { return MASK; } } static void getPropBeanInfo(ESBeanInfo info, Class cl) { try { String name = cl.getName() + "BeanInfo"; Class beanClass = CauchoSystem.loadClass(name, false, cl.getClassLoader()); if (beanClass == null) return; BeanInfo beanInfo = (BeanInfo) beanClass.newInstance(); PropertyDescriptor []props = beanInfo.getPropertyDescriptors(); for (int i = 0; props != null && i < props.length; i++) { ESMethodDescriptor read; ESMethodDescriptor write; read = new ESMethodDescriptor(props[i].getReadMethod(), false, false); write = new ESMethodDescriptor(props[i].getWriteMethod(), false, false); info.addProp(props[i].getName(), null, read, write, true); } } catch (Exception e) { } } private static void getMethods(ESBeanInfo info, Class cl) throws IntrospectionException { if (! cl.isPrimitive() && ! cl.isArray() && cl.getName().indexOf('.') < 0) info.addNonPkgClass(cl.getName()); getPropBeanInfo(info, cl); int mask = getBeanInfo(info, cl); if (mask == 0) return; Class []interfaces = cl.getInterfaces(); for (int i = 0; i < interfaces.length; i++) { ESBeanInfo subInfo = getBeanInfo(interfaces[i]); if ((mask & METHOD) != 0) info.addMethods(subInfo); if ((mask & PROPERTY) != 0) info.addProps(subInfo); } Class superClass = cl.getSuperclass(); if (superClass != null) { ESBeanInfo subInfo = getBeanInfo(superClass); if ((mask & METHOD) != 0) info.addMethods(subInfo); if ((mask & PROPERTY) != 0) info.addProps(subInfo); } int modifiers = cl.getModifiers(); if (Modifier.isPublic(modifiers)) { Method []methods = cl.getDeclaredMethods(); int len = methods == null ? 0 : methods.length; for (int i = 0; i < len; i++) { ESMethodDescriptor md = info.createMethodDescriptor(methods[i], false); if ((mask & METHOD) != 0) info.addMethod(md); if ((mask & PROPERTY) != 0) analyzeProperty(info, cl, md, false); } Field []fields = cl.getDeclaredFields(); for (int i = 0; fields != null && i < fields.length; i++) { info.addField(fields[i]); } } addEcmaWrap(info, cl, mask); } }