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).
This commit is contained in:
hns 2005-01-14 13:23:12 +00:00
parent da007f9506
commit 5cc104466c
4 changed files with 191 additions and 82 deletions

View file

@ -24,10 +24,7 @@ import helma.util.MimePart;
import helma.util.XmlUtils; import helma.util.XmlUtils;
import org.mozilla.javascript.*; import org.mozilla.javascript.*;
import java.util.Map; import java.util.*;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.net.URLConnection; 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; Application app;
RhinoCore core; RhinoCore core;
// fields to implement PropertyRecorder
private boolean isRecording = false;
private HashSet changedProperties;
/** /**
* Creates a new GlobalObject object. * Creates a new GlobalObject object.
* *
@ -81,6 +82,20 @@ public class GlobalObject extends ImporterTopLevel {
return "GlobalObject"; 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); 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;
}
} }

View file

@ -25,16 +25,13 @@ import org.mozilla.javascript.*;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.util.ArrayList; import java.util.*;
import java.util.Date;
import java.util.Enumeration;
import java.util.Map;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
/** /**
* *
*/ */
public class HopObject extends ScriptableObject implements Wrapper { public class HopObject extends ScriptableObject implements Wrapper, PropertyRecorder {
static Method hopObjCtor; static Method hopObjCtor;
static { static {
@ -53,6 +50,10 @@ public class HopObject extends ScriptableObject implements Wrapper {
INode node; INode node;
RhinoCore core; RhinoCore core;
// fields to implement PropertyRecorder
private boolean isRecording = false;
private HashSet changedProperties;
/** /**
* Creates a new HopObject object. * Creates a new HopObject object.
*/ */
@ -685,6 +686,11 @@ public class HopObject extends ScriptableObject implements Wrapper {
* @param value ... * @param value ...
*/ */
public void put(String name, Scriptable start, Object value) { public void put(String name, Scriptable start, Object value) {
// register property for PropertyRecorder interface
if (isRecording) {
changedProperties.add(name);
}
if (node == null) { if (node == null) {
super.put(name, start, value); super.put(name, start, value);
} else { } else {
@ -966,4 +972,36 @@ public class HopObject extends ScriptableObject implements Wrapper {
public String toString() { public String toString() {
return (className != null) ? ("[HopObject " + className + "]") : "[HopObject]"; 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;
}
} }

View file

@ -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();
}

View file

@ -792,7 +792,7 @@ public final class RhinoCore {
// get the current context // get the current context
Context cx = Context.getCurrentContext(); Context cx = Context.getCurrentContext();
Scriptable op = type.tmpObjProto; Scriptable op = type.objProto;
// do the update, evaluating the file // do the update, evaluating the file
// Script script = cx.compileReader(reader, sourceName, firstline, null); // Script script = cx.compileReader(reader, sourceName, firstline, null);
@ -848,9 +848,6 @@ public final class RhinoCore {
// the JavaScript prototype for this type // the JavaScript prototype for this type
ScriptableObject objProto; ScriptableObject objProto;
// temporary JS prototype used for compilation
ScriptableObject tmpObjProto;
// timestamp of last update. This is -1 so even an empty prototype directory // timestamp of last update. This is -1 so even an empty prototype directory
// (with lastUpdate == 0) gets evaluated at least once, which is necessary // (with lastUpdate == 0) gets evaluated at least once, which is necessary
// to get the prototype chain set. // to get the prototype chain set.
@ -859,9 +856,6 @@ public final class RhinoCore {
// the parent prototype info // the parent prototype info
TypeInfo parentType; 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 // a set of property keys that were present before first script compilation
final Set predefinedProperties; final Set predefinedProperties;
@ -870,7 +864,6 @@ public final class RhinoCore {
public TypeInfo(Prototype proto, ScriptableObject op) { public TypeInfo(Prototype proto, ScriptableObject op) {
frameworkProto = proto; frameworkProto = proto;
objProto = op; objProto = op;
compiledProperties = new HashSet(0);
// remember properties already defined on this object prototype // remember properties already defined on this object prototype
predefinedProperties = new HashSet(); predefinedProperties = new HashSet();
Object[] keys = op.getAllIds(); Object[] keys = op.getAllIds();
@ -878,92 +871,72 @@ public final class RhinoCore {
predefinedProperties.add(keys[i].toString()); predefinedProperties.add(keys[i].toString());
} }
} }
/** /**
* Set up an empty temporary object prototype to compile this type's * If prototype implements PropertyRecorder tell it to start
* code against. * registering property puts.
*/ */
public void prepareCompilation() { public void prepareCompilation() {
if ("global".equals(frameworkProto.getLowerCaseName())) { if (objProto instanceof PropertyRecorder) {
tmpObjProto = new GlobalObject(RhinoCore.this, app); ((PropertyRecorder) objProto).startRecording();
// 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);
} }
} }
/** /**
* Compilation has been completed successfully - switch over to code * 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. * renewed.
*/ */
public void commitCompilation() { public void commitCompilation() {
// loop through properties defined on the temporary proto and // loop through properties defined on the prototype object
// copy them over to the actual prototype object. Then, remove // and remove thos properties which haven't been renewed during
// those properties that haven't been renewed in this compilation // this compilation/evaluation pass.
Set newProperties = new HashSet(); if (objProto instanceof PropertyRecorder) {
Object[] keys = tmpObjProto.getAllIds();
for (int i = 0; i < keys.length; i++) { PropertyRecorder recorder = (PropertyRecorder) objProto;
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;
}
// add properties to newProperties set recorder.stopRecording();
newProperties.add(key); Set changedProperties = recorder.getChangeSet();
// copy them over to the actual prototype object recorder.clearChangeSet();
int attributes = tmpObjProto.getAttributes(key);
Object value = tmpObjProto.get(key, tmpObjProto); Object[] keys = objProto.getAllIds();
if (value instanceof ScriptableObject) {
// switch parent scope to actual prototype for (int i = 0; i < keys.length; i++) {
ScriptableObject obj = (ScriptableObject) value; if (! (keys[i] instanceof String)) {
if (obj.getParentScope() == tmpObjProto) { continue;
obj.setParentScope(objProto); }
}
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 // mark this type as updated
lastUpdate = frameworkProto.getLastUpdate(); lastUpdate = frameworkProto.getLastUpdate();
// If this prototype defines a postCompile() function, call it // If this prototype defines a postCompile() function, call it
Context cx = Context.getCurrentContext(); Context cx = Context.getCurrentContext();
try { try {
Object fObj = ScriptableObject.getProperty(objProto, Object fObj = ScriptableObject.getProperty(objProto,
"onCodeUpdate"); "onCodeUpdate");
if (fObj instanceof Function) { if (fObj instanceof Function) {
Object[] args = {frameworkProto.getName()}; Object[] args = {frameworkProto.getName()};