/*
* 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);
}
}