/* * 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 java.beans.IntrospectionException; import java.beans.MethodDescriptor; import java.beans.PropertyDescriptor; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; /** * Full analyzed information on the class as a JavaScript object. */ public class ESBeanInfo { static String BAD = "bad"; Class cl; HashMap<String,ArrayList<Method>> _methodMap; HashMap<String,ArrayList<Method>> _staticMethodMap; HashMap propMap; ArrayList nonPkgClasses = new ArrayList(); PropertyDescriptor []propertyDescriptors; ESMethodDescriptor iterator; ESBeanInfo(Class cl) { this.cl = cl; _methodMap = new HashMap<String,ArrayList<Method>>(); _staticMethodMap = new HashMap<String,ArrayList<Method>>(); propMap = new HashMap(); } void addNonPkgClass(String name) { if (name.indexOf('$') >= 0) return; if (! nonPkgClasses.contains(name)) nonPkgClasses.add(name); } ArrayList getNonPkgClasses() { return nonPkgClasses; } /** * Return the property descriptors for the bean. */ public PropertyDescriptor []getPropertyDescriptors() { if (propertyDescriptors == null) propertyDescriptors = propMapToArray(propMap); return propertyDescriptors; } private static PropertyDescriptor []propMapToArray(HashMap props) { int count = 0; Iterator i = props.keySet().iterator(); while (i.hasNext()) { String key = (String) i.next(); Object value = props.get(key); if (value != BAD) count++; } PropertyDescriptor []descriptors = new PropertyDescriptor[count]; count = 0; i = props.keySet().iterator(); while (i.hasNext()) { String key = (String) i.next(); Object value = props.get(key); if (value != BAD) { descriptors[count] = (PropertyDescriptor) value; count++; } } return descriptors; } void addProp(String name, Field field, ESMethodDescriptor getter, ESMethodDescriptor setter, boolean overwrite) throws IntrospectionException { Object value = propMap.get(name); if (value instanceof ESPropertyDescriptor && ! (value instanceof ESIndexedPropertyDescriptor) && ! (value instanceof NamedPropertyDescriptor)) { ESPropertyDescriptor prop = (ESPropertyDescriptor) value; if (field != null) prop.field = field; if (getter != null) prop.getter = getter; if (setter != null) prop.setter = setter; propMap.put(name, prop); } else if (value == null || overwrite) { propMap.put(name, new ESPropertyDescriptor(name, field, getter, setter)); } else { propMap.put(name, BAD); } } void addProp(String name, Field field, ESMethodDescriptor getter, ESMethodDescriptor setter) throws IntrospectionException { addProp(name, field, getter, setter, false); } void addIndexedProp(String name, ESMethodDescriptor getter, ESMethodDescriptor setter, ESMethodDescriptor size, boolean overwrite) throws IntrospectionException { Object value = propMap.get(name); if (value instanceof ESIndexedPropertyDescriptor) { ESIndexedPropertyDescriptor prop = (ESIndexedPropertyDescriptor) value; if (getter != null) prop.getter = getter; if (setter != null) prop.setter = setter; if (size != null) prop.size = size; propMap.put(name, prop); } else if (value == null || overwrite) { propMap.put(name, new ESIndexedPropertyDescriptor(name, getter, setter, size)); } else propMap.put(name, BAD); } void addIndexedProp(String name, ESMethodDescriptor getter, ESMethodDescriptor setter, ESMethodDescriptor size) throws IntrospectionException { addIndexedProp(name, getter, setter, size, false); } void addNamedProp(String name, ESMethodDescriptor getter, ESMethodDescriptor setter, ESMethodDescriptor remover, ESMethodDescriptor iterator, boolean overwrite) throws IntrospectionException { Object value = propMap.get(name); if (value instanceof NamedPropertyDescriptor) { NamedPropertyDescriptor prop = (NamedPropertyDescriptor) value; if (getter != null) prop.namedGetter = getter; if (setter != null) prop.namedSetter = setter; if (remover != null) prop.namedRemover = remover; if (iterator != null) prop.namedIterator = iterator; } else if (value == null || overwrite) { try { propMap.put(name, new NamedPropertyDescriptor(name, null, null, getter, setter, remover, iterator)); } catch (Exception e) { propMap.put(name, BAD); } } else propMap.put(name, BAD); } void addNamedProp(String name, ESMethodDescriptor getter, ESMethodDescriptor setter, ESMethodDescriptor remover, ESMethodDescriptor iterator) throws IntrospectionException { addNamedProp(name, getter, setter, remover, iterator, false); } /** * Returns the methods matching the given name. */ public ArrayList<Method> getMethods(String name) { return _methodMap.get(name); } /** * Returns the static methods matching the given name. */ public ArrayList<Method> getStaticMethods(String name) { return _staticMethodMap.get(name); } public ArrayList getConstructors() { ArrayList overload = new ArrayList(); if (! Modifier.isPublic(cl.getModifiers())) return overload; // non-static inner classes have no constructor if (cl.getDeclaringClass() != null && ! Modifier.isStatic(cl.getModifiers())) return overload; Constructor []constructors = cl.getConstructors(); for (int i = 0; i < constructors.length; i++) { if (! Modifier.isPublic(constructors[i].getModifiers())) continue; if (Modifier.isPublic(constructors[i].getModifiers())) addConstructor(overload, constructors[i]); } return overload; } private void addConstructor(ArrayList overload, Constructor constructor) { int modifiers = constructor.getModifiers(); if (! Modifier.isPublic(modifiers)) return; Class []params = constructor.getParameterTypes(); for (int i = 0; i < params.length; i++) { if (! Modifier.isPublic(params[i].getModifiers())) return; if (! params[i].isPrimitive() && ! params[i].isArray() && params[i].getName().indexOf('.') < 0) addNonPkgClass(params[i].getName()); } int length = params.length; Object oldConstructor; if (length < overload.size() && (oldConstructor = overload.get(length)) != null) { overload.set(length, BAD); } else { while (overload.size() <= length) overload.add(null); overload.set(length, constructor); } } /** * Create a new method descriptor. */ ESMethodDescriptor createMethodDescriptor(Method method, boolean overwrite) throws IntrospectionException { boolean staticVirtual = isStaticVirtual(cl, method); return new ESMethodDescriptor(method, overwrite, staticVirtual); } /** * Returns true if this is a static virtual method. * Static virtual methods are used by FooEcmaWrap classes add new methods * to a class. * * <p>The method is always static and the first argument has the class * of the overwriting class. */ private static boolean isStaticVirtual(Class cl, Method method) { int modifiers = method.getModifiers(); if (! Modifier.isStatic(modifiers)) return false; if (method.getName().equals("<init>")) return false; if (! method.getDeclaringClass().getName().endsWith("EcmaWrap")) return false; Class []params = method.getParameterTypes(); boolean result = params.length > 0 && params[0].equals(cl); return result; } /** * Adds a new method to the bean info, changing the overwrite property. * * @param oldMd the old method descriptor. * @param boolean true if overwritable. */ void addMethod(MethodDescriptor oldMd, boolean overwrite) throws IntrospectionException { ESMethodDescriptor md = createMethodDescriptor(oldMd.getMethod(), overwrite); md.setName(oldMd.getName()); addMethod(_methodMap, md, false); addMethod(_staticMethodMap, md, true); } /** * Adds a new method to the bean info. * * @param me the method descriptor. */ void addMethod(ESMethodDescriptor md) throws IntrospectionException { addMethod(_methodMap, md, false); addMethod(_staticMethodMap, md, true); } void addMethods(ESBeanInfo info) throws IntrospectionException { if (info.iterator != null) { iterator = info.iterator; } addMap(_methodMap, info._methodMap, false); addMap(_staticMethodMap, info._staticMethodMap, true); } /** * Merges two method maps together to produce a new map. */ private void addMap(HashMap newMap, HashMap oldMap, boolean isStatic) { Iterator i = oldMap.entrySet().iterator(); while (i.hasNext()) { Map.Entry entry = (Map.Entry) i.next(); ArrayList overload = (ArrayList) entry.getValue(); for (int j = 0; j < overload.size(); j++) { ESMethodDescriptor []mds = (ESMethodDescriptor []) overload.get(j); if (mds != null) { for (int k = 0; k < mds.length; k++) addMethod(newMap, mds[k], isStatic); } } } } /** * Adds a method to the method map. * * @param map the method map to modify. * @param md the method descriptor of the new method. * @param isStatic true if this is a static method. */ private void addMethod(HashMap map, ESMethodDescriptor md, boolean isStatic) { Method method = md.getMethod(); int modifiers = method.getModifiers(); boolean staticVirtual = md.isStaticVirtual(); boolean overwrite = md.isOverwrite(); if (! Modifier.isPublic(modifiers)) return; if (isStatic && ! md.isStatic()) return; Class []params = md.getParameterTypes(); for (int i = 0; i < params.length; i++) { if (! Modifier.isPublic(params[i].getModifiers())) return; if (! params[i].isPrimitive() && ! params[i].isArray() && params[i].getName().indexOf('.') < 0) addNonPkgClass(params[i].getName()); } ArrayList overload = (ArrayList) map.get(md.getName()); if (overload == null) { overload = new ArrayList(); map.put(md.getName(), overload); } int length = params.length; if (length >= overload.size()) { while (overload.size() <= length) overload.add(null); } ESMethodDescriptor []oldMethods; oldMethods = (ESMethodDescriptor []) overload.get(length); if (oldMethods == null) { overload.set(length, new ESMethodDescriptor[] { md }); return; } for (int i = 0; i < oldMethods.length; i++) { ESMethodDescriptor testMd = oldMethods[i]; if (testMd.overwrites(md)) return; else if (md.overwrites(testMd)) { oldMethods[i] = md; return; } Class []oldParams = testMd.getParameterTypes(); int j = 0; for (; j < length; j++) { if (! params[j].equals(oldParams[j])) break; } // duplicates another method if (j == length && md.isOverwrite()) { oldMethods[i] = md; return; } else if (j == length) { return; } } ESMethodDescriptor []newMethods; newMethods = new ESMethodDescriptor[oldMethods.length + 1]; System.arraycopy(oldMethods, 0, newMethods, 0, oldMethods.length); newMethods[oldMethods.length] = md; overload.set(length, newMethods); } private void addPropMap(HashMap oldMap) throws IntrospectionException { Iterator i = oldMap.entrySet().iterator(); while (i.hasNext()) { Map.Entry entry = (Map.Entry) i.next(); Object value = entry.getValue(); if (value == BAD) { // XXX: else add bad? continue; } if (value instanceof NamedPropertyDescriptor) { NamedPropertyDescriptor pd = (NamedPropertyDescriptor) value; addNamedProp(pd.getName(), pd.namedGetter, pd.namedSetter, pd.namedRemover, pd.namedIterator); } else if (value instanceof ESIndexedPropertyDescriptor) { ESIndexedPropertyDescriptor pd = (ESIndexedPropertyDescriptor) value; addIndexedProp(pd.getName(), pd.getESReadMethod(), pd.getESWriteMethod(), pd.getESSizeMethod()); } else if (value instanceof ESPropertyDescriptor) { ESPropertyDescriptor pd = (ESPropertyDescriptor) value; addProp(pd.getName(), pd.field, pd.getter, pd.setter); } // XXX: else add bad? } } void addField(Field field) throws IntrospectionException { int modifiers = field.getModifiers(); if (! Modifier.isPublic(modifiers)) return; addProp(field.getName(), field, null, null); } void addProps(ESBeanInfo info) throws IntrospectionException { addPropMap(info.propMap); } }