diff --git a/src/helma/scripting/rhino/GlobalObject.java b/src/helma/scripting/rhino/GlobalObject.java index d6db2cac..c83b5a92 100644 --- a/src/helma/scripting/rhino/GlobalObject.java +++ b/src/helma/scripting/rhino/GlobalObject.java @@ -24,10 +24,7 @@ import helma.util.MimePart; import helma.util.XmlUtils; import org.mozilla.javascript.*; -import java.util.Map; -import java.util.Date; -import java.util.Locale; -import java.util.TimeZone; +import java.util.*; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; @@ -37,10 +34,14 @@ import java.io.*; /** * */ -public class GlobalObject extends ImporterTopLevel { +public class GlobalObject extends ImporterTopLevel implements PropertyRecorder { Application app; RhinoCore core; + // fields to implement PropertyRecorder + private boolean isRecording = false; + private HashSet changedProperties; + /** * Creates a new GlobalObject object. * @@ -81,6 +82,20 @@ public class GlobalObject extends ImporterTopLevel { return "GlobalObject"; } + /** + * Override ScriptableObject.put() to implement PropertyRecorder interface. + * @param name + * @param start + * @param value + */ + public void put(String name, Scriptable start, Object value) { + // register property for PropertyRecorder interface + if (isRecording) { + changedProperties.add(name); + } + super.put(name, start, value); + } + /** * * @@ -500,4 +515,36 @@ public class GlobalObject extends ImporterTopLevel { } return Context.toString(obj); } + + /** + * Tell this PropertyRecorder to start recording changes to properties + */ + public void startRecording() { + changedProperties = new HashSet(); + isRecording = true; + } + + /** + * Tell this PropertyRecorder to stop recording changes to properties + */ + public void stopRecording() { + isRecording = false; + } + + /** + * Returns a set containing the names of properties changed since + * the last time startRecording() was called. + * + * @return a Set containing the names of changed properties + */ + public Set getChangeSet() { + return changedProperties; + } + + /** + * Clear the set of changed properties. + */ + public void clearChangeSet() { + changedProperties = null; + } } diff --git a/src/helma/scripting/rhino/HopObject.java b/src/helma/scripting/rhino/HopObject.java index 94a7526f..fcba8c8d 100644 --- a/src/helma/scripting/rhino/HopObject.java +++ b/src/helma/scripting/rhino/HopObject.java @@ -25,16 +25,13 @@ import org.mozilla.javascript.*; import java.lang.reflect.Method; import java.lang.reflect.Constructor; -import java.util.ArrayList; -import java.util.Date; -import java.util.Enumeration; -import java.util.Map; +import java.util.*; import java.io.UnsupportedEncodingException; /** * */ -public class HopObject extends ScriptableObject implements Wrapper { +public class HopObject extends ScriptableObject implements Wrapper, PropertyRecorder { static Method hopObjCtor; static { @@ -53,6 +50,10 @@ public class HopObject extends ScriptableObject implements Wrapper { INode node; RhinoCore core; + // fields to implement PropertyRecorder + private boolean isRecording = false; + private HashSet changedProperties; + /** * Creates a new HopObject object. */ @@ -685,6 +686,11 @@ public class HopObject extends ScriptableObject implements Wrapper { * @param value ... */ public void put(String name, Scriptable start, Object value) { + // register property for PropertyRecorder interface + if (isRecording) { + changedProperties.add(name); + } + if (node == null) { super.put(name, start, value); } else { @@ -966,4 +972,36 @@ public class HopObject extends ScriptableObject implements Wrapper { public String toString() { return (className != null) ? ("[HopObject " + className + "]") : "[HopObject]"; } + + /** + * Tell this PropertyRecorder to start recording changes to properties + */ + public void startRecording() { + changedProperties = new HashSet(); + isRecording = true; + } + + /** + * Tell this PropertyRecorder to stop recording changes to properties + */ + public void stopRecording() { + isRecording = false; + } + + /** + * Returns a set containing the names of properties changed since + * the last time startRecording() was called. + * + * @return a Set containing the names of changed properties + */ + public Set getChangeSet() { + return changedProperties; + } + + /** + * Clear the set of changed properties. + */ + public void clearChangeSet() { + changedProperties = null; + } } diff --git a/src/helma/scripting/rhino/PropertyRecorder.java b/src/helma/scripting/rhino/PropertyRecorder.java new file mode 100644 index 00000000..8cecc206 --- /dev/null +++ b/src/helma/scripting/rhino/PropertyRecorder.java @@ -0,0 +1,51 @@ +/* + * Helma License Notice + * + * The contents of this file are subject to the Helma License + * Version 2.0 (the "License"). You may not use this file except in + * compliance with the License. A copy of the License is available at + * http://adele.helma.org/download/helma/license.txt + * + * Copyright 1998-2003 Helma Software. All Rights Reserved. + * + * $RCSfile$ + * $Author$ + * $Revision$ + * $Date$ + */ + +package helma.scripting.rhino; + +import java.util.Set; + +/** + * An interface that allows us to keep track of changed properties in + * JavaScript objects. This is used when compiling prototypes in order + * to be able to remove properties from prototypes that haven't been + * renewed in the compilation step. + */ +public interface PropertyRecorder { + + /** + * Tell this PropertyRecorder to start recording changes to properties + */ + public void startRecording(); + + /** + * Tell this PropertyRecorder to stop recording changes to properties + */ + public void stopRecording(); + + /** + * Returns a set containing the names of properties changed since + * the last time startRecording() was called. + * + * @return a Set containing the names of changed properties + */ + public Set getChangeSet(); + + /** + * Clear the set of changed properties. + */ + public void clearChangeSet(); +} diff --git a/src/helma/scripting/rhino/RhinoCore.java b/src/helma/scripting/rhino/RhinoCore.java index 7f5b1659..ca075bf6 100644 --- a/src/helma/scripting/rhino/RhinoCore.java +++ b/src/helma/scripting/rhino/RhinoCore.java @@ -792,7 +792,7 @@ public final class RhinoCore { // get the current context Context cx = Context.getCurrentContext(); - Scriptable op = type.tmpObjProto; + Scriptable op = type.objProto; // do the update, evaluating the file // Script script = cx.compileReader(reader, sourceName, firstline, null); @@ -848,9 +848,6 @@ public final class RhinoCore { // the JavaScript prototype for this type ScriptableObject objProto; - // temporary JS prototype used for compilation - ScriptableObject tmpObjProto; - // timestamp of last update. This is -1 so even an empty prototype directory // (with lastUpdate == 0) gets evaluated at least once, which is necessary // to get the prototype chain set. @@ -859,9 +856,6 @@ public final class RhinoCore { // the parent prototype info TypeInfo parentType; - // a set of property values that were defined in last script compliation - Set compiledProperties; - // a set of property keys that were present before first script compilation final Set predefinedProperties; @@ -870,7 +864,6 @@ public final class RhinoCore { public TypeInfo(Prototype proto, ScriptableObject op) { frameworkProto = proto; objProto = op; - compiledProperties = new HashSet(0); // remember properties already defined on this object prototype predefinedProperties = new HashSet(); Object[] keys = op.getAllIds(); @@ -878,92 +871,72 @@ public final class RhinoCore { predefinedProperties.add(keys[i].toString()); } } - + /** - * Set up an empty temporary object prototype to compile this type's - * code against. + * If prototype implements PropertyRecorder tell it to start + * registering property puts. */ public void prepareCompilation() { - if ("global".equals(frameworkProto.getLowerCaseName())) { - tmpObjProto = new GlobalObject(RhinoCore.this, app); - // setting the prototype to global does not seem to be the right - // thing to do. that and not using a GlobalObject instance - // as temporary object proto resulted in bug 390. - tmpObjProto.setPrototype(null); - tmpObjProto.setParentScope(global); - } else { - tmpObjProto = new HopObject(frameworkProto.getName()); - tmpObjProto.setPrototype(objProto); - tmpObjProto.setParentScope(global); + if (objProto instanceof PropertyRecorder) { + ((PropertyRecorder) objProto).startRecording(); } } /** * Compilation has been completed successfully - switch over to code - * from temporary prototype, removing properties that haven't been + * from temporary prototype, removing properties that haven't been * renewed. */ public void commitCompilation() { - // loop through properties defined on the temporary proto and - // copy them over to the actual prototype object. Then, remove - // those properties that haven't been renewed in this compilation - Set newProperties = new HashSet(); - Object[] keys = tmpObjProto.getAllIds(); + // loop through properties defined on the prototype object + // and remove thos properties which haven't been renewed during + // this compilation/evaluation pass. + if (objProto instanceof PropertyRecorder) { - for (int i = 0; i < keys.length; i++) { - if (! (keys[i] instanceof String)) { - System.err.println("JUMP: "+keys[i]); - continue; - } - - String key = (String) keys[i]; - if (predefinedProperties.contains(key)) { - // don't mess with properties we didn't set - continue; - } + PropertyRecorder recorder = (PropertyRecorder) objProto; - // add properties to newProperties set - newProperties.add(key); - // copy them over to the actual prototype object - int attributes = tmpObjProto.getAttributes(key); - Object value = tmpObjProto.get(key, tmpObjProto); - if (value instanceof ScriptableObject) { - // switch parent scope to actual prototype - ScriptableObject obj = (ScriptableObject) value; - if (obj.getParentScope() == tmpObjProto) { - obj.setParentScope(objProto); - } + recorder.stopRecording(); + Set changedProperties = recorder.getChangeSet(); + recorder.clearChangeSet(); + + Object[] keys = objProto.getAllIds(); + + for (int i = 0; i < keys.length; i++) { + if (! (keys[i] instanceof String)) { + continue; + } + + String key = (String) keys[i]; + if (predefinedProperties.contains(key)) { + // don't mess with properties we didn't set + continue; + } + + Object value = objProto.get(key, objProto); + if (value instanceof FunctionObject) { + // this is probably a HopObject Constructor - don't remove! + continue; + } + + if (!changedProperties.contains(key)) { + try { + objProto.setAttributes(key, 0); + objProto.delete(key); + } catch (Exception px) { + System.err.println("Error unsetting property "+key+" on "+ + frameworkProto.getName()); + } + } } - objProto.defineProperty(key, value, attributes); } - // switch property set over to new version and - // get a set of those old properties that weren't renewed - Set oldProperties = compiledProperties; - compiledProperties = newProperties; - oldProperties.removeAll(newProperties); - - for (Iterator it = oldProperties.iterator(); it.hasNext(); ) { - // remove those properties that weren't renewed, meaning - // their definition has gone from the source files - String key = (String) it.next(); - try { - objProto.setAttributes(key, 0); - objProto.delete(key); - } catch (Exception px) { - System.err.println("Error unsetting property "+key+" on "+ - frameworkProto.getName()); - } - - } - // mark this type as updated lastUpdate = frameworkProto.getLastUpdate(); // If this prototype defines a postCompile() function, call it Context cx = Context.getCurrentContext(); try { - Object fObj = ScriptableObject.getProperty(objProto, + Object fObj = ScriptableObject.getProperty(objProto, "onCodeUpdate"); if (fObj instanceof Function) { Object[] args = {frameworkProto.getName()};