Package com.scratchdisk.script.rhino

Source Code of com.scratchdisk.script.rhino.ExtendedJavaClass

/*
* 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 01.01.2005.
*/

package com.scratchdisk.script.rhino;

import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;

import org.mozilla.javascript.Callable;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.MemberBox;
import org.mozilla.javascript.NativeJavaClass;
import org.mozilla.javascript.NativeObject;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;

import com.scratchdisk.script.ArgumentReader;

/**
* @author lehni
*/
public class ExtendedJavaClass extends NativeJavaClass {
  private String className;
  private HashMap<String, Object> properties;
  private Scriptable instanceProto = null;
  // A lookup for the associated ExtendedJavaClass wrappers
  private static IdentityHashMap<Class, ExtendedJavaClass> classes =
      new IdentityHashMap<Class, ExtendedJavaClass>();

  public ExtendedJavaClass(Scriptable scope, Class cls, boolean unsealed) {
    super(scope, cls);
    // Set the function prototype, as basically Java constructors
    // behave like JS constructor functions. Like this, all properties
    // from Function.prototype are inherited.
    setParentScope(scope);
    setPrototype(ScriptableObject.getFunctionPrototype(scope));
    // Determine short className:
    className = cls.getSimpleName();
    properties = unsealed ? new HashMap<String, Object>() : null;
    // put it in the class wrapper table
    classes.put(cls, this);
  }

  public Scriptable construct(Context cx, Scriptable scope, Object[] args) {
    // If the normal constructor failed, try to see if the last
    // argument is a Callable or a NativeObject object.
    // If it is a NativeObject, use it as a hashtable containing
    // fields to be added to the object. If it is a Callable,
    // call it on the object and again use its return value
    // as a hashtable if it is a NativeObject.
    Class classObject = getClassObject();
    int modifiers = classObject.getModifiers();
    NativeObject properties = null;
    Callable initialize = null;
    if (args.length > 0
        && !Modifier.isInterface(modifiers)
        && !Modifier.isAbstract(modifiers)) {
      // Look at the last argument to find out if we need to do something
      // special. Possibilities: a object literal that defines fields to
      // be set, or a function that is executed on the object and of which
      // the result can be fields to be set.
      Object last = args[args.length - 1];
      // Match callables for initialize functions but filter out java constructors
      // which might be arguments to methods...
      if (last instanceof Callable && !(last instanceof NativeJavaClass))
        initialize = (Callable) last;
      else if (last instanceof NativeObject) {
        // Now see if the constructor takes a Map as the last argument.
        // If so, the NativeObject will be converted to it thought
        // RhinoWrapFactory. Otherwise, the NativeObject is used
        // as the properties to be set on the instance after creation.
        MemberBox ctor = findConstructor(cx, args);
        if (ctor != null) {
          Class[] types = ctor.ctor().getParameterTypes();
          Class lastType = types[types.length - 1];
          // Only set the property object if the constructor does
          // not expect an ArgumentReader or a Map object, both
          // of which NativeObject's can be converted to.
          if (!ArgumentReader.class.isAssignableFrom(lastType)
              && !Map.class.isAssignableFrom(lastType)) {
            properties = (NativeObject) last;
            if (ScriptableObject.hasProperty(properties, "unwrap"))
              properties = null;
          }
        } else {
          // There is no constructor that has to be checked, so it
          // can only be a properties list.
          properties = (NativeObject) last;
        }
        if (properties != null) {
          // Support initialize in the passed object literal too.
          Object obj = ScriptableObject.getProperty(properties, "initialize");
          if (obj instanceof Callable)
            initialize = (Callable) obj;
        }
      }
      // Remove the last argument from the list, so the right constructor
      // will be found:
      if (initialize != null || properties != null) {
        Object[] newArgs = new Object[args.length - 1];
        for (int i = 0; i < newArgs.length; i++)
          newArgs[i] = args[i];
        args = newArgs;
      }
    }
    Scriptable obj = super.construct(cx, scope, args);
    // If properties are to be added, do it now. Add the ones from the
    // object literal first, then call initialize and after add the
    // properties returned by initialize.
    if (properties != null)
      setProperties(obj, properties);
    // If an initialize function was passed as the last argument, execute
    // it now. The fields of the result of the function are then injected
    // into the object after, if it is a NativeObject.
    if (initialize != null) {
      Object res = initialize.call(cx, scope, obj, args);
      if (res instanceof NativeObject)
        setProperties(obj, (NativeObject) res);
    }
    return obj;
  }

  private void setProperties(Scriptable obj, NativeObject properties) {
    for (Object id : properties.getIds()) {
      if (id instanceof String && !id.equals("initialize"))
        obj.put((String) id, obj, properties.get((String) id, properties));
    }
  }

  public Class<?> getClassObject() {
    // Why calling super.unwrap() when all it does is returning the internal
    // javaObject? That's how it's done in NativeJavaClass...
    return (Class<?>) javaObject;
  }

  public Object get(String name, Scriptable start) {
    // We are completely redefining get here without relying on
    // NativeJavaClass' implementation, in order to add more JS
    // like behavior.
   
        // When used as a constructor, ScriptRuntime.newObject() asks
        // for our prototype to create an object of the correct type.
        // We don't really care what the object is, since we're returning
        // one constructed out of whole cloth, so we return null.
    boolean isProto = name.equals("prototype");

    if (!isProto) {
      if (members.has(name, true)) {
        return members.get(this, name, javaObject, true);
      }

      // Changing access sequence of members / staticFieldAndMethods
      // to be more logical / java-like. TODO: Is this a Rhino bug?
      if (staticFieldAndMethods != null) {
        Object result = staticFieldAndMethods.get(name);
        if (result != null)
          return result;
      }
    }

    if (properties != null && properties.containsKey(name)) {
      // see whether this object defines the property.
      return properties.get(name);
    } else if (isProto) {
      // getPrototype creates prototype Objects on the fly:
      return getInstancePrototype();
    } else {
      Scriptable proto = getPrototype();
      if (proto != null) {
        Object result = proto.get(name, start);
        if (result != Scriptable.NOT_FOUND)
          return result;
      }
    }

    // Experimental: look for nested classes by appending $name to
    // current class' name.
    Class<?> nestedClass = findNestedClass(getClassObject(), name);
    if (nestedClass != null) {
      ExtendedJavaClass nestedValue = new ExtendedJavaClass(
          ScriptableObject.getTopLevelScope(this),
          nestedClass, properties != null);
      nestedValue.setParentScope(this);
      return nestedValue;
    }

        return Scriptable.NOT_FOUND;
   }

  public void put(String name, Scriptable start, Object value) {
    if (members.has(name, true)) {
      members.put(this, name, javaObject, value, true);
    } else if (name.equals("prototype")) {
      if (value instanceof Scriptable)
        this.setPrototype((Scriptable) value);
    } else if (properties != null) {
      properties.put(name, value);
    }
  }

  public boolean has(String name, Scriptable start) {
    boolean has = members.has(name, true);
    if (!has && properties != null)
      has = properties.get(name) != null;
    return has;
  }

  public void delete(String name) {
    if (properties != null)
      properties.remove(name);
  }

  public Scriptable getInstancePrototype() {
    if (instanceProto == null) {
      instanceProto = new NativeObject();
      // Set the prototype chain correctly for this prototype object,
      // so properties in the prototype of parent classes are found too:
      Class sup = getClassObject().getSuperclass();
      Scriptable parent;
      if (sup != null) {
        ExtendedJavaClass classWrapper = getClassWrapper(
            ScriptableObject.getTopLevelScope(this), sup);
        parent = classWrapper.getInstancePrototype();
      } else {
        // At the end of the chain, there is always the Object prototype.
        parent = ScriptableObject.getObjectPrototype(this);
      }
      instanceProto.setPrototype(parent);
    }
    return instanceProto;
  }

  public String getClassName() {
    return className;
  }

  public String toString() {
    return "[" + className + "]";
  }
 
  protected static ExtendedJavaClass getClassWrapper(Scriptable scope, Class javaClass) {
    ExtendedJavaClass cls = classes.get(javaClass);
    if (cls == null) {
      // Search for the ExtendedJavaClass by splitting the full name into bits
      // separated by '.', and walk up the Packages chain:
      String[] path = javaClass.getName().split("\\.");
      Scriptable global = ScriptableObject.getTopLevelScope(scope);
      // Use ScriptableObject.getProperty so it also looks in the prototypes
      // of shared scopes.
      Object packages = ScriptableObject.getProperty(global, "Packages");
      if (packages != Scriptable.NOT_FOUND) {
        Scriptable current = (Scriptable) packages;
        for (int i = 0; i < path.length; i++)
          current = (Scriptable) current.get(path[i], current);
        // Now obj needs to be an instance of ExtendedJavaClass.
        // Note that we do not need to put it into classes, as the constructor
        // does this for us.
        cls = (ExtendedJavaClass) current;
      }
    }
    return cls;
  }
}
TOP

Related Classes of com.scratchdisk.script.rhino.ExtendedJavaClass

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.