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:
parent
da007f9506
commit
5cc104466c
4 changed files with 191 additions and 82 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
51
src/helma/scripting/rhino/PropertyRecorder.java
Normal file
51
src/helma/scripting/rhino/PropertyRecorder.java
Normal 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();
|
||||
}
|
|
@ -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()};
|
||||
|
|
Loading…
Add table
Reference in a new issue