/*
* Copyright (c) 1998-2008 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 Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.quercus.program;
import com.caucho.quercus.Quercus;
import com.caucho.quercus.QuercusModuleException;
import com.caucho.quercus.QuercusException;
import com.caucho.quercus.QuercusRuntimeException;
import com.caucho.quercus.annotation.*;
import com.caucho.quercus.env.*;
import com.caucho.quercus.expr.Expr;
import com.caucho.quercus.expr.LiteralExpr;
import com.caucho.quercus.function.AbstractFunction;
import com.caucho.quercus.marshal.JavaMarshal;
import com.caucho.quercus.marshal.Marshal;
import com.caucho.quercus.marshal.MarshalFactory;
import com.caucho.quercus.module.ModuleContext;
import com.caucho.util.L10N;
import com.caucho.vfs.WriteStream;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Represents an introspected Java class.
*/
public class JavaClassDef<T> extends ClassDef {
private final static Logger log
= Logger.getLogger(JavaClassDef.class.getName());
private final static L10N L = new L10N(JavaClassDef.class);
private final ModuleContext _moduleContext;
private final String _name;
private final Class<T> _type;
private QuercusClass _quercusClass;
private HashSet<String> _instanceOfSet;
private HashSet<String> _instanceOfSetLowerCase;
private final boolean _isAbstract;
private final boolean _isInterface;
private final boolean _isDelegate;
private String _resourceType;
private JavaClassDef _componentDef;
protected volatile boolean _isInit;
private final HashMap<String, Value> _constMap
= new HashMap<String, Value>();
private final MethodMap<AbstractJavaMethod> _functionMap
= new MethodMap<AbstractJavaMethod>();
private final HashMap<StringValue, AbstractJavaMethod> _getMap
= new HashMap<StringValue, AbstractJavaMethod>();
private final HashMap<StringValue, AbstractJavaMethod> _setMap
= new HashMap<StringValue, AbstractJavaMethod>();
// _fieldMap stores all public non-static fields
// used by getField and setField
private final HashMap<StringValue, FieldMarshalPair> _fieldMap
= new HashMap<StringValue, FieldMarshalPair> ();
private AbstractJavaMethod _cons;
private AbstractJavaMethod __construct;
private JavaMethod __fieldGet;
private JavaMethod __fieldSet;
private FunctionArrayDelegate _funArrayDelegate;
private ArrayDelegate _arrayDelegate;
private JavaMethod __call;
private JavaMethod __toString;
private Method _printRImpl;
private Method _varDumpImpl;
private Method _entrySet;
private TraversableDelegate _traversableDelegate;
private CountDelegate _countDelegate;
private Method _iteratorMethod;
private Marshal _marshal;
private String _extension;
public JavaClassDef(ModuleContext moduleContext, String name, Class<T> type)
{
super(null, name, null, new String[] {});
_moduleContext = moduleContext;
_name = name;
_type = type;
_isAbstract = Modifier.isAbstract(type.getModifiers());
_isInterface = type.isInterface();
_isDelegate = type.isAnnotationPresent(ClassImplementation.class);
if (type.isArray() && ! isArray())
throw new IllegalStateException(L.l("'{0}' needs to be called with JavaArrayClassDef",
type));
}
public JavaClassDef(ModuleContext moduleContext,
String name,
Class<T> type,
String extension)
{
this(moduleContext, name, type);
_extension = extension;
moduleContext.addExtensionClass(extension, name);
}
private void fillInstanceOfSet(Class type, boolean isTop)
{
if (type == null)
return;
if (isTop && _isDelegate) {
_instanceOfSet.add(_name);
_instanceOfSetLowerCase.add(_name.toLowerCase());
}
else {
String name = type.getSimpleName();
_instanceOfSet.add(name);
_instanceOfSetLowerCase.add(name.toLowerCase());
}
fillInstanceOfSet(type.getSuperclass(), false);
Class []ifaceList = type.getInterfaces();
if (ifaceList != null) {
for (Class iface : ifaceList)
fillInstanceOfSet(iface, false);
}
}
@SuppressWarnings("unchecked")
public static JavaClassDef create(ModuleContext moduleContext,
String name, Class<?> type)
{
if (Double.class.isAssignableFrom(type)
|| Float.class.isAssignableFrom(type))
return new DoubleClassDef(moduleContext);
else if (Long.class.isAssignableFrom(type)
|| Integer.class.isAssignableFrom(type)
|| Short.class.isAssignableFrom(type)
|| Byte.class.isAssignableFrom(type))
return new LongClassDef(moduleContext);
else if (BigDecimal.class.isAssignableFrom(type))
return new BigDecimalClassDef(moduleContext);
else if (BigInteger.class.isAssignableFrom(type))
return new BigIntegerClassDef(moduleContext);
else if (String.class.isAssignableFrom(type)
|| Character.class.isAssignableFrom(type))
return new StringClassDef(moduleContext);
else if (Boolean.class.isAssignableFrom(type))
return new BooleanClassDef(moduleContext);
else if (Calendar.class.isAssignableFrom(type))
return new CalendarClassDef(moduleContext);
else if (Date.class.isAssignableFrom(type))
return new DateClassDef(moduleContext);
else if (URL.class.isAssignableFrom(type))
return new URLClassDef(moduleContext);
else if (Map.class.isAssignableFrom(type))
return new JavaMapClassDef<Object,Object>(moduleContext, name, (Class<Map<Object,Object>>)type);
else if (List.class.isAssignableFrom(type))
return new JavaListClassDef<Object>(moduleContext, name, (Class<List<Object>>) type);
else if (Collection.class.isAssignableFrom(type)
&& ! Queue.class.isAssignableFrom(type))
return new JavaCollectionClassDef<Object>(moduleContext, name, (Class<Collection<Object>>)type);
else
return null;
}
/**
* Returns the class name.
*/
@Override
public String getName()
{
return _name;
}
/**
* Returns the class name.
*/
public String getSimpleName()
{
return _type.getSimpleName();
}
public Class getType()
{
return _type;
}
/*
* Returns the type of this resource.
*/
public String getResourceType()
{
return _resourceType;
}
protected ModuleContext getModuleContext()
{
return _moduleContext;
}
/*
* Returns the name of the extension that this class is part of.
*/
@Override
public String getExtension()
{
return _extension;
}
@Override
public boolean isA(String name)
{
if (_instanceOfSet == null) {
_instanceOfSet = new HashSet<String>();
_instanceOfSetLowerCase = new HashSet<String>();
fillInstanceOfSet(_type, true);
}
return (_instanceOfSet.contains(name)
|| _instanceOfSetLowerCase.contains(name.toLowerCase()));
}
/**
* Adds the interfaces to the set
*/
@Override
public void addInterfaces(HashSet<String> interfaceSet)
{
addInterfaces(interfaceSet, _type, true);
}
protected void addInterfaces(HashSet<String> interfaceSet,
Class type,
boolean isTop)
{
if (type == null || Object.class.equals(type))
return;
if (isTop)
interfaceSet.add(_name.toLowerCase());
else
interfaceSet.add(type.getSimpleName().toLowerCase());
if (type.getInterfaces() != null) {
for (Class iface : type.getInterfaces()) {
addInterfaces(interfaceSet, iface, false);
}
}
// php/1z21
addInterfaces(interfaceSet, type.getSuperclass(), false);
}
@Override
public boolean isAbstract()
{
return _isAbstract;
}
public boolean isArray()
{
return false;
}
@Override
public boolean isInterface()
{
return _isInterface;
}
public boolean isDelegate()
{
return _isDelegate;
}
public JavaClassDef getComponentDef()
{
if (_componentDef == null) {
Class compType = getType().getComponentType();
_componentDef = _moduleContext.getJavaClassDefinition(compType.getName());
}
return _componentDef;
}
public Value wrap(Env env, T obj)
{
if (! _isInit)
init();
if (_resourceType != null)
return new JavaResourceValue<T>(env, obj, this);
else
return new JavaValue<T>(env, obj, this);
}
@SuppressWarnings("unchecked")
private int cmpObject(Object lValue, Object rValue)
{
if (lValue == rValue)
return 0;
if (lValue == null)
return -1;
if (rValue == null)
return 1;
if (lValue instanceof Comparable) {
if (!(rValue instanceof Comparable))
return -1;
return ((Comparable<Object>) lValue).compareTo(rValue);
}
else if (rValue instanceof Comparable) {
return 1;
}
if (lValue.equals(rValue))
return 0;
String lName = lValue.getClass().getName();
String rName = rValue.getClass().getName();
return lName.compareTo(rName);
}
public int cmpObject(Object lValue, Object rValue, JavaClassDef<?> rClassDef)
{
int cmp = cmpObject(lValue, rValue);
if (cmp != 0)
return cmp;
// attributes
// XX: not sure how to do this, to imitate PHP objects,
// should getters be involved as well?
for (Map.Entry<StringValue, FieldMarshalPair> lEntry : _fieldMap.entrySet()) {
StringValue lFieldName = lEntry.getKey();
FieldMarshalPair rFieldPair = rClassDef._fieldMap.get(lFieldName);
if (rFieldPair == null)
return 1;
FieldMarshalPair lFieldPair = lEntry.getValue();
try {
Object lResult = lFieldPair._field.get(lValue);
Object rResult = rFieldPair._field.get(lValue);
int resultCmp = cmpObject(lResult, rResult);
if (resultCmp != 0)
return resultCmp;
}
catch (IllegalAccessException e) {
log.log(Level.FINE, L.l(e.getMessage()), e);
return 0;
}
}
return 0;
}
/**
* Returns the field getter.
*
* @param name
* @return Value attained through invoking getter
*/
public Value getField(Env env, Value qThis, StringValue name)
{
AbstractJavaMethod get = _getMap.get(name);
if (get != null) {
try {
return get.callMethod(env, qThis);
} catch (Exception e) {
log.log(Level.FINE, L.l(e.getMessage()), e);
return null;
}
}
FieldMarshalPair fieldPair = _fieldMap.get(name);
if (fieldPair != null) {
try {
Object result = fieldPair._field.get(qThis.toJavaObject());
return fieldPair._marshal.unmarshal(env, result);
} catch (Exception e) {
log.log(Level.FINE, L.l(e.getMessage()), e);
return null;
}
}
if (__fieldGet != null) {
try {
return __fieldGet.callMethod(env, qThis, name);
} catch (Exception e) {
log.log(Level.FINE, L.l(e.getMessage()), e);
return null;
}
}
return null;
}
public Value putField(Env env,
Value qThis,
StringValue name,
Value value)
{
AbstractJavaMethod setter = _setMap.get(name);
if (setter != null) {
try {
return setter.callMethod(env, qThis, value);
} catch (Exception e) {
log.log(Level.FINE, L.l(e.getMessage()), e);
return NullValue.NULL;
}
}
FieldMarshalPair fieldPair = _fieldMap.get(name);
if (fieldPair != null) {
try {
Class<?> type = fieldPair._field.getType();
Object marshaledValue = fieldPair._marshal.marshal(env, value, type);
fieldPair._field.set(qThis.toJavaObject(), marshaledValue);
return value;
} catch (Exception e) {
log.log(Level.FINE, L.l(e.getMessage()), e);
return NullValue.NULL;
}
}
if (__fieldSet != null) {
try {
return __fieldSet.callMethod(env, qThis, name, value);
} catch (Exception e) {
log.log(Level.FINE, L.l(e.getMessage()), e);
return NullValue.NULL;
}
}
return null;
}
/**
* Returns the marshal instance.
*/
public Marshal getMarshal()
{
return _marshal;
}
/**
* Creates a new instance.
*/
@Override
public ObjectValue newInstance(Env env, QuercusClass qClass)
{
// return newInstance();
return null;
}
public Value newInstance()
{
return null;
/*
try {
//Object obj = _type.newInstance();
return new JavaValue(null, _type.newInstance(), this);
} catch (Exception e) {
throw new QuercusRuntimeException(e);
}
*/
}
/**
* Eval new
*/
@Override
public Value callNew(Env env, Value []args)
{
if (_cons != null) {
Value value = _cons.callMethod(env, null, args);
if (__construct != null)
__construct.callMethod(env, value, args);
return value;
}
else
return NullValue.NULL;
}
/**
* Returns the matching method.
*/
public AbstractFunction findFunction(String name)
{
return _functionMap.get(name);
}
/**
* Returns the __call.
*/
public AbstractFunction getCallMethod()
{
return __call;
}
/**
* Eval a method
*/
public Value callMethod(Env env, Value qThis,
int hash, char []name, int nameLen,
Expr []args)
{
AbstractJavaMethod method = _functionMap.get(hash, name, nameLen);
if (method == null) {
env.warning(L.l("{0}::{1} is an unknown method.",
_name, toMethod(name, nameLen)));
return NullValue.NULL;
}
return method.callMethod(env, qThis, args);
}
/**
* Eval a method
*/
public Value callMethod(Env env, Value qThis,
int hash, char []name, int nameLen,
Value []args)
{
AbstractJavaMethod method = _functionMap.get(hash, name, nameLen);
if (method != null)
return method.callMethod(env, qThis, args);
else if (__call != null) {
Value []extArgs = new Value[args.length + 1];
extArgs[0] = env.createString(name, nameLen);
System.arraycopy(args, 0, extArgs, 1, args.length);
return __call.callMethod(env, qThis, extArgs);
}
else {
env.error(L.l("'{0}::{1}' is an unknown method",
_name, toMethod(name, nameLen)));
return NullValue.NULL;
}
}
/**
* Eval a method
*/
public Value callMethod(Env env, Value qThis,
int hash, char []name, int nameLen)
{
AbstractJavaMethod method = _functionMap.get(hash, name, nameLen);
if (method != null)
return method.callMethod(env, qThis);
else if (__call != null)
return __call.callMethod(env, qThis, env.createString(name, nameLen));
else {
env.error(L.l("'{0}::{1}()' is an unknown method",
_name, toMethod(name, nameLen)));
return NullValue.NULL;
}
}
/**
* Eval a method
*/
public Value callMethod(Env env, Value qThis,
int hash, char []name, int nameLen,
Value a1)
{
AbstractJavaMethod method = _functionMap.get(hash, name, nameLen);
if (method != null)
return method.callMethod(env, qThis, a1);
else if (__call != null)
return __call.callMethod(env, qThis, env.createString(name, nameLen), a1);
else {
env.error(L.l("'{0}::{1}(a1)' is an unknown method",
_name, toMethod(name, nameLen)));
return NullValue.NULL;
}
}
/**
* Eval a method
*/
public Value callMethod(Env env, Value qThis,
int hash, char []name, int nameLen,
Value a1, Value a2)
{
AbstractJavaMethod method = _functionMap.get(hash, name, nameLen);
if (method != null)
return method.callMethod(env, qThis, a1, a2);
else if (__call != null)
return __call.callMethod(env, qThis, env.createString(name, nameLen),
a1, a2);
else {
env.error(L.l("'{0}::{1}(a1,a2)' is an unknown method",
_name, toMethod(name, nameLen)));
return NullValue.NULL;
}
}
/**
* Eval a method
*/
public Value callMethod(Env env, Value qThis,
int hash, char []name, int nameLen,
Value a1, Value a2, Value a3)
{
AbstractJavaMethod method = _functionMap.get(hash, name, nameLen);
if (method != null)
return method.callMethod(env, qThis, a1, a2, a3);
else if (__call != null)
return __call.callMethod(env, qThis, env.createString(name, nameLen),
a1, a2, a3);
else {
env.error(L.l("'{0}::{1}(a1,a2,a3)' is an unknown method",
_name, toMethod(name, nameLen)));
return NullValue.NULL;
}
}
/**
* Eval a method
*/
public Value callMethod(Env env, Value qThis,
int hash, char []name, int nameLen,
Value a1, Value a2, Value a3, Value a4)
{
AbstractJavaMethod method = _functionMap.get(hash, name, nameLen);
if (method != null)
return method.callMethod(env, qThis, a1, a2, a3, a4);
else if (__call != null)
return __call.callMethod(env, qThis, env.createString(name, nameLen),
a1, a2, a3, a4);
else {
env.error(L.l("'{0}::{1}(a1,a2,a3,a4)' is an unknown method",
_name, toMethod(name, nameLen)));
return NullValue.NULL;
}
}
/**
* Eval a method
*/
public Value callMethod(Env env, Value qThis,
int hash, char []name, int nameLen,
Value a1, Value a2, Value a3, Value a4, Value a5)
{
AbstractJavaMethod method = _functionMap.get(hash, name, nameLen);
if (method != null)
return method.callMethod(env, qThis, a1, a2, a3, a4, a5);
else if (__call != null)
return __call.callMethod(env, qThis,
new Value[] { env.createString(name, nameLen),
a1, a2, a3, a4, a5 });
else {
env.error(L.l("'{0}::{1}(a1,a2,a3,a4,a5)' is an unknown method",
_name, toMethod(name, nameLen)));
return NullValue.NULL;
}
}
private String toMethod(char []name, int nameLen)
{
return new String(name, 0, nameLen);
}
@SuppressWarnings("unchecked")
public Set<Map.Entry<Value,Value>> entrySet(Object obj)
{
try {
if (_entrySet == null) {
return null;
}
return (Set<Map.Entry<Value,Value>>) _entrySet.invoke(obj);
} catch (Exception e) {
throw new QuercusException(e);
}
}
/**
* Initialize the quercus class.
*/
@Override
public void initClass(QuercusClass cl)
{
init();
if (_cons != null) {
cl.setConstructor(_cons);
cl.addMethod("__construct", _cons);
}
if (__construct != null) {
cl.addMethod("__construct", __construct);
}
for (AbstractJavaMethod value : _functionMap.values()) {
cl.addMethod(value.getName(), value);
}
if (__fieldGet != null)
cl.setFieldGet(__fieldGet);
if (__fieldSet != null)
cl.setFieldSet(__fieldSet);
if (__call != null)
cl.setCall(__call);
if (__toString != null) {
cl.addMethod("__toString", __toString);
}
if (_arrayDelegate != null)
cl.setArrayDelegate(_arrayDelegate);
else if (_funArrayDelegate != null)
cl.setArrayDelegate(_funArrayDelegate);
if (_traversableDelegate != null)
cl.setTraversableDelegate(_traversableDelegate);
else if (cl.getTraversableDelegate() == null &&
_iteratorMethod != null) {
// adds support for Java classes implementing iterator()
// php/
cl.setTraversableDelegate(new JavaTraversableDelegate(_iteratorMethod));
}
if (_countDelegate != null)
cl.setCountDelegate(_countDelegate);
for (Map.Entry<String,Value> entry : _constMap.entrySet()) {
cl.addConstant(entry.getKey(), new LiteralExpr(entry.getValue()));
}
}
/**
* Finds the matching constant
*/
public Value findConstant(Env env, String name)
{
return _constMap.get(name);
}
/**
* Creates a new instance.
*/
public void initInstance(Env env, Value value)
{
}
/**
* Returns the quercus class
*/
public QuercusClass getQuercusClass()
{
if (_quercusClass == null) {
init();
_quercusClass = new QuercusClass(_moduleContext, this, null);
}
return _quercusClass;
}
/**
* Returns the constructor
*/
@Override
public AbstractFunction findConstructor()
{
return null;
}
@Override
public final void init()
{
if (_isInit)
return;
synchronized (this) {
if (_isInit)
return;
super.init();
try {
initInterfaceList(_type);
introspect();
}
finally {
_isInit = true;
}
}
}
private void initInterfaceList(Class type)
{
Class[] ifaces = type.getInterfaces();
if (ifaces == null)
return;
for (Class iface : ifaces) {
JavaClassDef javaClassDef = _moduleContext.getJavaClassDefinition(iface);
if (javaClassDef != null)
addInterface(javaClassDef.getName());
// recurse for parent interfaces
initInterfaceList(iface);
}
}
/**
* Introspects the Java class.
*/
private void introspect()
{
introspectConstants(_type);
introspectMethods(_moduleContext, _type);
introspectFields(_moduleContext, _type);
_marshal = new JavaMarshal(this, false);
Method consMethod = getConsMethod(_type);
if (consMethod != null) {
if (Modifier.isStatic(consMethod.getModifiers()))
_cons = new JavaMethod(_moduleContext, consMethod);
else
__construct = new JavaMethod(_moduleContext, consMethod);
}
if (_cons == null) {
Constructor []cons = _type.getConstructors();
if (cons.length > 0) {
int i;
for (i = 0; i < cons.length; i++) {
if (cons[i].isAnnotationPresent(Construct.class))
break;
}
if (i < cons.length) {
_cons = new JavaConstructor(_moduleContext, cons[i]);
}
else {
_cons = new JavaConstructor(_moduleContext, cons[0]);
for (i = 1; i < cons.length; i++) {
_cons = _cons.overload(new JavaConstructor(_moduleContext, cons[i]));
}
}
} else
_cons = null;
}
introspectAnnotations(_type);
}
private void introspectAnnotations(Class type)
{
try {
if (type == null || type == Object.class)
return;
// interfaces
for (Class<?> iface : type.getInterfaces())
introspectAnnotations(iface);
// super-class
introspectAnnotations(type.getSuperclass());
// this
for (Annotation annotation : type.getAnnotations()) {
if (annotation.annotationType() == Delegates.class) {
Class[] delegateClasses = ((Delegates) annotation).value();
for (Class cl : delegateClasses) {
boolean isDelegate = addDelegate(cl);
if (! isDelegate)
throw new IllegalArgumentException(L.l("unknown @Delegate class '{0}'",
cl));
}
}
else if (annotation.annotationType() == ResourceType.class) {
_resourceType = ((ResourceType) annotation).value();
}
}
} catch (RuntimeException e) {
throw e;
} catch (InstantiationException e) {
throw new QuercusModuleException(e.getCause());
} catch (Exception e) {
throw new QuercusModuleException(e);
}
}
private boolean addDelegate(Class cl)
throws InstantiationException, IllegalAccessException
{
boolean isDelegate = false;
if (TraversableDelegate.class.isAssignableFrom(cl)) {
_traversableDelegate = (TraversableDelegate) cl.newInstance();
isDelegate = true;
}
if (ArrayDelegate.class.isAssignableFrom(cl)) {
_arrayDelegate = (ArrayDelegate) cl.newInstance();
isDelegate = true;
}
if (CountDelegate.class.isAssignableFrom(cl)) {
_countDelegate = (CountDelegate) cl.newInstance();
isDelegate = true;
}
return isDelegate;
}
private Method getConsMethod(Class type)
{
Method []methods = type.getMethods();
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
if (! method.getName().equals("__construct"))
continue;
if (! Modifier.isPublic(method.getModifiers()))
continue;
return method;
}
return null;
}
protected void setCons(Method method)
{
_cons = new JavaMethod(_moduleContext, method);
}
/**
* Introspects the Java class.
*/
private void introspectFields(ModuleContext moduleContext, Class type)
{
if (type == null || type.equals(Object.class))
return;
if (! Modifier.isPublic(type.getModifiers()))
return;
// Introspect getXXX and setXXX
// also register whether __get, __getField, __set, __setField exists
Method[] methods = type.getMethods();
for (Method method : methods) {
if (Modifier.isStatic(method.getModifiers()))
continue;
String methodName = method.getName();
int length = methodName.length();
if (length > 3) {
if (methodName.startsWith("get")) {
StringValue quercusName
= javaToQuercusConvert(methodName.substring(3, length));
AbstractJavaMethod existingGetter = _getMap.get(quercusName);
AbstractJavaMethod newGetter = new JavaMethod(moduleContext, method);
if (existingGetter != null) {
try {
newGetter = existingGetter.overload(newGetter);
} catch (Exception e) {
log.log(Level.WARNING, "Could not introspect a overloaded method", e);
continue;
}
}
_getMap.put(quercusName, newGetter);
}
else if (methodName.startsWith("is")) {
StringValue quercusName
= javaToQuercusConvert(methodName.substring(2, length));
AbstractJavaMethod existingGetter = _getMap.get(quercusName);
AbstractJavaMethod newGetter = new JavaMethod(moduleContext, method);
if (existingGetter != null) {
newGetter = existingGetter.overload(newGetter);
}
_getMap.put(quercusName, newGetter);
}
else if (methodName.startsWith("set")) {
StringValue quercusName
= javaToQuercusConvert(methodName.substring(3, length));
AbstractJavaMethod existingSetter = _setMap.get(quercusName);
AbstractJavaMethod newSetter = new JavaMethod(moduleContext, method);
if (existingSetter != null)
newSetter = existingSetter.overload(newSetter);
_setMap.put(quercusName, newSetter);
} else if ("__get".equals(methodName)) {
if (_funArrayDelegate == null)
_funArrayDelegate = new FunctionArrayDelegate();
_funArrayDelegate.setArrayGet(new JavaMethod(moduleContext, method));
} else if ("__set".equals(methodName)) {
if (_funArrayDelegate == null)
_funArrayDelegate = new FunctionArrayDelegate();
_funArrayDelegate.setArrayPut(new JavaMethod(moduleContext, method));
} else if ("__getField".equals(methodName)) {
__fieldGet = new JavaMethod(moduleContext, method);
} else if ("__setField".equals(methodName)) {
__fieldSet = new JavaMethod(moduleContext, method);
} else if ("__fieldGet".equals(methodName)) {
__fieldGet = new JavaMethod(moduleContext, method);
} else if ("__fieldSet".equals(methodName)) {
__fieldSet = new JavaMethod(moduleContext, method);
}
}
}
// Introspect public non-static fields
Field[] fields = type.getFields();
for (Field field : fields) {
if (Modifier.isStatic(field.getModifiers()))
continue;
MarshalFactory factory = moduleContext.getMarshalFactory();
Marshal marshal = factory.create(field.getType(), false);
_fieldMap.put(new ConstStringValue(field.getName()),
new FieldMarshalPair(field, marshal));
}
// introspectFields(quercus, type.getSuperclass());
}
/**
* helper for introspectFields
*
* @param s (eg: Foo, URL)
* @return (foo, URL)
*/
private StringValue javaToQuercusConvert(String s)
{
if (s.length() == 1) {
return new ConstStringValue(new char[] {Character.toLowerCase(s.charAt(0))});
}
if (Character.isUpperCase(s.charAt(1)))
return new ConstStringValue(s);
else {
StringBuilderValue sb = new StringBuilderValue();
sb.append(Character.toLowerCase(s.charAt(0)));
int length = s.length();
for (int i = 1; i < length; i++) {
sb.append(s.charAt(i));
}
return sb;
}
}
/**
* Introspects the Java class.
*/
private void introspectConstants(Class type)
{
if (type == null || type.equals(Object.class))
return;
if (! Modifier.isPublic(type.getModifiers()))
return;
Class []ifcs = type.getInterfaces();
for (Class ifc : ifcs) {
introspectConstants(ifc);
}
Field []fields = type.getDeclaredFields();
for (Field field : fields) {
if (_constMap.get(field.getName()) != null)
continue;
else if (! Modifier.isPublic(field.getModifiers()))
continue;
else if (! Modifier.isStatic(field.getModifiers()))
continue;
else if (! Modifier.isFinal(field.getModifiers()))
continue;
try {
Value value = Quercus.objectToValue(field.get(null));
if (value != null)
_constMap.put(field.getName().intern(), value);
} catch (Throwable e) {
log.log(Level.FINER, e.toString(), e);
}
}
introspectConstants(type.getSuperclass());
}
/**
* Introspects the Java class.
*/
private void introspectMethods(ModuleContext moduleContext, Class type)
{
if (type == null || type.equals(Object.class))
return;
Method []methods = type.getMethods();
for (Method method : methods) {
if (! Modifier.isPublic(method.getModifiers()))
continue;
if (method.getDeclaringClass() == Object.class)
continue;
if ("iterator".equals(method.getName())
&& method.getParameterTypes().length == 0
&& Iterator.class.isAssignableFrom(method.getReturnType())) {
_iteratorMethod = method;
}
if ("printRImpl".equals(method.getName())) {
_printRImpl = method;
} else if ("varDumpImpl".equals(method.getName())) {
_varDumpImpl = method;
} else if (method.isAnnotationPresent(EntrySet.class)) {
_entrySet = method;
} else if ("__call".equals(method.getName())) {
__call = new JavaMethod(moduleContext, method);
} else if ("__toString".equals(method.getName())) {
__toString = new JavaMethod(moduleContext, method);
} else {
if (method.getName().startsWith("quercus_"))
throw new UnsupportedOperationException(L.l("{0}: use @Name instead", method.getName()));
JavaMethod newFun = new JavaMethod(moduleContext, method);
AbstractJavaMethod fun = _functionMap.get(newFun.getName());
if (fun != null) {
try {
fun = fun.overload(newFun);
} catch (Exception e) {
log.log(Level.WARNING, "Could not introspect a overloaded method", e);
continue;
}
}
else
fun = newFun;
_functionMap.put(fun.getName(), fun);
}
}
introspectMethods(moduleContext, type.getSuperclass());
Class []ifcs = type.getInterfaces();
for (Class ifc : ifcs) {
introspectMethods(moduleContext, ifc);
}
}
public JavaMethod getToString()
{
return __toString;
}
public StringValue toReprString(Env env,
JavaValue value)
{
if (__toString == null)
return null;
return __toString.callMethod(env, value, new Expr[0]).toStringValue(env);
}
/**
*
* @return false if printRImpl not implemented
* @throws IOException
*/
public boolean printRImpl(Env env,
Object obj,
WriteStream out,
int depth,
IdentityHashMap<Value, String> valueSet)
throws IOException
{
try {
if (_printRImpl == null) {
return false;
}
_printRImpl.invoke(obj, env, out, depth, valueSet);
return true;
} catch (Exception e) {
throw new QuercusException(e);
}
}
public boolean varDumpImpl(Env env,
Object obj,
WriteStream out,
int depth,
IdentityHashMap<Value, String> valueSet)
throws IOException
{
try {
if (_varDumpImpl == null)
return false;
_varDumpImpl.invoke(obj, env, out, depth, valueSet);
return true;
} catch (Exception e) {
throw new QuercusException(e);
}
}
private class JavaTraversableDelegate
implements TraversableDelegate
{
private Method _iteratorMethod;
public JavaTraversableDelegate(Method iterator)
{
_iteratorMethod = iterator;
}
public Iterator<Map.Entry<Value, Value>>
getIterator(Env env, ObjectValue qThis)
{
try {
Iterator iterator
= (Iterator) _iteratorMethod.invoke(qThis.toJavaObject());
return new JavaIterator(env, iterator);
}
catch (Exception e) {
throw new QuercusRuntimeException(e);
}
}
public Iterator<Value> getKeyIterator(Env env, ObjectValue qThis)
{
try {
Iterator iterator =
(Iterator) _iteratorMethod.invoke(qThis.toJavaObject());
return new JavaKeyIterator(iterator);
}
catch (Exception e) {
throw new QuercusRuntimeException(e);
}
}
public Iterator<Value> getValueIterator(Env env, ObjectValue qThis)
{
try {
Iterator iterator =
(Iterator) _iteratorMethod.invoke(qThis.toJavaObject());
return new JavaValueIterator(env, iterator);
}
catch (Exception e) {
throw new QuercusRuntimeException(e);
}
}
}
private class JavaKeyIterator
implements Iterator<Value>
{
private Iterator _iterator;
private int _index;
public JavaKeyIterator(Iterator iterator)
{
_iterator = iterator;
}
public Value next()
{
_iterator.next();
return LongValue.create(_index++);
}
public boolean hasNext()
{
return _iterator.hasNext();
}
public void remove()
{
throw new UnsupportedOperationException();
}
}
private class JavaValueIterator
implements Iterator<Value>
{
private Env _env;
private Iterator _iterator;
public JavaValueIterator(Env env, Iterator iterator)
{
_env = env;
_iterator = iterator;
}
public Value next()
{
return _env.wrapJava(_iterator.next());
}
public boolean hasNext()
{
if (_iterator != null)
return _iterator.hasNext();
else
return false;
}
public void remove()
{
throw new UnsupportedOperationException();
}
}
private class JavaIterator
implements Iterator<Map.Entry<Value, Value>>
{
private Env _env;
private Iterator _iterator;
private int _index;
public JavaIterator(Env env, Iterator iterator)
{
_env = env;
_iterator = iterator;
}
@SuppressWarnings("unchecked")
public Map.Entry<Value, Value> next()
{
Object next = _iterator.next();
int index = _index++;
if (next instanceof Map.Entry) {
Map.Entry entry = (Map.Entry) next;
if (entry.getKey() instanceof Value &&
entry.getValue() instanceof Value)
{
return (Map.Entry<Value, Value>) entry;
}
else {
Value key = _env.wrapJava(entry.getKey());
Value val = _env.wrapJava(entry.getValue());
return new JavaEntry(key, val);
}
}
else {
return new JavaEntry(LongValue.create(index), _env.wrapJava(next));
}
}
public boolean hasNext()
{
if (_iterator != null)
return _iterator.hasNext();
else
return false;
}
public void remove()
{
throw new UnsupportedOperationException();
}
}
private class JavaEntry
implements Map.Entry<Value, Value>
{
private Value _key;
private Value _value;
public JavaEntry(Value key, Value value)
{
_key = key;
_value = value;
}
public Value getKey()
{
return _key;
}
public Value getValue()
{
return _value;
}
public Value setValue(Value value)
{
throw new UnsupportedOperationException();
}
}
private static class FieldMarshalPair {
public Field _field;
public Marshal _marshal;
public FieldMarshalPair(Field field,
Marshal marshal)
{
_field = field;
_marshal = marshal;
}
}
private static class LongClassDef extends JavaClassDef<Long> {
LongClassDef(ModuleContext module)
{
super(module, "Long", Long.class);
}
@Override
public Value wrap(Env env, Long obj)
{
return LongValue.create(((Number) obj).longValue());
}
}
private static class DoubleClassDef extends JavaClassDef<Double> {
DoubleClassDef(ModuleContext module)
{
super(module, "Double", Double.class);
}
@Override
public Value wrap(Env env, Double obj)
{
return new DoubleValue(((Number) obj).doubleValue());
}
}
private static class BigIntegerClassDef extends JavaClassDef<BigInteger> {
BigIntegerClassDef(ModuleContext module)
{
super(module, "BigInteger", BigInteger.class);
}
@Override
public Value wrap(Env env, BigInteger obj)
{
return new BigIntegerValue(env, obj, this);
}
}
private static class BigDecimalClassDef extends JavaClassDef<BigDecimal> {
BigDecimalClassDef(ModuleContext module)
{
super(module, "BigDecimal", BigDecimal.class);
}
@Override
public Value wrap(Env env, BigDecimal obj)
{
return new BigDecimalValue(env, obj, this);
}
}
private static class StringClassDef extends JavaClassDef<String> {
StringClassDef(ModuleContext module)
{
super(module, "String", String.class);
}
@Override
public Value wrap(Env env, String obj)
{
return env.createStringOld((String) obj);
}
}
private static class BooleanClassDef extends JavaClassDef<Boolean> {
BooleanClassDef(ModuleContext module)
{
super(module, "Boolean", Boolean.class);
}
@Override
public Value wrap(Env env, Boolean obj)
{
if (Boolean.TRUE.equals(obj))
return BooleanValue.TRUE;
else
return BooleanValue.FALSE;
}
}
private static class CalendarClassDef extends JavaClassDef<Calendar> {
CalendarClassDef(ModuleContext module)
{
super(module, "Calendar", Calendar.class);
}
@Override
public Value wrap(Env env, Calendar obj)
{
return new JavaCalendarValue(env, (Calendar)obj, this);
}
}
private static class DateClassDef extends JavaClassDef<Date> {
DateClassDef(ModuleContext module)
{
super(module, "Date", Date.class);
}
@Override
public Value wrap(Env env, Date obj)
{
return new JavaDateValue(env, obj, this);
}
}
private static class URLClassDef extends JavaClassDef<URL> {
URLClassDef(ModuleContext module)
{
super(module, "URL", URL.class);
}
@Override
public Value wrap(Env env, URL obj)
{
return new JavaURLValue(env, obj, this);
}
}
}