From 5cc104466c3e24baf87fd11f47391d2e6e8ed5ea Mon Sep 17 00:00:00 2001 From: hns Date: Fri, 14 Jan 2005 13:23:12 +0000 Subject: [PATCH] Introduce PropertyRecorder interface that allows us to keep track of changed properties in prototypes during code compilation. This way we're able to remove prototype properties that haven't been renewed in the last compilation (i.e. have been removed from the code). --- src/helma/scripting/rhino/GlobalObject.java | 57 ++++++++- src/helma/scripting/rhino/HopObject.java | 48 ++++++- .../scripting/rhino/PropertyRecorder.java | 51 ++++++++ src/helma/scripting/rhino/RhinoCore.java | 117 +++++++----------- 4 files changed, 191 insertions(+), 82 deletions(-) create mode 100644 src/helma/scripting/rhino/PropertyRecorder.java 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()};