/*
* Copyright (C) 2000 - 2008 TagServlet Ltd
*
* This file is part of Open BlueDragon (OpenBD) CFML Server Engine.
*
* OpenBD is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* Free Software Foundation,version 3.
*
* OpenBD 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. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenBD. If not, see http://www.gnu.org/licenses/
*
* Additional permission under GNU GPL version 3 section 7
*
* If you modify this Program, or any covered work, by linking or combining
* it with any of the JARS listed in the README.txt (or a modified version of
* (that library), containing parts covered by the terms of that JAR, the
* licensors of this Program grant you additional permission to convey the
* resulting work.
* README.txt @ http://www.openbluedragon.org/license/README.txt
*
* http://www.openbluedragon.org/
*/
package com.naryx.tagfusion.cfm.engine;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import com.allaire.cfx.cfmlPage;
import com.naryx.tagfusion.cfm.parser.CFContext;
import com.naryx.tagfusion.cfm.tag.tagUtils;
import com.naryx.tagfusion.cfx.cfmlPageContextImpl;
import com.naryx.tagfusion.expression.function.xml.xmlFormat;
import com.naryx.tagfusion.util.InstanceTrackingPrintWriter;
/**
* This class acts as a wrapper to a Java class accessed via CFObject.
* The instance of the Java class isn't actually created until a method or
* field is accessed thru' getData().
*
* Note that when a field is requested, what is returned is a cfJavaObjectFieldData
* instance which is basically an indirect reference to the object field allowing
* for the getting and setting of the value.
*
* There is no requirement for the setData methods to be implemented.
*
* The internal data objects have been marked tranisent for the purposes of caching
* queries to disk.
*/
public class cfJavaObjectData extends cfData implements java.io.Serializable {
static final long serialVersionUID = 1;
private static final javolution.util.FastSet<Class<?>> numericClassSet = new javolution.util.FastSet<Class<?>>( 11 );
static {
numericClassSet.add( Integer.class );
numericClassSet.add( int.class );
numericClassSet.add( Double.class );
numericClassSet.add( double.class );
numericClassSet.add( Float.class );
numericClassSet.add( float.class );
numericClassSet.add( Short.class );
numericClassSet.add( short.class );
numericClassSet.add( Long.class );
numericClassSet.add( long.class );
numericClassSet.add( Number.class );
}
transient protected Object instance;
/**
* For performance reasons we're not going to look up the Class until we
* actually need it. Therefore, never read the instanceClass attribute
* directly, but always indirectly via the getInstanceClass() method.
*/
transient private Class<?> instanceClass;
transient private cfSession parentSession; //--[ This is for the cfmlPageContext
transient private cfmlPageContextImpl thisPageContext; //--[ This is for the cfmlPageContext
public cfJavaObjectData(Object _obj){
instance = _obj;
}// cfJavaObjectData()
public cfJavaObjectData( cfSession _session, Class<?> _class ){
instanceClass = _class;
parentSession = _session;
}// cfJavaObjectData()
// subclasses using this constructor MUST also invoke setInstance()
protected cfJavaObjectData(){
super();
}
public String getString() throws dataNotSupportedException {
if ( getDataType() == cfData.CFJAVAOBJECTDATA ){
try{
return getInstance().toString();
}catch( cfmRunTimeException e ){
throw new dataNotSupportedException( "Failed to instantiate object: " + e.getMessage() );
}
}
return super.getString();
}
public byte getDataType(){ return cfData.CFJAVAOBJECTDATA; }
public String getDataTypeName() { return "java object"; }
public Object getInstance() throws cfmRunTimeException {
if ( instance == null ) {
createInstance();
}
return instance;
}// getInstance()
public Object getUnderlyingInstance()
{
// Yes, even if it's null
return instance;
}
protected void setInstance(Object _inst){
instance = _inst;
instanceClass = null;
}// setInstance()
/**
* For performance reasons we're not going to look up the Class until we
* actually need it. Therefore, never read the instanceClass attribute
* directly, but always indirectly via the getInstanceClass() method.
*/
public Class<?> getInstanceClass()
{
if ((instanceClass == null) && (instance != null))
{
instanceClass = instance.getClass();
}
return instanceClass;
}
public String getInstanceClassName()
{
Class<?> cls = getInstanceClass();
if (cls != null)
return cls.getName();
else if (instance != null)
return instance.getClass().getName();
else
return null;
}
public cfData getJavaData( cfData _field ) throws cfmRunTimeException{
if ( ( _field != null ) && ( _field.getDataType() == cfData.CFSTRINGDATA ) ) {
return getJavaData( ( (cfStringData) _field ).getString() );
}else{
throw new cfmRunTimeException( catchDataFactory.generalException( cfCatchData.TYPE_OBJECT,
"errorCode.runtimeError",
"cfdata.javaInvalidClass",
null ));
}
}// getData()
/**
* Returns the field named '_field' performing a case-insensitive match
*/
private Field getField(Class<?> cls, String _field ) {
// we could attempt to do case sensitive match first with Class.getField( String )
// but the likely overhead of a created NoSuchFieldException is not worth it
Field [] allFields = cls.getFields();
for ( int i = 0; i < allFields.length; i++ ){
if ( allFields[i].getName().equalsIgnoreCase( _field ) ){
return allFields[i];
}
}
return null;
}
public cfData getJavaData( String _Field ) throws cfmRunTimeException{
Class<?> cls = getInstanceClass();
Exception exception = null;
try {
Field theField = getField(cls, _Field );
if ( theField != null ){
cfData converted = convertToCfData( theField );
return converted;
}
}catch( SecurityException e3 ){
exception = e3;
}
// either an exception was thrown or the field wasn't found
cfData fieldAsMethod = getFieldMethod(cls, _Field );
if ( fieldAsMethod != null ){
return fieldAsMethod;
}
if ( exception == null ){
exception = new NoSuchFieldException( _Field );
}
throw new cfmRunTimeException( catchDataFactory.javaMethodException( "errorCode.javaException",
exception.getClass().getName(), exception.getMessage(), exception));
}// getData()
private cfData getFieldMethod(Class<?> cls, String _field ) {
Method getMethod = null;
Method setMethod = null;
Method [] allMethods = cls.getMethods();
String getMethName1 = "get" + _field;
String getMethName3 = "is" + _field;
List<Method> getMethods = new ArrayList<Method>();
String setMethName1 = "set" + _field;
List<Method> setMethods = new ArrayList<Method>();
for ( int i = 0; i < allMethods.length; i++ ){
String methName = allMethods[i].getName();
if ( ( getMethName1.equalsIgnoreCase( methName ) ||
getMethName3.equalsIgnoreCase( methName ) ) &&
allMethods[i].getParameterTypes().length == 0 )
{
getMethods.add( 0, allMethods[i] );
}
else if ( setMethName1.equalsIgnoreCase( methName ) &&
allMethods[i].getParameterTypes().length == 1 )
{
setMethods.add( 0, allMethods[i] );
}
}
if ( getMethods.size() != 0 ){
getMethod = getMethods.get(0);
}
if ( setMethods.size() != 0 ){
setMethod = setMethods.get(0);
}
if ( getMethod == null && setMethod == null ){
return null;
}
return new com.naryx.tagfusion.cfm.parser.cfJavaObjectFieldData( this, _field, getMethod, setMethod );
}
public cfData getJavaData( javaMethodDataInterface _method, CFContext _context ) throws cfmRunTimeException{
String methodName = _method.getFunctionName();
List<cfData> args = _method.getEvaluatedArguments( _context, false );
if ( methodName.toLowerCase().equals( "init" ) ){
// bug #2359: if the instance already exists, see if there's an "init" method
if ( instance != null ){
try{
return invokeMethod( methodName, args, _context.getSession() );
}catch ( cfmRunTimeException ignore ) {} // failed to invoke "init" method, try constructor
}
// handle init
if ( args.size() == 0 ){
createInstance();
}else{
createInstance( args, _context.getSession() );
}
return tagUtils.convertToCfData( instance );
}
return invokeMethod( methodName, args, _context.getSession() );
}// getData()
/**
* If the instance doesn't exist then creates it
**/
synchronized protected void createInstance() throws cfmRunTimeException{
// another thread may have executed this method whilst this thread waited queued on this method
if ( instance != null ){
return;
}
Class<?> cls = getInstanceClass();
// create the instance if it doesn't exist
try{
instance = cls.newInstance();
setCfmlPageContext( this );
}catch( ExceptionInInitializerError iiee ){
// if the constructor threw an exception, get the
throw new cfmRunTimeException( catchDataFactory.extendedException(
cfCatchData.TYPE_OBJECT,
"errorCode.runtimeError",
"cfdata.javaobjectfail",
null,
iiee.getException().getClass().getName() + " ( Message : " + iiee.getException().getMessage() + ")",
iiee.getException() ));
}catch ( Throwable t ){
// throw if cannot instantiate object due to IllegalArgumentException, InstantiationException or IllegalAccessException
// also, a NoSuchMethodError can be thrown if a no-arguments constructor isn't defined
throw new cfmRunTimeException( catchDataFactory.extendedException( cfCatchData.TYPE_OBJECT,
"errorCode.runtimeError",
"cfdata.javaobjectfail",
null,
t.getMessage()));
}
}// createInstance()
private void createInstance( List<cfData> _args, cfSession _Session ) throws cfmRunTimeException {
Class<?>[] argTypes = new Class[ _args.size() ];
Object[] argValues = new Object[ _args.size() ];
Class<?> cls = getInstanceClass();
// get Constructor (throws runTimeException if can't find a matching one)
Constructor<?> constructor = getConstructor(cls, _args, argTypes, argValues );
try
{
instance = constructor.newInstance( argValues );
setCfmlPageContext( this );
}
catch( InvocationTargetException ite )
{
Throwable e = ite.getTargetException();
// if the constructor threw an exception, return the exception name and it's message
throw new cfmRunTimeException( catchDataFactory.javaMethodException( "errorCode.javaException",
e.getClass().getName(),
e.getMessage(),
e ));
}
catch( Exception ie )
{
// throw if cannot instantiate object due to IllegalArgumentException, InstantiationException or IllegalAccessException
throw new cfmRunTimeException( catchDataFactory.extendedException( cfCatchData.TYPE_OBJECT,
"errorCode.runtimeError",
"cfdata.javaobjectfail",
null,
ie.getMessage() ));
}
}// createInstance()
private Constructor<?> getConstructor(Class<?> cls, List<cfData> _args, Class<?>[] _newArgTypes,
Object [] _newArgValues ) throws cfmRunTimeException
{
// get Vector of potential matches i.e. same name + same number of args
List<Constructor<?>> possibles = getMatchingConstructors(cls, _args.size() );
boolean ambiguous = false;
if ( possibles.size() > 1 ){
ambiguous = true;
// do best fit
possibles = bestFitConstructor( possibles, _args );
}
// if Vector.size() != 1 (i.e. could be 0 or more than 1)
if ( possibles.size() != 1 ){
if ( possibles.size() == 0 && !ambiguous )
{
throw new cfmRunTimeException(
catchDataFactory.generalException(
cfCatchData.TYPE_OBJECT,
"errorCode.runtimeError",
"cfdata.javaInvalidConstructor",
null
)
);
}
else
{
throw new cfmRunTimeException(
catchDataFactory.generalException(
cfCatchData.TYPE_OBJECT,
"errorCode.runtimeError",
"cfdata.javaInvalidConstructor2",
null
)
);
}
}
// INVARIANT - possibles.size() == 1;
Constructor<?> theConstructor = possibles.get(0);
Class<?>[] constParamTypes = theConstructor.getParameterTypes();
System.arraycopy( constParamTypes, 0, _newArgTypes, 0, constParamTypes.length );
// convert real args to _newArgs (throwing an exception if can't convert)
convertData( _args, _newArgTypes, _newArgValues );
return theConstructor;
}// getConstructor()
private List<Constructor<?>> getMatchingConstructors(Class<?> cls, int _noArgs ){
List<Constructor<?>> matching = new ArrayList<Constructor<?>>();
Constructor<?> [] constrs = cls.getConstructors();
if ( constrs != null ){
for ( int i = 0; i < constrs.length; i++ ){
if ( constrs[i].getParameterTypes().length == _noArgs ){
matching.add( constrs[i] );
}
}
}
return matching;
}// getMatchingConstructors()
/**
* returns all the Method's in '_possibles' that should be possible to
* invoke given the arguments and their types
*/
private static List<Constructor<?>> bestFitConstructor( List<Constructor<?>> _possibles, List<cfData> _actualArgs ){
List<Constructor<?>> newPossibles = new ArrayList<Constructor<?>>();
double minRank = 999;
Constructor<?> nextConstr = null;
Class<?>[] constrParamTypes = null;
int noArgs = _actualArgs.size();
// loop thru each method
for ( int i = 0; i < _possibles.size(); i++ ){
nextConstr = _possibles.get(i);
constrParamTypes = nextConstr.getParameterTypes();
double constrRank = 0;
boolean fullMatch = true;
// loop thru the arguments getting a rank for each argument to Method parameter
// match up
for ( int j = 0; j < noArgs; j++ ){
cfData arg = _actualArgs.get( j );
double argRank = getMatchRank( arg, constrParamTypes[j] );
if ( argRank == 0 ){
fullMatch = false;
break;
}else{
constrRank += argRank;
}
}
// if it's a match...
if ( fullMatch ){
// ... and a better match than the current best match
if ( constrRank < minRank ){
newPossibles.clear();
newPossibles.add( nextConstr );
minRank = constrRank;
// ... or is an equally good match
}else if ( constrRank == minRank ){
newPossibles.add( nextConstr );
}
}
}
return newPossibles;
}
// returns a cfJavaObjectFieldData representing this field
private cfData convertToCfData( Field _field ){
return new com.naryx.tagfusion.cfm.parser.cfJavaObjectFieldData( this, _field );
}// convertToCfData()
/***
private Class[] getClasses( Vector objs ){
int noArgs = objs.size();
Class [] argClasses = null;
if ( noArgs > 0 ){
argClasses = new Class[ noArgs ];
for ( int i = 0; i < noArgs; i++ ){
argClasses[ i ] = objs.elementAt( i ).getClass();
}
}
return argClasses;
}// getClasses()
***/
/**
* invokes the given named method choosing the one
* that fits best out of the possible. An instance of the class
* will be created if one doesn't already exist AND the method
* being invoked is non-static.
* throws an exception if no matching method found
*/
public cfData invokeMethod( String _name, List<cfData> _args, cfSession _Session ) throws cfmRunTimeException {
Class<?> [] argTypes;
Object [] argValues;
if ( _name.equals("each") ){
if ( instance instanceof cfArrayData
|| instance instanceof cfStructData
|| instance instanceof cfQueryResultData )
_args.add(0, new cfDataSession(_Session) );
}
argTypes = new Class[ _args.size() ];
argValues = new Object[ _args.size() ];
Object returnValue = null;
Class<?> cls = getInstanceClass();
//get the Method trying a case-sensitive search first
Method method = getMethod(cls, _name, _args, argTypes, argValues, true, false );
if ( method == null ){
method = getMethod(cls, _name, _args, argTypes, argValues, false, true );
}
if ( method == null ) {
throw new cfmRunTimeException( catchDataFactory.generalException( cfCatchData.TYPE_OBJECT,
"errorCode.runtimeError",
"cfdata.javaInvalidMethod",
new String[]{_name} ));
}
// if instance hasn't been created and method is non-static, create the instance
if ( instance == null ){
if ( !Modifier.isStatic( method.getModifiers() ) ){
createInstance();
}
}
try {
returnValue = method.invoke( instance, argValues );
} catch( IllegalArgumentException e ) { // report that a matching method could not be found
// note that this is unlikely but there are situations where this occurs
throw new cfmRunTimeException( catchDataFactory.generalException( cfCatchData.TYPE_OBJECT,
"errorCode.runtimeError",
"cfdata.javaInvalidMethod",
new String[]{_name} ));
}catch ( IllegalAccessException iae1 ){
// it may be that the method is a public method from a private implementation of a
// public interface...
boolean throwError = false;
Class currCls = cls;
while(currCls != null){
try
{
Method innerMeth = getInnerClassMethod(currCls, method.getName(), argTypes );
if ( innerMeth != null )
{
returnValue = innerMeth.invoke( instance, argValues );
throwError = false;
break;
}
}catch( IllegalAccessException e ){
throwError = true;
}catch (InvocationTargetException e2 ){
throwError = true;
}
//part of the fix for OpenBD#180
Class<?> superClass = cls.getSuperclass();
if(currCls == superClass) //then we can't go any higher up (we're already at java.lang.Object)
{
throwError = true;
break; //to avoid an infinite loop (seen on OpenBD-GAE)
}
else
currCls = superClass;
}
if ( throwError ){ // handle original error
throw new cfmRunTimeException( catchDataFactory.generalException( cfCatchData.TYPE_OBJECT,
"errorCode.runtimeError",
"cfdata.javaIllegalMethod",
new String[]{_name} ));
}
}catch ( InvocationTargetException ite ){
Throwable targetExc = ite.getTargetException();
if ( targetExc instanceof cfmAbortException )
throw (cfmAbortException)targetExc;
if ( targetExc != null ){
throw new cfmRunTimeException( catchDataFactory.javaMethodException( "errorCode.javaException",
targetExc.getClass().getName(),
targetExc.getMessage(),
targetExc ));
} else{
String msg = "The method \"" + _name + "\" threw an exception and BlueDragon was unable to retrieve the exception information.";
if ( ite.getMessage() != null )
msg += " (InvocationTargetException message = " + ite.getMessage() + ")";
throw new cfmRunTimeException( catchDataFactory.javaMethodException( "errorCode.javaException",
ite.getClass().getName(),
msg,
ite ));
}
}
return tagUtils.convertToCfData( returnValue );
}// invokeMethod
private Method getInnerClassMethod(Class<?> cls, String _method, Class<?>[] _paramTypes ) {
// We're looking for the instance of Method that has the same attributes as the one we first
// found but now we want the version that belongs to the super class or implemented interface
Method parentMethod = null;
// bug #2360: first see if the method is from an implemented interface
Class<?>[] implemented = cls.getInterfaces();
for ( int i = 0; i < implemented.length; i++ ) {
try {
parentMethod = implemented[i].getMethod( _method, _paramTypes );
break;
} catch ( NoSuchMethodException e ) {
} // ignore and try next
}
// now look for a superclass method
if ( parentMethod == null ) {
try {
Class<?> superclass = cls.getSuperclass();
if(superclass != null) //part of the fix for OpenBD#180
parentMethod = superclass.getMethod( _method, _paramTypes );
} catch ( NoSuchMethodException e ) {
}
}
return parentMethod;
}
/**
* returns a Method that has the name '_name' and best matches the Vector of cfData arguments.
* The 2 Class[]'s will reflect the chosen method. '_newArgTypes' will contain the Classes of
* the chosen method argmuents, and '_newArgValues' will contain the argument to pass to the
* Method (converted from the cfData's in '_actualArgs' to Java Objects).
*/
private static Method getMethod( Class<?> _class, String _name, List<cfData> _actualArgs,
Class<?>[] _newArgTypes, Object[] _newArgValues,
boolean _caseSensitive, boolean _throwOnError ) throws cfmRunTimeException{
// get Vector of potential matches i.e. same name + same number of args [non-case-sensitive match]
List<Method> possibles = getMatchingMethods( _class, _name, _actualArgs.size(), _caseSensitive, true );
boolean ambiguous = false;
// if Vector.size() > 1
if ( possibles.size() > 1 ){
// do best fit
ambiguous = true;
possibles = bestFitMethod( possibles, _actualArgs );
}
// if Vector.size() != 1 (i.e. could be 0 or more than 1)
if ( possibles.size() != 1 ){
if ( _throwOnError ){
// if there's no matching methods and it wasn't best matched
if ( possibles.size() == 0 && !ambiguous ){
throw new cfmRunTimeException( catchDataFactory.generalException( cfCatchData.TYPE_OBJECT,
"errorCode.runtimeError",
"cfdata.javaGetMethod",
new String[]{_name} ));
}else{
throw new cfmRunTimeException( catchDataFactory.generalException( cfCatchData.TYPE_OBJECT,
"errorCode.runtimeError",
"cfdata.javaGetMethod2",
new String[]{_name} ));
}
}else{
return null;
}
}
// INVARIANT - possibles.size() == 1;
Method theMethod = possibles.get(0);
Class<?>[] methParamTypes = theMethod.getParameterTypes();
System.arraycopy( methParamTypes, 0, _newArgTypes, 0, methParamTypes.length );
// convert real args to _newArgs (throwing an exception if can't convert)
try{
convertData( _actualArgs, _newArgTypes, _newArgValues );
}catch( cfmRunTimeException e ){
if ( _throwOnError )
throw e;
else
return null;
}
return theMethod;
}// getMethod()
/**
* returns all the Method's for this Java class that have the name '_methodName'
* with '_noArgs' number of parameters
*/
private static List<Method> getMatchingMethods( Class<?> _class, String _methodName, int _noArgs, boolean _case, boolean _includeStatic ){
List<Method> matching = new ArrayList<Method>();
Method nextMethod;
String nextMethodName;
if ( _class == null )
return matching;
Method [] allMethods = _class.getMethods();
for ( int i = 0; i < allMethods.length; i++ ){
nextMethod = allMethods[i];
nextMethodName = nextMethod.getName();
if ( ( _includeStatic || (!_includeStatic && !Modifier.isStatic( nextMethod.getModifiers() ) ) )
&& ( ( _case && nextMethodName.equals(_methodName ) )
|| ( !_case && nextMethodName.equalsIgnoreCase(_methodName ) ) )
&& nextMethod.getParameterTypes().length == _noArgs ){
addMatchingMethod( matching, nextMethod );
}
}
return matching;
}// getMatchingMethods()
private static void addMatchingMethod( List<Method> matching, Method m )
{
// With JDK 1.5, it's possible to have two methods that are exactly the same except for
// the return type. In this case we'll ignore the one that returns a java.lang.Object
// and only use the one that returns a specific class. If it's possible to have more than
// two methods that are exactly the same except for the return type then we'll need to
// modify this logic to ignore the ones that return specific classes and only use the one
// that returns a java.lang.Object. This problem was seen with the regression test for
// bug #978.
Class<?> returnType = m.getReturnType();
if ( returnType != null )
{
for ( int i = 0; i < matching.size(); i++ )
{
Method nextMethod = matching.get( i );
if ( methodArgsMatch( nextMethod, m ) )
{
Class<?> nextMethodReturnType = nextMethod.getReturnType();
if ( returnType.getName().equals( "java.lang.Object" ) )
{
// The methods match and this one returns java.lang.Object so just
// return without adding it to the matching vector.
return;
}
else if ( nextMethodReturnType.getName().equals( "java.lang.Object" ) )
{
// The methods match and the one in the matching vector returns java.lang.Object so
// replace it with the passed in method.
matching.set( i, m );
return;
}
else if ( m.getDeclaringClass().getName().equals( "java.lang.Object" ) )
{
// The methods match except the one passed in is declared by java.lang.Object
// so let the non-Object version take precedence
return;
}
else if ( nextMethod.getDeclaringClass().getName().equals( "java.lang.Object" ) )
{
// The methods match except the one passed we have already is declared by
// java.lang.Object so replace it with the non-Object version
matching.set(i, m);
return;
}
else if (nextMethod.isBridge() && !m.isBridge())
{
// the methods match but the non-bridge method takes precedence
matching.set( i, m );
return;
}
else if (m.isBridge())
{
// ignore bridge method
return;
}else
{
// This should never happen. If it does then we'll need to change our logic as
// described in the comment at the top of this method.
throw new IllegalStateException( "Found 2 methods that are exactly the same except for the return types and neither return java.lang.Object. Method name = " + m.getName() );
}
}
}
}
matching.add( m );
}
private static boolean methodArgsMatch( Method m1, Method m2 )
{
Class<?>[] paramTypes1 = m1.getParameterTypes();
Class<?>[] paramTypes2 = m2.getParameterTypes();
// If they take a different number of arguments then return false
if ( paramTypes1.length != paramTypes2.length )
return false;
for ( int i = 0; i < paramTypes1.length; i++ )
{
// If one of the parameters doesn't match then return false
if ( !paramTypes1[ i ].equals( paramTypes2[ i ] ) )
return false;
}
return true;
}
/**
* returns all the Method's in '_possibles' that should be possible to
* invoke given the arguments and their types
*/
private static List<Method> bestFitMethod( List<Method> _possibles, List<cfData> _actualArgs ){
List<Method> newPossibles = new ArrayList<Method>();
double minRank = 999;
Method nextMethod = null;
Class<?>[] methodParamTypes = null;
int noArgs = _actualArgs.size();
// loop thru each method
for ( int i = 0; i < _possibles.size(); i++ ){
nextMethod = _possibles.get(i);
methodParamTypes = nextMethod.getParameterTypes();
double methRank = 0;
boolean fullMatch = true;
// loop thru the arguments getting a rank for each argument to Method parameter
// match up
for ( int j = 0; j < noArgs; j++ ){
cfData arg = _actualArgs.get( j );
double argRank = getMatchRank( arg, methodParamTypes[j] );
if ( argRank == 0 ){
fullMatch = false;
break;
}else{
methRank += argRank;
}
}
// if it's a match...
if ( fullMatch ){
// ... and a better match than the current best match
if ( methRank < minRank ){
newPossibles.clear();
newPossibles.add( nextMethod );
minRank = methRank;
// ... or is an equally good match
}else if ( methRank == minRank ){
newPossibles.add( nextMethod );
}
}
}
return newPossibles;
}
private static double getMatchRank( cfData _cfdata, Class<?> _class ){
// javacast takes priority so if it's set use that to get the rank
Javacast javaCast = _cfdata.getJavaCast();
//int javaCast = _cfdata.getJavaCast();
boolean isArrayCast = javaCast != null ? javaCast.isArray() : false;
if ( javaCast != null ){
if ( isArrayCast ) {
if ( ( javaCast == Javacast.INT_ARRAY && _class.equals( int[].class ) )
|| ( javaCast == Javacast.LONG_ARRAY && _class.equals( long[].class ) )
|| ( javaCast == Javacast.DOUBLE_ARRAY && _class.equals( double[].class ) )
|| ( javaCast == Javacast.FLOAT_ARRAY && _class.equals( float[].class ) )
|| ( javaCast == Javacast.BOOLEAN_ARRAY && _class.equals( boolean[].class ) )
|| ( javaCast == Javacast.STRING_ARRAY && _class.equals( String[].class ) )
|| ( javaCast == Javacast.BYTE_ARRAY && _class.equals( byte[].class ) )
|| ( javaCast == Javacast.CHAR_ARRAY && _class.equals( char[].class ) )
|| ( javaCast == Javacast.SHORT_ARRAY && _class.equals( short[].class ) )
){
return 0.75;
}else if ( ( javaCast == Javacast.INT_ARRAY && _class.equals( Integer[].class ) )
|| ( javaCast == Javacast.LONG_ARRAY && _class.equals( Long[].class ) )
|| ( javaCast == Javacast.DOUBLE_ARRAY && _class.equals( Double[].class ) )
|| ( javaCast == Javacast.FLOAT_ARRAY && _class.equals( Float[].class ) )
|| ( javaCast == Javacast.BOOLEAN_ARRAY && _class.equals( Boolean[].class ) )
|| ( javaCast == Javacast.BYTE && _class.equals( Byte[].class ) )
|| ( javaCast == Javacast.CHAR_ARRAY && _class.equals( Character[].class ) )
|| ( javaCast == Javacast.SHORT_ARRAY && _class.equals( Short[].class ) )
|| ( javaCast == Javacast.BIGDECIMAL_ARRAY && _class.equals( BigDecimal[].class ) )
|| ( javaCast.getDatatype() == Javacast.Datatype.CUSTOM && javaCast.isArray() && _class.equals( javaCast.getCustomClass() ) ) ){
return 0.50;
}
}else if ( javaCast.getDatatype() == Javacast.Datatype.CUSTOM && javaCast.getCustomClass().equals( _class ) ){
return 0.5;
}else{
// non-primitive type is preferred over it's primitive equivalent
if ( ( javaCast == Javacast.INT && _class.equals( int.class ) )
|| ( javaCast == Javacast.LONG && _class.equals( long.class ) )
|| ( javaCast == Javacast.DOUBLE && _class.equals( double.class ) )
|| ( javaCast == Javacast.FLOAT && _class.equals( float.class ) )
|| ( javaCast == Javacast.BOOLEAN && _class.equals( boolean.class ) )
|| ( javaCast == Javacast.STRING && _class.equals( String.class ) )
|| ( javaCast == Javacast.BYTE && _class.equals( byte.class ) )
|| ( javaCast == Javacast.CHAR && _class.equals( char.class ) )
|| ( javaCast == Javacast.SHORT && _class.equals( short.class ) )
|| ( javaCast.getDatatype() == Javacast.Datatype.CUSTOM && _class.equals( javaCast.getClass() ) ) ){
return 0.75;
}else if ( ( javaCast == Javacast.INT && _class.equals( Integer.class ) )
|| ( javaCast == Javacast.LONG && _class.equals( Long.class ) )
|| ( javaCast == Javacast.BOOLEAN && _class.equals( Boolean.class ) )
|| ( javaCast == Javacast.DOUBLE && _class.equals( Double.class ) )
|| ( javaCast == Javacast.FLOAT && _class.equals( Float.class ) )
|| ( javaCast == Javacast.BOOLEAN && _class.equals( Boolean.class ) )
|| ( javaCast == Javacast.BYTE && _class.equals( Byte.class ) )
|| ( javaCast == Javacast.CHAR && _class.equals( Character.class ) )
|| ( javaCast == Javacast.SHORT && _class.equals( Short.class ) )
|| ( javaCast == Javacast.BIGDECIMAL && _class.equals( BigDecimal.class ) ) ){
return 0.50;
}else if ( javaCast == Javacast.STRING && ( _class.equals( Object.class ) ||
_class.equals( Serializable.class ) || _class.equals( Comparable.class ) ) ){
return 0.85;
}
}
}
int dType = _cfdata.getDataType();
boolean isStringCompatible = true;
switch ( dType ){
case cfData.CFSTRINGDATA:
case cfData.CFNUMBERDATA:
case cfData.CFBOOLEANDATA:
if ( dType == cfData.CFNUMBERDATA ){ // skip this if it's a string
// indicates whether the cfNumberData has been created from a returned java method value
isStringCompatible = !( (cfNumberData) _cfdata ).isJavaNumeric();
}else if ( dType == cfData.CFBOOLEANDATA ){
isStringCompatible = !( (cfBooleanData) _cfdata ).isJavaBoolean();
}
if ( isStringCompatible && _class == String.class ){
return 1;
}else if ( _class.equals( char.class ) ){
try {
// a string with a single character matches "char"
if ( _cfdata.getString().length() == 1 ){
return 1.1;
}
}catch ( dataNotSupportedException ignore ) {} // should never happen
}
case cfData.CFDATEDATA:
if ( ( _class == String.class ) && isStringCompatible ) { // all these cfdata types can be converted to a string
return 1;
}else if ( _class == Serializable.class ){
return 2;
}else if ( _class == Comparable.class ){
return 2;
}else if ( _class== Object.class ){
return 3;
}else if ( ( _class == boolean.class ) || ( _class == Boolean.class ) ){
return ( _cfdata.isBooleanConvertible() ? 1.1 : 0 );
}else if ( numericClassSet.contains( _class ) ) {
return ( _cfdata.isNumberConvertible() ? 1.1 : 0 );
}else if ( _class == java.util.Date.class ) {
return ( _cfdata.isDateConvertible() ? 1.1 : 0 );
}
return 0;
case cfData.CFARRAYDATA:
if ( _class.isArray() ){
int dim = 1;
Class<?> componentType = _class.getComponentType();
if ( _cfdata instanceof cfFixedArrayData ){
if ( ( (cfFixedArrayData) _cfdata).getArray().getClass().getComponentType() == componentType ){
return 1;
}
}
while ( componentType.isArray() ){
dim++;
componentType = componentType.getComponentType();
}
if ( dim == ( (cfArrayData)_cfdata ).getDimension() ){
return 1.5;
}else{
return 2;
}
}else{
return matchClass( ( (cfJavaObjectData) _cfdata ).getInstanceClass(), _class );
}
case cfData.CFSTRUCTDATA:
case cfData.CFJAVAOBJECTDATA:
return matchClass( ( (cfJavaObjectData) _cfdata ).getInstanceClass(), _class );
case cfData.CFQUERYRESULTDATA:
return matchClass( _cfdata.getClass(), _class );
case cfData.CFBINARYDATA:
if ( _class.isArray() && (_class.getComponentType().equals( Byte.class ) || _class.getComponentType().equals( byte.class ) ) ){
return 1;
}else if ( _class.equals( Object.class ) ){
return 3;
}
return 0;
case cfData.CFNULLDATA:
if ( ((cfNullData)_cfdata).isDBNull() ){
if ( _class.equals( String.class ) )
return 1;
if ( _class.equals( Serializable.class ) )
return 1.5;
if ( _class.equals( Comparable.class ) )
return 1.5;
if ( _class.equals( Object.class ) )
return 2;
if ( _class.equals( Boolean.class ) || _class.equals( Number.class ) ||
_class.equals( Integer.class ) || _class.equals( Long.class ) ||
_class.equals( Double.class ) || _class.equals( Short.class ) ||
_class.equals( Float.class ) || (_class.equals( float.class ) ||
_class.equals( double.class ) || _class.equals( short.class ) ||
_class.equals( int.class ) || _class.equals( long.class ) ||
_class.equals( boolean.class ) ) )
return 2.5;
return 3;
}else{
if ( _class.equals( Object.class ) )
return 2;
if ( !_class.isPrimitive() )
return 1;
}
case cfData.CFCOMPONENTOBJECTDATA:
if ( _class == cfComponentData.class ){
return 1;
}else if ( _class == Serializable.class ) {
return 2;
}else if ( _class.equals( Object.class ) )
return 3;
default:
break;
}
return 0;
}
/**
* converts all the cfData's in _actualArgs to the corresponding Class types specified
* in _newArgTypes leaving the resultant conversion in _newArgValues.
* throw an exception if can't convert? or can we definitely expect it?
*/
private static void convertData( List<cfData> _actualArgs, Class<?>[] _newArgTypes, Object[] _newArgValues ) throws cfmRunTimeException{
for ( int i = 0; i < _newArgTypes.length; i++ ){
cfData nextData = _actualArgs.get(i);
// fix for bug 1691 allowing null to be passed to java methods
// don't want this to work for primitive type args, otherwise a NullPointerException will be thrown on invoke()
// if the null value came from a database field then let it go to the else clause so it will be treated as an
// empty string instead of null in order to stay compatible with CF6.1 and CF7. Refer to bugs 1745 and 1691.
if ( nextData.getDataType() == cfData.CFNULLDATA &&
!((cfNullData)nextData).isDBNull() &&
Object.class.isAssignableFrom( _newArgTypes[i] ) ){
_newArgValues[i] = null;
}else{
_newArgValues[i] = tagUtils.convertCFtoJava( nextData, _newArgTypes[i] );
if ( _newArgValues[i] == null ){
throw new cfmRunTimeException( catchDataFactory.generalException( cfCatchData.TYPE_OBJECT,
"errorCode.runtimeError",
"cfdata.javaBadMethod",
null ));
}
}
}
}//convertData()
/**
* returns whether or not a cfData can be strictly converted to the given Class type
*/
/***
private static boolean typeMatch( int _jCastType, Class _class ){
String className = _class.getName();
if ( _jCastType == cfData.JAVA_INT ){
return className.equals( "int" ) || className.equals( "java.lang.Integer" );
}else if ( _jCastType == cfData.JAVA_LONG ){
return className.equals( "long" ) || className.equals( "java.lang.Long" );
}else if ( _jCastType == cfData.JAVA_BOOLEAN ){
return className.equals( "boolean" ) || className.equals( "java.lang.Boolean" );
}else if ( _jCastType == cfData.JAVA_DOUBLE ){
return className.equals( "double" ) || className.equals( "java.lang.Double" );
}else if ( _jCastType == cfData.JAVA_FLOAT ){
return className.equals( "float" ) || className.equals( "java.lang.Float" );
}else if ( _jCastType == cfData.JAVA_STRING ){
return className.equals( "java.lang.String" );
}else{
return false;
}
}// typeMatch()
***/
/***
private static boolean exactMatch( cfData _cfdata, Class _class ){
String className = _class.getName();
if ( className.equals( "java.lang.Object" ) ){
return true;
}
int dType = _cfdata.getDataType();
switch ( dType ){
/** KEEP NUMBER & BOOLEAN OUT OF IT FOR NOW
set( int ); vs set( long )
and
set( boolean ); vs set( Boolean )
** /
case cfData.CFNUMBERDATA:
return className.equals( "java.lang.Integer" ) || className.equals( "int" )
|| className.equals( "java.lang.Double" ) || className.equals( "double" )
|| className.equals( "java.lang.Float" ) || className.equals( "float" )
|| className.equals( "java.lang.Short" ) || className.equals( "short" )
|| className.equals( "java.lang.Long" ) || className.equals( "long" )
|| className.equals( "java.lang.Number" ) || className.equals( "java.lang.Comparable" )
|| className.equals( "java.lang.Serializable" );
case cfData.CFBOOLEANDATA:
return className.equals( "java.lang.Boolean" ) || className.equals( "boolean" ) || className.equals( "java.lang.Serializable" );
case cfData.CFSTRINGDATA:
return className.equals( "java.lang.String" ) || className.equals( "java.lang.Comparable" ) || className.equals( "java.lang.Serializable" );
case cfData.CFDATEDATA:
try{
return Class.forName( "java.util.Date" ).isAssignableFrom( _class ); //className.equals( "java.util.Date" ) ||
}catch( ClassNotFoundException unlikely ){ return false; }
case cfData.CFSTRUCTDATA:
try{
return Class.forName( "java.util.Hashtable" ).isAssignableFrom( _class );//Class.forName( "java.util.Map" ).isAssignableFrom( _class );
}catch( ClassNotFoundException e ){ return false; }
case cfData.CFARRAYDATA:
return className.equals( "java.util.Vector" ) || _class.isArray() && ( ( (cfArrayData) _cfdata ).getDimension() == 1 );
case cfData.CFJAVAOBJECTDATA:
try{
Object theInst = ( (cfJavaObjectData) _cfdata ).getInstance();
return theInst.getClass().isAssignableFrom( _class );
}catch( Exception e ){
return false;
}
default:
return false;
}
}// exactMatch()
***/
/**
* returns a rank indicating how well matched a Class _c1 is to Class _c2.
* returns 0 if _c1 is not within the class hierarchy of _c2
* returns 1 if _c1 matches _c2 exactly (they are the same class)
* returns 2 if _c2 is an interface that _c1 implements
* returns 3+ if _c2 is a superclass of _c1
*/
private static int matchClass(Class<?> _c1, Class<?> _c2)
{
if (_c1 != null && _c2 != null && _c1.getName().equals(_c2.getName()))
{
return 1;
}
else if (_c1 != null && _c2 != null && _c2.isAssignableFrom(_c1))
{
int superclassIndex = 0;
Class<?> nextClass = _c1;
// loop thru the hierarchy of superclasses
while (nextClass != null)
{
// try interfaces first
if (isInterface(nextClass, _c2))
{
return 2 + superclassIndex;
}
superclassIndex += 3;
// no match so far, walk our way up the Object hierarchy
Class<?> superclass = nextClass.getSuperclass();
if (superclass.getName().equals(_c2.getName()))
{
return superclassIndex;
}
nextClass = superclass;
}
return 0; // shouldn't happen since we know _c2.isAssignableFrom( _c1 )
}
else
{ // classes are related
return 0;
}
}
private static boolean isInterface( Class<?> _c1, Class<?> _c2 ){
Class<?>[] interfaces = _c1.getInterfaces();
for ( int j = 0; j < interfaces.length; j++ ){
if ( _c2.getName().equals( interfaces[j].getName() ) ){
return true;
}
if ( isInterface( interfaces[j], _c2 ) ){
return true;
}
}
return false;
}
//-----------------------------------------------
//--[ Methods for handling if the underlying class is a implementation of com.allaire.cfx.cfmlPage
//-----------------------------------------------
public static void setCfmlPageContext( cfJavaObjectData obj ){
if ( obj.instance instanceof com.allaire.cfx.cfmlPage ){
cfmlPage thisPageObject = (cfmlPage)obj.instance;
//--[ Create the page context
obj.thisPageContext = new cfmlPageContextImpl( obj.parentSession );
try{
//--[ Call the trigger method to say its alive
thisPageObject.cfmlPageStarted( obj.thisPageContext );
//--[ Take a note of this object for this session
//--[ We are using the hashcode of this object since we need a unique reference for
//--[ the data bin to accept it.
obj.parentSession.setDataBin( obj.hashCode() + "", obj );
}catch(Throwable seriousError){
obj.thisPageContext = null;
}
}
//--[ Session no longer required, make it null so no hanging references
obj.parentSession = null;
}
public void removeCfmlPageContext(){
//--[ This is called when the page has finished processing.
if ( thisPageContext != null ){
cfmlPage thisPageObject = (cfmlPage)instance;
try{
thisPageObject.cfmlPageFinished( thisPageContext ); //--[ Call the trigger method
}catch(Throwable seriousError){}
thisPageContext.closeSession(); //--[ invalidate the session
thisPageContext = null;
}
}
public void dumpWDDX(int version, java.io.PrintWriter out)
{
if (!(out instanceof InstanceTrackingPrintWriter))
out = new InstanceTrackingPrintWriter(out);
// open object's structure
if (version > 10)
out.write("<s>");
else
out.write("<struct>");
// set object name
if (version > 10)
{
out.write("<v n='object'>");
out.write("<s>");
if (getInstanceClassName() != null)
out.write(getInstanceClassName());
else
out.write("[undefined class]");
out.write("</s>");
out.write("</v>");
}
else
{
out.write("<var name='object'>");
out.write("<string>");
if (getInstanceClassName() != null)
out.write(getInstanceClassName());
else
out.write("[undefined class]");
out.write("</string>");
out.write("</var>");
}
Class<?> cls = getInstanceClass();
if (cls != null)
{
// Methods
Method[] allMethods = cls.getMethods();
cfArrayData arr = cfArrayData.createArray(1);
for (int i = 0; i < allMethods.length; i++)
{
try
{
arr.addElement(new cfStringData(allMethods[i].getName() +
" (returns " + allMethods[i].getReturnType().getName() + ")"));
}
catch (cfmRunTimeException ex)
{
com.nary.Debug.printStackTrace(ex);
}
}
if (version > 10)
{
out.write("<v n='Methods'>");
arr.dumpWDDX(version, out);
out.write("</v>");
}
else
{
out.write("<var name='Methods'>");
arr.dumpWDDX(version, out);
out.write("</var>");
}
// Fields
Field[] allFields = cls.getFields();
for (int j = 0; j < allFields.length; j++)
{
if (version > 10)
{
out.write("<v name='");
}
else
{
out.write("<var name='");
}
out.write(xmlFormat.replaceChars(allFields[j].getName()));
out.write("'>");
try
{
Object fieldValue = allFields[j].get(instance);
if (fieldValue != null)
{
if (out instanceof InstanceTrackingPrintWriter)
{
if (((InstanceTrackingPrintWriter)out).isInstancePrinted(fieldValue))
{
new cfStringData("[circular reference ...]").dumpWDDX(version, out);
}
else
{
((InstanceTrackingPrintWriter)out).instancePrinted(fieldValue);
tagUtils.convertToCfData(fieldValue).dumpWDDX(version, out);
}
}
else
{
tagUtils.convertToCfData(fieldValue).dumpWDDX(version, out);
}
}
else
{
cfNullData.NULL.dumpWDDX(version, out);
}
}
catch (Exception ex)
{
new cfStringData("[undeterminable value]").dumpWDDX(version, out);
}
if (version > 10)
out.write("</v>");
else
out.write("</var>");
}
}
// close object's structure
if (version > 10)
out.write("</s>");
else
out.write("</struct>");
}
public String toString(){
return toString("");
}
public String toString(String _lbl)
{
StringBuilder out = new StringBuilder();
out.append("<table class='cfdump_table_object'>");
//--[ Print out the header
out.append("<th class='cfdump_th_object' colspan='2'>");
if (_lbl.length() > 0)
{
out.append(_lbl);
out.append(" - ");
}
out.append("object of ");
if (getInstanceClassName() != null)
out.append(getInstanceClassName());
else
out.append("[undefined class]");
out.append("</th>");
Class<?> cls = getInstanceClass();
if (cls != null)
{
// Methods
Method[] allMethods = cls.getMethods();
out.append("<tr><td class='cfdump_td_object'>Methods</td>");
out.append("<td class='cfdump_td_value'>");
for (int i = 0; i < allMethods.length; i++)
{
out.append(allMethods[i].getName());
out.append(" (returns ");
out.append(allMethods[i].getReturnType().getName());
out.append(") <br>");
}
out.append("</td></tr>");
// Fields
Field[] allFields = cls.getFields();
for (int j = 0; j < allFields.length; j++)
{
out.append("<tr><td class='cfdump_td_object'>");
out.append(allFields[j].getName());
out.append("</td>");
out.append("<td class='cfdump_td_value'>");
try
{
Object fieldValue = allFields[j].get(instance);
if (fieldValue != null)
out.append(fieldValue.toString());
else
out.append("[undefined value]");
}
catch (Exception ex)
{
out.append("[undeterminable value]");
}
out.append("</td></tr>");
}
}
out.append("</table>");
return out.toString();
}
// this version of equals() is for use by the CFML expression engine
public boolean equals( cfData _data ) throws cfmRunTimeException {
return super.equals( _data ); // throws unsupported exception
}
// this version of equals() is for use by generic Collections classes
public boolean equals( Object o )
{
if ( o instanceof cfJavaObjectData )
return instance.equals( ((cfJavaObjectData)o).instance );
return false;
}
public int hashCode() {
if ( instance != null )
return instance.hashCode();
else
return 0;
}
public cfData duplicate() {
return null; // can't duplicate Java objects
}
//--[ Override the Serialising methods as we don't wish this object to be serialised
//--[ Causes problems when we would doing things with the client scope for example.
private void writeObject( java.io.ObjectOutputStream out ) {}
private void readObject( java.io.ObjectInputStream in ) {}
}