From e57a939fa0c27b438714cb8564623616574bd7e6 Mon Sep 17 00:00:00 2001 From: hns Date: Thu, 18 Dec 2003 17:34:13 +0000 Subject: [PATCH] Make type handling case-insensitive, change default prototypes to first-letter-upper-case, do some heavily needed refactoring around helma.scripting.rhino.HopObject and helma.framework.core.TypeManager. --- src/helma/framework/core/Application.java | 16 ++- src/helma/framework/core/Prototype.java | 58 ++++++-- .../framework/core/RequestEvaluator.java | 1 + src/helma/framework/core/RequestPath.java | 1 + src/helma/framework/core/Skin.java | 2 +- src/helma/framework/core/TypeManager.java | 107 +++++++------- src/helma/framework/core/ZippedAppFile.java | 2 +- src/helma/objectmodel/db/Node.java | 2 +- src/helma/scripting/rhino/HopObject.java | 53 +++++-- src/helma/scripting/rhino/RhinoCore.java | 133 ++++++++---------- 10 files changed, 220 insertions(+), 155 deletions(-) diff --git a/src/helma/framework/core/Application.java b/src/helma/framework/core/Application.java index affb3661..fe118837 100644 --- a/src/helma/framework/core/Application.java +++ b/src/helma/framework/core/Application.java @@ -283,7 +283,7 @@ public final class Application implements IPathElement, Runnable { // set the context classloader. Note that this must be done before // using the logging framework so that a new LogFactory gets created // for this app. - Thread.currentThread().setContextClassLoader(typemgr.loader); + Thread.currentThread().setContextClassLoader(typemgr.getClassLoader()); if (Server.getServer() != null) { Vector extensions = Server.getServer().getExtensions(); @@ -356,7 +356,7 @@ public final class Application implements IPathElement, Runnable { nmgr = new NodeManager(this, dbDir.getAbsolutePath(), props); // reset the classloader to the parent/system/server classloader. - Thread.currentThread().setContextClassLoader(typemgr.loader.getParent()); + Thread.currentThread().setContextClassLoader(typemgr.getClassLoader().getParent()); } @@ -695,13 +695,15 @@ public final class Application implements IPathElement, Runnable { try { if (classMapping.containsKey("root.factory.class") && classMapping.containsKey("root.factory.method")) { - Class c = typemgr.loader.loadClass(classMapping.getProperty("root.factory.class")); + String rootFactory = classMapping.getProperty("root.factory.class"); + Class c = typemgr.getClassLoader().loadClass(rootFactory); Method m = c.getMethod(classMapping.getProperty("root.factory.method"), null); rootObject = m.invoke(c, null); } else { - Class c = typemgr.loader.loadClass(classMapping.getProperty("root")); + String rootClass = classMapping.getProperty("root"); + Class c = typemgr.getClassLoader().loadClass(rootClass); rootObject = c.newInstance(); } @@ -795,14 +797,14 @@ public final class Application implements IPathElement, Runnable { * Return the prototype with the given name, if it exists */ public Prototype getPrototypeByName(String name) { - return (Prototype) typemgr.prototypes.get(name); + return typemgr.getPrototype(name); } /** * Return a collection containing all prototypes defined for this application */ public Collection getPrototypes() { - return typemgr.prototypes.values(); + return typemgr.getPrototypes(); } /** @@ -1193,7 +1195,7 @@ public final class Application implements IPathElement, Runnable { * Return the application's classloader */ public ClassLoader getClassLoader() { - return typemgr.loader; + return typemgr.getClassLoader(); } ////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/helma/framework/core/Prototype.java b/src/helma/framework/core/Prototype.java index 28d2f773..ca180be2 100644 --- a/src/helma/framework/core/Prototype.java +++ b/src/helma/framework/core/Prototype.java @@ -19,6 +19,7 @@ package helma.framework.core; import helma.objectmodel.db.DbMapping; import helma.scripting.*; import helma.util.SystemMap; +import helma.util.SystemProperties; import java.io.*; import java.util.*; @@ -30,6 +31,7 @@ import java.util.*; */ public final class Prototype { String name; + String lowerCaseName; Application app; File directory; File[] files; @@ -61,15 +63,37 @@ public final class Prototype { /** * Creates a new Prototype object. * - * @param name ... - * @param dir ... - * @param app ... + * @param name the prototype's name + * @param dir the prototype directory, if known + * @param app the application this prototype is a part of */ public Prototype(String name, File dir, Application app) { // app.logEvent ("Constructing Prototype "+app.getName()+"/"+name); this.app = app; this.name = name; - this.directory = dir; + lowerCaseName = name.toLowerCase(); + + if (dir != null) { + directory = dir; + } else { + directory = new File(app.appDir, name); + // a little bit of overkill to maintain backwards compatibility + // with lower case type names... + if (!directory.isDirectory()) { + File lowerDir = new File(app.appDir, lowerCaseName); + if (lowerDir.isDirectory()) { + directory = lowerDir; + } + } + } + + // Create and register type properties file + File propfile = new File(directory, "type.properties"); + SystemProperties props = new SystemProperties(propfile.getAbsolutePath()); + dbmap = new DbMapping(app, name, props); + // we don't need to put the DbMapping into proto.updatables, because + // dbmappings are checked separately in TypeManager.checkFiles() for + // each request code = new HashMap(); zippedCode = new HashMap(); @@ -145,7 +169,7 @@ public final class Prototype { */ public void setParentPrototype(Prototype parent) { // this is not allowed for the hopobject and global prototypes - if ("hopobject".equalsIgnoreCase(name) || "global".equalsIgnoreCase(name)) { + if ("HopObject".equals(name) || "global".equals(name)) { return; } @@ -164,11 +188,11 @@ public final class Prototype { * Check if the given prototype is within this prototype's parent chain. */ public final boolean isInstanceOf(String pname) { - if (name.equals(pname)) { + if (name.equals(pname) || lowerCaseName.equals(pname)) { return true; } - if ((parent != null) && !"hopobject".equalsIgnoreCase(parent.getName())) { + if ((parent != null) && !"HopObject".equals(parent.getName())) { return parent.isInstanceOf(pname); } @@ -185,12 +209,17 @@ public final class Prototype { Prototype p = parent; - while ((p != null) && !"hopobject".equalsIgnoreCase(p.getName())) { + while ((p != null) && !"hopobject".equals(p.getLowerCaseName())) { Object old = handlers.put(p.name, obj); // if an object was already registered by this name, put it back in again. if (old != null) { handlers.put(p.name, old); } + // same with lower case name + old = handlers.put(p.lowerCaseName, obj); + if (old != null) { + handlers.put(p.lowerCaseName, old); + } p = p.parent; } @@ -201,7 +230,7 @@ public final class Prototype { * * @param dbmap ... */ - public void setDbMapping(DbMapping dbmap) { + protected void setDbMapping(DbMapping dbmap) { this.dbmap = dbmap; } @@ -245,7 +274,7 @@ public final class Prototype { } /** - * + * Return this prototype's name * * @return ... */ @@ -253,6 +282,15 @@ public final class Prototype { return name; } + /** + * Return this prototype's name in lower case letters + * + * @return ... + */ + public String getLowerCaseName() { + return lowerCaseName; + } + /** * Get the last time any script has been re-read for this prototype. */ diff --git a/src/helma/framework/core/RequestEvaluator.java b/src/helma/framework/core/RequestEvaluator.java index 9a69293a..3703ce83 100644 --- a/src/helma/framework/core/RequestEvaluator.java +++ b/src/helma/framework/core/RequestEvaluator.java @@ -294,6 +294,7 @@ public final class RequestEvaluator implements Runnable { // immediately register objects with their direct prototype name if (protos[i] != null) { macroHandlers.put(protos[i].getName(), obj); + macroHandlers.put(protos[i].getLowerCaseName(), obj); } } diff --git a/src/helma/framework/core/RequestPath.java b/src/helma/framework/core/RequestPath.java index c4e99b7e..65cff8e1 100644 --- a/src/helma/framework/core/RequestPath.java +++ b/src/helma/framework/core/RequestPath.java @@ -61,6 +61,7 @@ public class RequestPath { if (proto != null) { primaryProtos.put(proto.getName(), obj); + primaryProtos.put(proto.getLowerCaseName(), obj); proto.registerParents(secondaryProtos, obj); } } diff --git a/src/helma/framework/core/Skin.java b/src/helma/framework/core/Skin.java index 8c150887..8dbdfa33 100644 --- a/src/helma/framework/core/Skin.java +++ b/src/helma/framework/core/Skin.java @@ -412,7 +412,7 @@ public final class Skin { // not a global macro - need to find handler object // was called with this object - check it or its parents for matching prototype if (!handler.equals("this") && - !handler.equals(app.getPrototypeName(thisObject))) { + !handler.equalsIgnoreCase(app.getPrototypeName(thisObject))) { // the handler object is not what we want Object n = thisObject; diff --git a/src/helma/framework/core/TypeManager.java b/src/helma/framework/core/TypeManager.java index 25fb5a74..498dc494 100644 --- a/src/helma/framework/core/TypeManager.java +++ b/src/helma/framework/core/TypeManager.java @@ -30,30 +30,34 @@ import java.util.*; * applications and updates the evaluators if anything has changed. */ public final class TypeManager { - final static String[] standardTypes = { "user", "global", "root", "hopobject" }; + final static String[] standardTypes = { "User", "Global", "Root", "HopObject" }; final static String templateExtension = ".hsp"; final static String scriptExtension = ".js"; final static String actionExtension = ".hac"; final static String skinExtension = ".skin"; - Application app; - File appDir; - HashMap prototypes; // map of prototypes - HashMap zipfiles; // map of zipped script files - HashSet jarfiles; // set of Java archives - long lastCheck = 0; - long appDirMod = 0; + + private Application app; + private File appDir; + // map of prototypes + private HashMap prototypes; + // map of zipped script files + private HashMap zipfiles; + // set of Java archives + private HashSet jarfiles; + private long lastCheck = 0; + private long appDirMod = 0; // a checksum that changes whenever something in the application files changes. - long checksum; + private long checksum; // the hopobject prototype - Prototype hopobjectProto; + // private Prototype hopobjectProto; // the global prototype - Prototype globalProto; + // private Prototype globalProto; // app specific class loader, includes jar files in the app directory - AppClassLoader loader; + private AppClassLoader loader; /** * Creates a new TypeManager object. @@ -114,13 +118,9 @@ public final class TypeManager { */ public void createPrototypes() { // create standard prototypes. - createPrototype("root"); - createPrototype("user"); - - // get references to hopobject and global protos, - // since we need it regularly when setting parent prototypes. - hopobjectProto = createPrototype("hopobject"); - globalProto = createPrototype("global"); + for (int i = 0; i < standardTypes.length; i++) { + createPrototype(standardTypes[i], null); + } // loop through directories and create prototypes checkFiles(); @@ -236,19 +236,12 @@ public final class TypeManager { DbMapping dbmap = proto.getDbMapping(); if ((dbmap != null) && dbmap.needsUpdate()) { + // call dbmap.update(). This also checks the + // parent prototype for prototypes other than + // global and HopObject, which is a bit awkward... + // I mean we're the type manager, so this should + // be part of our job, right? dbmap.update(); - - // this is now done in dbmap.update()!!! - /*if ((proto != hopobjectProto) && (proto != globalProto)) { - // set parent prototype, in case it has changed. - String parentName = dbmap.getExtends(); - - if (parentName != null) { - proto.setParentPrototype(getPrototype(parentName)); - } else if (!app.isJavaPrototype(proto.getName())) { - proto.setParentPrototype(hopobjectProto); - } - } */ } } @@ -292,40 +285,48 @@ public final class TypeManager { return checksum; } + /** + * Return the class loader used by this application. + * + * @return the ClassLoader + */ + public ClassLoader getClassLoader() { + return loader; + } + + /** + * Return a collection containing the prototypes defined for this type + * manager. + * + * @return a collection containing the prototypes + */ + public Collection getPrototypes() { + return prototypes.values(); + } + /** * Get a prototype defined for this application */ public Prototype getPrototype(String typename) { - return (Prototype) prototypes.get(typename); + if (typename == null) { + return null; + } + return (Prototype) prototypes.get(typename.toLowerCase()); } /** - * Get a prototype, creating it if it doesn't already exist. Note - * that it doesn't create a DbMapping - this is left to the - * caller (e.g. ZippedAppFile). - */ - public Prototype createPrototype(String typename) { - return createPrototype(typename, new File(appDir, typename)); - } - - /** - * Create a prototype from a directory containing scripts and other stuff + * Create and register a new Prototype. + * + * @param typename the name of the prototype + * @param dir the prototype directory if it is know, or null if we + * ought to find out by ourselves + * @return the newly created prototype */ public Prototype createPrototype(String typename, File dir) { Prototype proto = new Prototype(typename, dir, app); - // Create and register type properties file - File propfile = new File(dir, "type.properties"); - SystemProperties props = new SystemProperties(propfile.getAbsolutePath()); - DbMapping dbmap = new DbMapping(app, typename, props); - - // we don't need to put the DbMapping into proto.updatables, because - // dbmappings are checked separately in checkFiles for each request - // proto.updatables.put ("type.properties", dbmap); - proto.setDbMapping(dbmap); - // put the prototype into our map - prototypes.put(typename, proto); + prototypes.put(proto.getLowerCaseName(), proto); return proto; } diff --git a/src/helma/framework/core/ZippedAppFile.java b/src/helma/framework/core/ZippedAppFile.java index 6057c1ed..49ef0068 100644 --- a/src/helma/framework/core/ZippedAppFile.java +++ b/src/helma/framework/core/ZippedAppFile.java @@ -97,7 +97,7 @@ public class ZippedAppFile implements Updatable { Prototype proto = app.typemgr.getPrototype(dir); if (proto == null) { - proto = app.typemgr.createPrototype(dir); + proto = app.typemgr.createPrototype(dir, null); newPrototypes.add(proto); } diff --git a/src/helma/objectmodel/db/Node.java b/src/helma/objectmodel/db/Node.java index f2014e00..5184cf3f 100644 --- a/src/helma/objectmodel/db/Node.java +++ b/src/helma/objectmodel/db/Node.java @@ -505,7 +505,7 @@ public final class Node implements INode, Serializable { public String getPrototype() { // if prototype is null, it's a vanilla HopObject. if (prototype == null) { - return "hopobject"; + return "HopObject"; } return prototype; diff --git a/src/helma/scripting/rhino/HopObject.java b/src/helma/scripting/rhino/HopObject.java index 862048f6..e6c5fbab 100644 --- a/src/helma/scripting/rhino/HopObject.java +++ b/src/helma/scripting/rhino/HopObject.java @@ -78,6 +78,35 @@ public class HopObject extends ScriptableObject implements Wrapper { setPrototype(proto); } + public static HopObject init(Scriptable scope) + throws PropertyException { + int attributes = READONLY | DONTENUM | PERMANENT; + + // create prototype object + HopObject proto = new HopObject(); + proto.setPrototype(getObjectPrototype(scope)); + + // install JavaScript methods and properties + Method[] methods = HopObject.class.getDeclaredMethods(); + for (int i=0; i"+super.get(name, start)); Object retval = null; if (node == null) { diff --git a/src/helma/scripting/rhino/RhinoCore.java b/src/helma/scripting/rhino/RhinoCore.java index 3b65c57b..04bc4329 100644 --- a/src/helma/scripting/rhino/RhinoCore.java +++ b/src/helma/scripting/rhino/RhinoCore.java @@ -51,6 +51,9 @@ public final class RhinoCore { // the wrap factory WrapFactory wrapper; + // the prototype for HopObject + ScriptableObject hopObjectProto; + // the prototype for path objects PathWrapper pathProto; @@ -90,20 +93,14 @@ public final class RhinoCore { global = (GlobalObject) context.initStandardObjects(g); - ScriptableObject.defineClass(global, HopObject.class); - ScriptableObject.defineClass(global, FileObject.class); - ScriptableObject.defineClass(global, FtpObject.class); - pathProto = new PathWrapper(this); + hopObjectProto = HopObject.init(global); + FileObject.init(global); + FtpObject.init(global); ImageObject.init(global); XmlRpcObject.init(global); MailObject.init(global, app.getProperties()); - - registerPrototype("hopobject", - (ScriptableObject) ScriptableObject - .getClassPrototype(global, "HopObject")); - registerPrototype("global", global); // add some convenience functions to string, date and number prototypes Scriptable stringProto = ScriptableObject.getClassPrototype(global, "String"); @@ -150,33 +147,34 @@ public final class RhinoCore { * * @param prototype the prototype to be created */ - synchronized void initPrototype(Prototype prototype) { + synchronized TypeInfo initPrototype(Prototype prototype) { String name = prototype.getName(); + String lowerCaseName = prototype.getLowerCaseName(); + + TypeInfo type = (TypeInfo) prototypes.get(lowerCaseName); // check if the prototype info exists already - ScriptableObject op = getRawPrototype(name); + ScriptableObject op = (type == null) ? null : type.objectPrototype; // if prototype info doesn't exist (i.e. is a standard prototype // built by HopExtension), create it. if (op == null) { - try { + if ("global".equals(lowerCaseName)) { + op = global; + } else if ("hopobject".equals(lowerCaseName)) { + op = hopObjectProto; + } else { op = new HopObject(name); op.setParentScope(global); - // op.put("prototypename", op, name); - } catch (Exception ignore) { - System.err.println("Error creating prototype: " + ignore); - ignore.printStackTrace(); } - registerPrototype(name, op); + type = registerPrototype(prototype, op); } // Register a constructor for all types except global. - // This will first create a new prototyped hopobject and then calls + // This will first create a new prototyped HopObject and then calls // the actual (scripted) constructor on it. - if (!"global".equalsIgnoreCase(name) && - !"root".equalsIgnoreCase(name) && - !"hopobject".equalsIgnoreCase(name)) { + if (!"global".equals(lowerCaseName)) { try { installConstructor(name, op); } catch (Exception ignore) { @@ -184,18 +182,20 @@ public final class RhinoCore { ignore.printStackTrace(); } } + + return type; } /** * Set up a prototype, parsing and compiling all its script files. * - * @param prototype the prototype to update/evaluate/compile * @param type the info, containing the object proto, last update time and * the set of compiled functions properties */ - synchronized void evaluatePrototype(Prototype prototype, TypeInfo type) { - // System.err.println("EVALUATING PROTO: "+prototype); + synchronized void evaluatePrototype(TypeInfo type) { + Scriptable op = type.objectPrototype; + Prototype prototype = type.frameworkPrototype; // set the parent prototype in case it hasn't been done before // or it has changed... @@ -258,8 +258,9 @@ public final class RhinoCore { */ private void setParentPrototype(Prototype prototype, Scriptable op) { String name = prototype.getName(); + String lowerCaseName = prototype.getLowerCaseName(); - if (!"global".equalsIgnoreCase(name) && !"hopobject".equalsIgnoreCase(name)) { + if (!"global".equals(lowerCaseName) && !"hopobject".equals(lowerCaseName)) { // get the prototype's prototype if possible and necessary Scriptable opp = null; @@ -282,12 +283,12 @@ public final class RhinoCore { /** * This is a version of org.mozilla.javascript.FunctionObject.addAsConstructor() * that does not set the constructor property in the prototype. This is because - * we want our own scripted constructor function to prevail, if it is defined. + * we want our own scripted constructor function to be visible, if it is defined. * * @param name the name of the constructor * @param op the object prototype */ - private void installConstructor(String name, Scriptable op) { + void installConstructor(String name, Scriptable op) { FunctionObject fo = new FunctionObject(name, HopObject.hopObjCtor, global); ScriptRuntime.setFunctionProtoAndParent(global, fo); @@ -315,27 +316,23 @@ public final class RhinoCore { for (Iterator i = protos.iterator(); i.hasNext();) { Prototype proto = (Prototype) i.next(); - TypeInfo type = (TypeInfo) prototypes.get(proto.getName()); + TypeInfo type = (TypeInfo) prototypes.get(proto.getLowerCaseName()); if (type == null) { // a prototype we don't know anything about yet. Init local update info. - initPrototype(proto); - type = (TypeInfo) prototypes.get(proto.getName()); + type = initPrototype(proto); } // only update prototype if it has already been initialized. // otherwise, this will be done on demand // System.err.println ("CHECKING PROTO "+proto+": "+type); if (type.lastUpdate > -1) { - Prototype p = app.typemgr.getPrototype(type.protoName); + // let the type manager scan the prototype's directory + app.typemgr.updatePrototype(proto); - if (p != null) { - // System.err.println ("UPDATING PROTO: "+p); - app.typemgr.updatePrototype(p); - - if (p.getLastUpdate() > type.lastUpdate) { - evaluatePrototype(p, type); - } + // and re-evaluate if necessary + if (type.needsUpdate()) { + evaluatePrototype(type); } } } @@ -343,19 +340,6 @@ public final class RhinoCore { lastUpdate = System.currentTimeMillis(); } - /** - * Get a raw prototype, i.e. in potentially unfinished state - * without checking if it needs to be updated. - */ - private ScriptableObject getRawPrototype(String protoName) { - if (protoName == null) { - return null; - } - - TypeInfo type = (TypeInfo) prototypes.get(protoName); - - return (type == null) ? null : type.objectPrototype; - } /** * A version of getPrototype() that retrieves a prototype and checks @@ -394,20 +378,16 @@ public final class RhinoCore { return null; } - TypeInfo type = (TypeInfo) prototypes.get(protoName); + TypeInfo type = (TypeInfo) prototypes.get(protoName.toLowerCase()); // if type exists and hasn't been evaluated (used) yet, evaluate it now. // otherwise, it has already been evaluated for this request by updatePrototypes(), // which is called before a request is handled. if ((type != null) && (type.lastUpdate == -1)) { - Prototype p = app.typemgr.getPrototype(protoName); + app.typemgr.updatePrototype(type.frameworkPrototype); - if (p != null) { - app.typemgr.updatePrototype(p); - - if (p.getLastUpdate() > type.lastUpdate) { - evaluatePrototype(p, type); - } + if (type.needsUpdate()) { + evaluatePrototype(type); } } @@ -417,10 +397,10 @@ public final class RhinoCore { /** * Register an object prototype for a prototype name. */ - private void registerPrototype(String protoName, ScriptableObject op) { - if ((protoName != null) && (op != null)) { - prototypes.put(protoName, new TypeInfo(op, protoName)); - } + private TypeInfo registerPrototype(Prototype proto, ScriptableObject op) { + TypeInfo type = new TypeInfo(proto, op); + prototypes.put(proto.getLowerCaseName(), type); + return type; } /** @@ -561,8 +541,8 @@ public final class RhinoCore { Scriptable op = getPrototype(prototypeName); if (op == null) { - prototypeName = "hopobject"; - op = getPrototype("hopobject"); + prototypeName = "HopObject"; + op = getPrototype("HopObject"); } w = new JavaObject(global, e, prototypeName, op, this); @@ -602,8 +582,8 @@ public final class RhinoCore { // no prototype found for this node? if (op == null) { - op = getValidPrototype("hopobject"); - protoname = "hopobject"; + op = getValidPrototype("HopObject"); + protoname = "HopObject"; } esn = new HopObject(protoname, op); @@ -797,7 +777,7 @@ public final class RhinoCore { } // mark prototype as broken if (type.error == null && e instanceof EcmaError) { - if ("global".equals(type.protoName)) { + if ("global".equals(type.frameworkPrototype.getLowerCaseName())) { globalError = (EcmaError) e; } else { type.error = (EcmaError) e; @@ -828,7 +808,10 @@ public final class RhinoCore { */ class TypeInfo { - // the object prototype for this type + // the framework prototype object + Prototype frameworkPrototype; + + // the JavaScript prototype for this type ScriptableObject objectPrototype; // timestamp of last update. This is -1 so even an empty prototype directory @@ -837,7 +820,7 @@ public final class RhinoCore { long lastUpdate = -1; // the prototype name - String protoName; + // String protoName; // a set of property values that were defined in last script compliation Set compiledFunctions; @@ -847,9 +830,9 @@ public final class RhinoCore { EcmaError error; - public TypeInfo(ScriptableObject op, String name) { + public TypeInfo(Prototype proto, ScriptableObject op) { + frameworkPrototype = proto; objectPrototype = op; - protoName = name; compiledFunctions = new HashSet(0); // remember properties already defined on this object prototype predefinedProperties = new HashSet(); @@ -859,8 +842,12 @@ public final class RhinoCore { } } + public boolean needsUpdate() { + return frameworkPrototype.getLastUpdate() > lastUpdate; + } + public String toString() { - return ("TypeInfo[" + protoName + "," + new Date(lastUpdate) + "]"); + return ("TypeInfo[" + frameworkPrototype + "," + new Date(lastUpdate) + "]"); } }