// 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); } return new ESWrapper(obj, evaluator); } /** * 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. *
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. *
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. *
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. *
All the conversion needed are described in the returned * compatibilityDescriptor and will only be applied for the selected * routine. *
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). *
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