/* * Scriptographer * * This file is part of Scriptographer, a Scripting Plugin for Adobe Illustrator * http://scriptographer.org/ * * Copyright (c) 2002-2010, Juerg Lehni * http://scratchdisk.com/ * * All rights reserved. See LICENSE file for details. * * File created on 02.01.2005. */ package com.scratchdisk.script.rhino; import java.util.HashMap; import org.mozilla.javascript.EcmaError; import org.mozilla.javascript.EvaluatorException; import org.mozilla.javascript.NativeJavaObject; import org.mozilla.javascript.Scriptable; import org.mozilla.javascript.ScriptableObject; import com.scratchdisk.script.ChangeReceiver; import com.scratchdisk.script.ChangeEmitter; /** * @author lehni */ public class ExtendedJavaObject extends NativeJavaObject { HashMap<String, Object> properties; ExtendedJavaClass classWrapper = null; /** * @param scope * @param javaObject * @param staticType */ public ExtendedJavaObject(Scriptable scope, Object javaObject, Class staticType, boolean unsealed) { super(scope, javaObject, staticType); properties = unsealed ? new HashMap<String, Object>() : null; classWrapper = ExtendedJavaClass.getClassWrapper(scope, staticType); } public Scriptable getPrototype() { Scriptable prototype = super.getPrototype(); return prototype == null ? classWrapper.getInstancePrototype() : prototype; } protected ExtendedJavaObject changeReceiver = null; protected String changeProperty = null; protected int currentChangeVersion = -1; public static int changeVersion = -1; protected void fetchChangeReceiver() { // If there is a change receiver, fetch the new value of this object // from it before calling anything on it. We are doing this by // replacing the underlying native object each time. if (currentChangeVersion != changeVersion) { Object result = changeReceiver.get(changeProperty, this); if (result != this && result instanceof ExtendedJavaObject) { ExtendedJavaObject update = (ExtendedJavaObject) result; javaObject = update.javaObject; // Help the garbage collector update.javaObject = null; } currentChangeVersion = changeVersion; } } protected void updateChangeReceiver() { // If there is a modification listener, update this field in it // right away now. changeReceiver.put(changeProperty, changeReceiver, this); } protected void handleChangeEmitter(Object object, String property) { // Now see if the result is a change emitter for this listener. // If so, create the links between the two. if (object instanceof ExtendedJavaObject) { ExtendedJavaObject other = (ExtendedJavaObject) object; if (other.javaObject instanceof ChangeEmitter) { other.changeReceiver = this; other.changeProperty = property; } } } public Object get(String name, Scriptable start) { // See whether this object defines the property. // Properties need to come first, as they might override something // defined in the underlying Java object if (properties != null && properties.containsKey(name)) return properties.get(name); if (changeReceiver != null) fetchChangeReceiver(); Scriptable prototype = getPrototype(); Object result = prototype.get(name, this); if (result != Scriptable.NOT_FOUND) return result; result = members.get(this, name, javaObject, false); if (result != Scriptable.NOT_FOUND) { if (javaObject instanceof ChangeReceiver) handleChangeEmitter(result, name); return result; } if (name.equals("prototype")) return prototype; return Scriptable.NOT_FOUND; } public void put(String name, Scriptable start, Object value) { EvaluatorException error = null; if (members.has(name, false)) { try { // Try setting the value on member first members.put(this, name, javaObject, value, false); if (changeReceiver != null) updateChangeReceiver(); return; // done } catch (EvaluatorException e) { // Rethrow errors that have another cause (a real Java exception from the // called getter / setter) or that are about conversion problems. if (e.getCause() != null || e.getMessage().indexOf("Cannot convert") != -1) throw e; error = e; } } // Still here? An exception has happened. Let's try other things if (name.equals("prototype")) { if (value instanceof Scriptable) setPrototype((Scriptable) value); } else if (properties != null) { // Ignore EvaluatorException exceptions that might have happened in // members.put above. These would happen if the user tries to // override a Java method or field. We allow this on the level of // the wrapper though, if the wrapper was created unsealed (meaning // properties exist). properties.put(name, value); } else if (error != null) { // If nothing of the above worked, throw the error again. throw error; } } public boolean has(String name, Scriptable start) { return members.has(name, false) || properties != null && properties.containsKey(name); } public void delete(String name) { if (properties != null) properties.remove(name); } public Object[] getIds() { // Concatenate the super classes ids array with the keySet from // properties HashMap: Object[] javaIds = super.getIds(); if (properties != null) { int numProps = properties.size(); if (numProps == 0) return javaIds; Object[] ids = new Object[javaIds.length + numProps]; properties.keySet().toArray(ids); System.arraycopy(javaIds, 0, ids, numProps, javaIds.length); return ids; } return javaIds; } public Object getDefaultValue(Class<?> hint) { // Try the ScriptableObject version first that tries to call toString / valueOf, // and fallback on the Java toString only if that does not work out. try { return ScriptableObject.getDefaultValue(this, hint); } catch (EcmaError e) { return super.getDefaultValue(hint); } } }