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