may or may not return a scriptable object, depending on whether the object's class name is defined in scriptable.properties.
499 lines
20 KiB
Java
499 lines
20 KiB
Java
// ESLoader.java
|
|
// FESI Copyright (c) Jean-Marc Lugrin, 1999
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU Lesser General Public
|
|
// License as published by the Free Software Foundation; either
|
|
// version 2 of the License, or (at your option) any later version.
|
|
|
|
// This program 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
|
|
// Lesser General Public License for more details.
|
|
|
|
// You should have received a copy of the GNU Lesser General Public
|
|
// License along with this library; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
package FESI.Data;
|
|
|
|
import FESI.Exceptions.*;
|
|
import FESI.Interpreter.*;
|
|
import FESI.jslib.JSFunction;
|
|
|
|
import java.util.Date;
|
|
import java.util.Enumeration;
|
|
|
|
import java.lang.reflect.*;
|
|
|
|
import java.io.*;
|
|
import java.awt.event.*;
|
|
import java.util.EventListener;
|
|
import java.util.zip.*;
|
|
|
|
/**
|
|
* Implements the common functionality of package(object) and beans loaders
|
|
*/
|
|
public abstract class ESLoader extends ESObject {
|
|
|
|
// Debug support
|
|
static protected boolean debugJavaAccess = false;
|
|
public static void setDebugJavaAccess(boolean b) {
|
|
debugJavaAccess = b;
|
|
}
|
|
static public boolean isDebugJavaAccess() {
|
|
return debugJavaAccess;
|
|
}
|
|
|
|
static protected boolean debugLoader = false;
|
|
public static void setDebugLoader(boolean b) {
|
|
debugLoader = b;
|
|
}
|
|
static public boolean isDebugLoader() {
|
|
return debugLoader;
|
|
}
|
|
|
|
// Incremental package name
|
|
protected String packageName = null;
|
|
protected ESLoader previousPackage = null;
|
|
protected LocalClassLoader classLoader = null;
|
|
|
|
// the non compatible flag
|
|
static private CompatibilityDescriptor nonCompatible =
|
|
new CompatibilityDescriptor(-1, null, null);
|
|
|
|
|
|
/**
|
|
* To contruct the Bean or Package object
|
|
*/
|
|
public ESLoader(Evaluator evaluator) {
|
|
super(null, evaluator);
|
|
}
|
|
|
|
/**
|
|
* To construct a bean or package sub-object (with a specific
|
|
* partial or complete package name
|
|
* @param packageName The extension of the package name
|
|
* @param previousPackage Represents the higher level package names
|
|
* @param classLoader the class loader to use for this loader
|
|
* @param evaluator the evaluator
|
|
*/
|
|
public ESLoader(String packageName,
|
|
ESLoader previousPackage,
|
|
LocalClassLoader classLoader,
|
|
Evaluator evaluator) {
|
|
super(null, evaluator);
|
|
this.packageName = packageName;
|
|
this.previousPackage = previousPackage;
|
|
this.classLoader = classLoader;
|
|
}
|
|
|
|
/**
|
|
* Build the prefix name of the package, concatenating
|
|
* all upper level prefix separated by dots
|
|
* @return prefix of the current package name
|
|
*/
|
|
protected String buildPrefix() {
|
|
if (previousPackage == null) {
|
|
return null;
|
|
} else {
|
|
String prefix = previousPackage.buildPrefix();
|
|
if (prefix == null) {
|
|
return packageName;
|
|
} else {
|
|
return prefix + "." + packageName;
|
|
}
|
|
}
|
|
}
|
|
|
|
// overrides
|
|
public ESObject getPrototype() {
|
|
throw new ProgrammingError("Cannot get prototype of Package");
|
|
}
|
|
|
|
// overrides
|
|
public int getTypeOf() {
|
|
return EStypeObject;
|
|
}
|
|
|
|
// overrides
|
|
public ESValue getPropertyInScope(String propertyName, ScopeChain previousScope, int hash)
|
|
throws EcmaScriptException {
|
|
throw new EcmaScriptException("A loader object ("+this+") should not be part of a with statement");
|
|
}
|
|
|
|
|
|
// overrides
|
|
public boolean hasProperty(String propertyName, int hash)
|
|
throws EcmaScriptException {
|
|
return true; // So it can be dereferenced by scopechain
|
|
// and wont be created
|
|
}
|
|
|
|
// overrides
|
|
public boolean isHiddenProperty(String propertyName, int hash) {
|
|
return false;
|
|
}
|
|
|
|
// overrides
|
|
public void putProperty(String propertyName, ESValue propertyValue, int hash)
|
|
throws EcmaScriptException {
|
|
return; // None can be put by the user
|
|
}
|
|
|
|
// overrides
|
|
public void putHiddenProperty(String propertyName, ESValue propertyValue)
|
|
throws EcmaScriptException {
|
|
throw new ProgrammingError("Cannot put hidden property in " + this);
|
|
}
|
|
|
|
// overrides
|
|
public boolean deleteProperty(String propertyName, int hash)
|
|
throws EcmaScriptException {
|
|
// all possible package name do potentialy exists and
|
|
// cannot be deleted, as they are recreated at the first
|
|
// reference.
|
|
return false;
|
|
}
|
|
|
|
// overrides
|
|
public ESValue getDefaultValue(int hint)
|
|
throws EcmaScriptException {
|
|
if (hint == EStypeString) {
|
|
return new ESString(this.toString());
|
|
} else {
|
|
throw new EcmaScriptException ("No default value for " + this +
|
|
" and hint " + hint);
|
|
}
|
|
}
|
|
|
|
// overrides
|
|
public ESValue getDefaultValue()
|
|
throws EcmaScriptException {
|
|
return this.getDefaultValue(EStypeString);
|
|
}
|
|
|
|
// overrides
|
|
public ESObject doConstruct(ESObject thisObject,
|
|
ESValue[] arguments)
|
|
throws EcmaScriptException {
|
|
throw new EcmaScriptException("No contructor for loader object: " + this);
|
|
}
|
|
|
|
// overrides
|
|
public double doubleValue() {
|
|
double d = Double.NaN;
|
|
return d;
|
|
}
|
|
|
|
// overrides
|
|
public boolean booleanValue() {
|
|
return true;
|
|
}
|
|
|
|
// overrides
|
|
public String toString() {
|
|
return this.toDetailString();
|
|
}
|
|
|
|
//---------------------------------------------------------------
|
|
// Tools for the java wrapper objects
|
|
//---------------------------------------------------------------
|
|
|
|
/**
|
|
* Returns true if it is a primitive type
|
|
* @param the Class to test
|
|
* @return true if primitive type
|
|
*/
|
|
static boolean isBasicClass(Class cls) {
|
|
return cls == String.class ||
|
|
cls == Character.class ||
|
|
cls == Byte.class ||
|
|
cls == Short.class ||
|
|
cls == Integer.class ||
|
|
cls == Long.class ||
|
|
cls == Float.class ||
|
|
cls == Double.class ||
|
|
cls == Boolean.class ||
|
|
cls == Date.class;
|
|
}
|
|
|
|
// With the Hop, all instances of Number (including java.math.BigXXX) are
|
|
// treated as native numbers, so this is not called by normalizeValue.
|
|
static boolean isPrimitiveNumberClass(Class cls) {
|
|
return cls == Byte.class ||
|
|
cls == Short.class ||
|
|
cls == Integer.class ||
|
|
cls == Long.class ||
|
|
cls == Float.class ||
|
|
cls == Double.class;
|
|
}
|
|
|
|
/**
|
|
* Transform a java object to an EcmaScript value (primitive if possible)
|
|
* @param obj the object to transform
|
|
* @param evaluator the evaluator
|
|
* @return the EcmaScript object
|
|
* @exception EcmaScriptException the normalization failed
|
|
*/
|
|
public static ESValue normalizeValue(Object obj, Evaluator evaluator)
|
|
throws EcmaScriptException {
|
|
if (obj == null) {
|
|
return ESNull.theNull;
|
|
} else if (obj instanceof String) {
|
|
return new ESString((String) obj);
|
|
} else if (obj instanceof Number) {
|
|
return new ESNumber(((Number) obj).doubleValue());
|
|
} else if (obj instanceof Boolean) {
|
|
return ESBoolean.makeBoolean(((Boolean) obj).booleanValue());
|
|
} else if (obj instanceof Character) {
|
|
return new ESNumber(((Character) obj).charValue());
|
|
} else if (obj instanceof JSFunction) {
|
|
return JSWrapper.wrapJSFunction(evaluator, (JSFunction) obj);
|
|
} else if (obj instanceof JSWrapper) {
|
|
return ((JSWrapper)obj).getESObject();
|
|
} else if (obj instanceof Date) {
|
|
return new DatePrototype(evaluator, (Date) obj);
|
|
} else if (obj instanceof ESWrapper) {
|
|
return (ESWrapper) obj; // A wrapper received externally
|
|
} else if (obj instanceof ESArrayWrapper) {
|
|
return (ESArrayWrapper) obj; // An array wrapper received externally
|
|
} else if (obj.getClass().isArray()) {
|
|
return new ESArrayWrapper(obj, evaluator);
|
|
} // else if (obj instanceof helma.framework.IPathElement) { // Hannes Wallnoefer, 13. Aug 2001
|
|
// return evaluator.reval.getElementWrapper ((helma.framework.IPathElement) obj);
|
|
// }
|
|
// return new ESWrapper(obj, evaluator);
|
|
return evaluator.reval.getObjectWrapper (obj);
|
|
}
|
|
|
|
/**
|
|
* Transform a java object to an EcmaScript object (not a primitive)
|
|
* @param obj the object to transform
|
|
* @param evaluator the evaluator
|
|
* @return the EcmaScript object
|
|
* @exception EcmaScriptException the normalization failed
|
|
*/
|
|
public static ESObject normalizeObject(Object obj, Evaluator evaluator)
|
|
throws EcmaScriptException {
|
|
ESValue value = normalizeValue(obj, evaluator);
|
|
return (ESObject) value.toESObject(evaluator);
|
|
}
|
|
|
|
/**
|
|
* Convert the primitive class types to their corresponding
|
|
* class types. Must be called for primitive classes only.
|
|
*
|
|
* @param target The primitive class to convert
|
|
* @return The converted class
|
|
*/
|
|
private static Class convertPrimitive(Class target) {
|
|
if (target==java.lang.Boolean.TYPE) {
|
|
target=Boolean.class;
|
|
} else if (target==java.lang.Character.TYPE) {
|
|
target=Character.class;
|
|
} else if (target==java.lang.Byte.TYPE) {
|
|
target=Byte.class;
|
|
} else if (target==java.lang.Short.TYPE) {
|
|
target=Short.class;
|
|
} else if (target==java.lang.Integer.TYPE) {
|
|
target=Integer.class;
|
|
} else if (target==java.lang.Long.TYPE) {
|
|
target=Long.class;
|
|
} else if (target==java.lang.Float.TYPE) {
|
|
target=Float.class;
|
|
} else if (target==java.lang.Double.TYPE) {
|
|
target=Double.class;
|
|
} else {
|
|
throw new ProgrammingError("Not a recognized primitive type: " + target);
|
|
}
|
|
return target;
|
|
}
|
|
|
|
/**
|
|
* Get a number correlated to the wideness of the class in some lousy sense
|
|
*/
|
|
static private int getNumberSize(Class cls) {
|
|
|
|
if (cls == Byte.class) {
|
|
return 1;
|
|
} else if (cls == Character.class) {
|
|
return 2;
|
|
} else if (cls == Short.class) { // short and char widen in the same way
|
|
return 2;
|
|
} else if (cls == Integer.class) {
|
|
return 3;
|
|
} else if (cls == Long.class) {
|
|
return 4;
|
|
} else if (cls == Float.class) {
|
|
return 5;
|
|
} else if (cls == Double.class) {
|
|
return 6;
|
|
} else {
|
|
throw new ProgrammingError("Unexpected number class");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check that each object in the paremeter array can be converted
|
|
* to the type specified by the target array and convert them if needed.
|
|
* <P>Even if the parameters are compatible with an EcmaScript value,
|
|
|
|
* some may have to be converted to an intermediate type. For example
|
|
* an EcmaScript string of 1 character long is compatible with a Java
|
|
* Character, but some conversion is needed. Arrays need a similar
|
|
* special processing.
|
|
* <P> The parameters have been converted to java Objects by the routine
|
|
* toJavaObject. Wrapped objects (java objects given to an EcmaScript
|
|
* routine and given back as parameters) have been unwrapped, so they
|
|
* are their original object again (including arrays), we do therefore
|
|
* not have ESWrapped objects as parameters.
|
|
* ESObjects have been wrapped in a JSObject, including Array objects.
|
|
* The handling of array is more delicate as they
|
|
* could not be converted to a cannonical form - we must know the element
|
|
* type to understand if they are convertible.
|
|
* <P> The target classes which are primitive are converted to their
|
|
* object counterpart, as only object can be given as parameter and the
|
|
* invoke mechanism will convert them back to primitive if needed.
|
|
* <P>All the conversion needed are described in the returned
|
|
* compatibilityDescriptor and will only be applied for the selected
|
|
* routine.
|
|
* <P>The distance is a metric on how "far" a parameter list is
|
|
* from the immediate value (used currntly only for simple value
|
|
* widening). It allows to select the routine having the nearest
|
|
* parameter list. This is not totally full proof and multiple routine
|
|
* can have the same distance (including 0, because of the conversion
|
|
* of primitive type to the corresponding objects).
|
|
* <P>The tracing must allow to find the logic of the conversion.
|
|
* @param target The class objects of the target routine
|
|
* @param parms The EcmaScript objects converted to Java objects (IN/OUT)
|
|
* @return a compatibility descriptor.
|
|
*/
|
|
static CompatibilityDescriptor areParametersCompatible(Class target[], Object params[]) {
|
|
int n = target.length;
|
|
if (n!=params.length) return nonCompatible; // Ensure we have the same number
|
|
boolean [] convertToChar = null; // flag to indicate if conversion to char is needed
|
|
Object [] convertedArrays = null; // Converted array if any needed
|
|
int distance = 0; // For perfect match, something added for widening
|
|
for (int i=0; i<n; i++) {
|
|
boolean accepted = false;
|
|
Class targetClass = target[i];
|
|
Class sourceClass = null;
|
|
String debugInfo = " not accepted";
|
|
if (params[i] == null) {
|
|
// A null parameter is of type Object, so we check
|
|
// that whatever is the target class be an object
|
|
if (targetClass.isPrimitive()) { // or: Object.class.isAssignableFrom(targetClass)
|
|
accepted = false;
|
|
debugInfo = " rejected (null cannot be assigned to primitive)";
|
|
|
|
} else {
|
|
accepted = true;
|
|
debugInfo = " accepted (null to Object)";
|
|
}
|
|
} else {
|
|
// We consider all primitive types as they object
|
|
// equivallent, as the parameter can only be done as
|
|
// object anyhow. Invoke will convert back if needed.
|
|
if (targetClass.isPrimitive()) {
|
|
// To accept long by Long, etc... - must be done after test of assigning null to object
|
|
targetClass = convertPrimitive(targetClass);
|
|
}
|
|
// The simplest case is direct object compatibility
|
|
sourceClass = params[i].getClass();
|
|
accepted = targetClass.isAssignableFrom(sourceClass);
|
|
debugInfo = " accepted (subclassing)";
|
|
|
|
if (!accepted) {
|
|
// If we do not have direct object compatibility, we check various
|
|
// allowed conversions.
|
|
|
|
// Handle number and number widening
|
|
if ((isPrimitiveNumberClass(sourceClass) ||
|
|
sourceClass == Character.class)
|
|
&& isPrimitiveNumberClass(targetClass)) {
|
|
// Can be widened ?
|
|
int targetSize = getNumberSize(targetClass);
|
|
int sourceSize = getNumberSize(sourceClass);
|
|
if (targetSize>sourceSize) {
|
|
accepted = true; // if == already accepted because same class
|
|
// or must be rejected (because char != short)
|
|
distance += Math.abs(targetSize-sourceSize);
|
|
debugInfo = " accepted (number widening: " + distance + ")";
|
|
} else {
|
|
debugInfo = " rejected (not widening numbers)";
|
|
}
|
|
// Handle String of length 1 as a Char, which can be converted to a number
|
|
} else if ((targetClass == Character.class ||
|
|
targetClass == Integer.class ||
|
|
targetClass == Long.class ||
|
|
targetClass == Float.class ||
|
|
targetClass == Double.class)
|
|
&& params[i] instanceof String) {
|
|
if (((String) params[i]).length()==1) {
|
|
accepted = true; // will require conversion of parameter
|
|
if (convertToChar == null) {
|
|
convertToChar = new boolean[n];
|
|
}
|
|
convertToChar[i] = true;
|
|
debugInfo = " accepted (String(1) as Character)";
|
|
} else {
|
|
debugInfo = " rejected (String not of length 1)";
|
|
}
|
|
|
|
// Handle array conversion if not from a native java array
|
|
} else if (targetClass.isArray()) {
|
|
if (params[i] instanceof JSWrapper) {
|
|
ESObject esArray = ((JSWrapper) params[i]).getESObject();
|
|
if (esArray instanceof ArrayPrototype) {
|
|
Object array = null;
|
|
try {
|
|
// We convert to the orginal class (possibly a primitive type)
|
|
array = ((ArrayPrototype) esArray).toJavaArray(targetClass.getComponentType());
|
|
accepted = true;
|
|
debugInfo = " accepted (array converted)";
|
|
if (convertedArrays == null) {
|
|
convertedArrays = new Object[n];
|
|
}
|
|
convertedArrays[i] = array; // save it for replacement at end
|
|
} catch (EcmaScriptException e) {
|
|
// ignore
|
|
debugInfo = " rejected ("+e.getMessage()+")";
|
|
}
|
|
} else {
|
|
debugInfo = " rejected (EcmaScript object is not an array)";
|
|
}
|
|
} else {
|
|
debugInfo = " rejected (only same native array or EcmaScript Array can be assigned to array)";
|
|
}
|
|
|
|
} else {
|
|
debugInfo = " rejected (incompatible types)";
|
|
}
|
|
} // if ! acccepted
|
|
} // if ! null
|
|
|
|
if (debugJavaAccess) System.out.println (" Assign " + sourceClass +
|
|
" to " + target[i] +
|
|
debugInfo);
|
|
if (!accepted) return nonCompatible;
|
|
|
|
} // for
|
|
|
|
// Compatible, return appropriate descriptor for future
|
|
// processing of conversion of the "nearest" method
|
|
return new CompatibilityDescriptor(distance,convertToChar,convertedArrays);
|
|
}
|
|
|
|
// overrides
|
|
static public String typeName(Class t) {
|
|
String brackets = "";
|
|
while (t.isArray()) {
|
|
brackets += "[]";
|
|
t = t.getComponentType();
|
|
}
|
|
return t.getName() + brackets;
|
|
}
|
|
|
|
|
|
}
|