Package com.scratchdisk.script.rhino

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

/*
* 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 Apr 10, 2007.
*/

package com.scratchdisk.script.rhino;

import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.util.IdentityHashMap;
import java.util.Map;

import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.NativeArray;
import org.mozilla.javascript.NativeObject;
import org.mozilla.javascript.ScriptRuntime;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.Undefined;
import org.mozilla.javascript.WrapFactory;
import org.mozilla.javascript.Wrapper;

import com.scratchdisk.list.ReadOnlyList;
import com.scratchdisk.script.ArgumentReader;
import com.scratchdisk.script.Callable;
import com.scratchdisk.script.Converter;
import com.scratchdisk.script.StringArgumentReader;
import com.scratchdisk.util.ClassUtils;
import com.scratchdisk.util.WeakIdentityHashMap;

/**
* @author lehni
*/
public class RhinoWrapFactory extends WrapFactory implements Converter {
  private WeakIdentityHashMap<Object, WeakReference<Scriptable>> wrappers =
    new WeakIdentityHashMap<Object, WeakReference<Scriptable>>();

  protected RhinoEngine engine;

  public RhinoWrapFactory() {
    this.setJavaPrimitiveWrap(false);
  }

  /**
   * wrapCustom should wrap all objects that it would like to be cached in
   * the wrappers WeakIdentityHashMap. If it returns null, a temporary
   * ExtendedJavaObject wrapper is created which is not cached.
   * This is used for example to allow the definition of JS wrappers for File,
   * which in itself then explicitly create java.io.File objects for the
   * same file, which would otherwise then already be in wrappers and returned.
   */
  public Scriptable wrapCustom(Context cx, Scriptable scope,
      Object javaObj, Class<?> staticType, boolean newObject) {
    return new ExtendedJavaObject(scope, javaObj, staticType, true);
  }

  public Object wrap(Context cx, Scriptable scope, Object obj,
      Class<?> staticType) {
    if (obj == null || obj == Undefined.instance
        || obj instanceof Scriptable)
      return obj;
    if (obj instanceof RhinoCallable) {
      // Handle the ScriptFunction special case, return the unboxed
      // function value.
      obj = ((RhinoCallable) obj).getCallable();
    }
    // Allays override staticType and set it to the native type of
    // the class. Sometimes the interface used to access an object of
    // a certain class is passed.
    // But why should it be wrapped that way?
    if (staticType == null || !staticType.isPrimitive())
      staticType = obj.getClass();
    Object result = staticType != null && staticType.isArray() ?
        new ExtendedJavaArray(scope, obj, staticType, true) :
        super.wrap(cx, scope, obj, staticType);
    return result;
  }

  public Scriptable wrapNewObject(Context cx, Scriptable scope, Object obj) {
    if (obj instanceof Scriptable)
      return (Scriptable) obj;
    // TODO: Pass as boolean variable instead and change Rhino further
    cx.putThreadLocal("newObject", true);
    try {
      return wrapAsJavaObject(cx, scope, obj, null, true);
    } finally {
      cx.removeThreadLocal("newObject");
    }
  }

  public Scriptable wrapAsJavaObject(Context cx, Scriptable scope,
      Object javaObj, Class<?> staticType, boolean newObject) {
    // Keep track of wrappers so that if a given object needs to be
    // wrapped again, take the wrapper from the pool...
    WeakReference<Scriptable> ref = wrappers.get(javaObj);
    Scriptable obj = ref == null ? null : ref.get();
    if (obj == null) {
      boolean cache = true;
      // Allays override staticType and set it to the native type
      // of the class. Sometimes the interface used to access an
      // object of a certain class is passed. But why should it
      // be wrapped that way?
      staticType = javaObj.getClass();
      if (staticType != null && staticType.isArray())
        obj = new ExtendedJavaArray(scope, javaObj, staticType, true);
      else {
        if (javaObj instanceof ReadOnlyList) {
          obj = new ListWrapper(scope, (ReadOnlyList) javaObj,
              staticType, true);
        } else if (javaObj instanceof Map) {
          obj = new MapWrapper(scope, (Map) javaObj);
        } else {
          obj = wrapCustom(cx, scope, javaObj, staticType, newObject);
          if (obj == null) {
            obj = new ExtendedJavaObject(scope, javaObj, staticType,
                true);
            // See the comment in wrapCustom for an explanation of
            // this:
            cache = false;
          }
        }
      }
      if (cache)
        wrappers.put(javaObj, new WeakReference<Scriptable>(obj));
    }
    return obj;
  }

  private IdentityHashMap<Class, IdentityHashMap<Class, Integer>> conversionCache =
      new IdentityHashMap<Class, IdentityHashMap<Class, Integer>>();

  /**
   * getConversionWeight is defined here to only calculate the weight per
   * from-to class pair once, after that it is cached in the conversionTable
   * and retrieved from there. calculateConversionWeight is used instead for
   * the calculations.
   */
  public int getConversionWeight(Object from, Object unwrapped, Class<?> to,
          int defaultWeight) {
    IdentityHashMap<Class, Integer> fromCache = null;
    if (unwrapped != null) {
      Class fromClass = unwrapped.getClass();
      fromCache =
          conversionCache.get(fromClass);
      if (fromCache == null) {
        fromCache = new IdentityHashMap<Class, Integer>();
        conversionCache.put(fromClass, fromCache);
      }
      Integer res = fromCache.get(to);
      if (res != null)
        return res;
    }
    int weight = calculateConversionWeight(from, unwrapped, to,
        defaultWeight);
    if (fromCache != null) {
      fromCache.put(to, weight);
    }
    return weight;
  }

  /**
   * getConversionWeight above is defined to call calculateConversionWeight
   * and cache the results. Do not override getConversionWeight in any
   * subclasses, override calculateConversionWeight instead.
   */
  public int calculateConversionWeight(Object from, Object unwrapped,
          Class<?> to, int defaultWeight) {
    // See if object "from" can be converted to an instance of class "to"
    // by the use of a map constructor or the setting of all the fields
    // of a NativeObject on the instance after its creation,
    // all added features of JS in Scriptographer:
    boolean isString = false;
    // Let through string as well, for ArgumentReader
    if (from instanceof Scriptable || (isString = from instanceof String)) {
      // The preferred conversion is from a native object / array to
      // a class that supports an ArgumentReader constructor.
      // Everything else is less preferred (even conversion using
      // the same constructor and another Scriptable object, e.g.
      // a wrapped Java object).
      boolean isNativeObject = from instanceof NativeObject;
      if (isNativeObject || from instanceof NativeArray || isString) {
        if (ArgumentReader.class.isAssignableFrom(to))
          return CONVERSION_TRIVIAL + 1;
        else if (ArgumentReader.canConvert(to))
          return CONVERSION_TRIVIAL + 2;
      }
      if (isNativeObject) {
        if (Map.class.isAssignableFrom(to)) {
          // If there are two version of a method, e.g. one with Map
          // and the other with EnumMap prefer the more general one:
          return Map.class.equals(to)
              ? CONVERSION_TRIVIAL + 1
              : CONVERSION_TRIVIAL + 2;
        }
        // Try and see if unwrapping NativeObjects through JS unwrap
        // method brings us to the right type.
        unwrapped = unwrap(from);
        // TODO: Should this be run through calculateConversionWeight
        // again?
        // TODO: The result of this should not be cached under the
        // wrapper type as it will permanently link NativeObject to the
        // result. This should be achieved by never caching results when
        // from is a NativeObject.
        if (unwrapped != from && to.isInstance(unwrapped))
          return CONVERSION_TRIVIAL;
      } else if (!isString) {
        // String and ArgumentReader we tried above already
        if (getZeroArgumentConstructor(to) != null
            || ArgumentReader.canConvert(to)) {
          // Now if there are more options here to convert from, e.g.
          // Size and Point prefer the one that has the same simple
          // name, to encourage conversion between ADM and AI Size,
          // Rectangle, Point objects!
          return unwrapped.getClass().getSimpleName().equals(
              to.getSimpleName())
              ? CONVERSION_TRIVIAL + 1
              : CONVERSION_TRIVIAL + 2;
        }
      }
    }
    return defaultWeight;
  }

  protected ArgumentReader getArgumentReader(Object object) {
    if (object instanceof NativeArray)
      return new ArrayArgumentReader(this, (NativeArray) object);
    else if (object instanceof Scriptable)
      return new MapArgumentReader(this, (Scriptable) object);
    else if (object instanceof String)
      return new StringArgumentReader(this, (String) object);
    return null;
  }

  public void setProperties(Object object, ArgumentReader reader) {
    // Similar to ExtendedJavaClass#setProperties, but we need to wrap the
    // result in a Scriptable object so we can rely on Rhino to set all
    // properties on it. It will automatically find the right setters for us
    // and use all value conversion mechanisms available.
    Scriptable scriptable = wrapNewObject(
        Context.getCurrentContext(),
        engine.getScope(), object);
    for (Object id : reader.keys())
      scriptable.put((String) id, scriptable, reader.readObject(
          id.toString()));
  }

  public Object coerceType(Class<?> type, Object value, Object unwrapped) {
    // Coerce native objects to maps when needed
    if (value instanceof Function) {
      if (type == Callable.class)
        return new RhinoCallable(engine, (Function) value);
    } else if (value instanceof Scriptable || value instanceof String) {
      // Let through string as well, for ArgumentReader
      // TODO: Add support for constructor detection that receives the
      // passed value, or can convert to it.
      if (Map.class.isAssignableFrom(type))
        return toMap((Scriptable) value);
      // Try and see if unwrapping NativeObjects through JS unwrap
      // method brings us to the right type.
      boolean isNativeObject = value instanceof NativeObject;
      if (isNativeObject) {
        unwrapped = unwrap(value);
        if (unwrapped != value && type.isInstance(unwrapped))
          return unwrapped;
      }
      ArgumentReader reader = null;
      if (ArgumentReader.canConvert(type)
          && (reader = getArgumentReader(value)) != null) {
        return ArgumentReader.convert(reader, unwrapped, type, this);
      } else if (isNativeObject) {
        Constructor ctor = getZeroArgumentConstructor(type);
        if (ctor != null) {
          try {
            Object result = ctor.newInstance();
            // As a conversion, use setProperties through argument
            // reader to set all values on the newly
            // created object.
            setProperties(result, getArgumentReader(value));
            return result;
          } catch (Exception e) {
                  throw Context.throwAsScriptRuntimeEx(e);
          }
        }
      }
    } else if (value == Undefined.instance) {
      // Convert undefined to false if destination is boolean
      if (type == Boolean.TYPE)
        return Boolean.FALSE;
    } else if (value instanceof Boolean) {
      // Convert false to null / undefined for non primitive destination
      // classes.
      if (!((Boolean) value).booleanValue() && !type.isPrimitive())
        return Undefined.instance;
    }
    return null;
  }

  @SuppressWarnings("unchecked")
  public <T> T convert(Object from, Class<T> to) {
    return (T) Context.jsToJava(from, to);
  }

  public Object unwrap(Object obj) {
    if (obj instanceof Wrapper) {
      return ((Wrapper) obj).unwrap();
    } else if (obj instanceof NativeObject) {
      // Allow JS objects to define a unwrap method:
      NativeObject object = (NativeObject) obj;
      Object unwrap = ScriptableObject.getProperty(object, "unwrap");
      if (unwrap != Scriptable.NOT_FOUND
          && unwrap instanceof org.mozilla.javascript.Callable) {
        obj = ((org.mozilla.javascript.Callable) unwrap).call(
            Context.getCurrentContext(),
            engine.topLevel, object, ScriptRuntime.emptyArgs);
        if (obj != object)
          return unwrap(obj);
      }
    }
    return obj;
  }

  /**
   * Takes a scriptable and either wraps it in a MapAdapter or unwraps a map
   * within it if it is a MapWrapper. This avoids multiple wrapping of
   * MapWrappers and MapAdapters
   *
   * @param scriptable
   * @return a map object representing the passed scriptable.
   */
  private Map toMap(Scriptable scriptable) {
    if (scriptable instanceof MapWrapper)
      return (Map) ((MapWrapper) scriptable).unwrap();
    return new MapAdapter(scriptable);
  }

  /**
   * Determines whether the class has a zero argument constructor or not.
   * A cache is used to speed up lookup.
   *
   * @param cls
   * @return true if the class has a zero argument constructor, false
   *         otherwise.
   */
  private static Constructor getZeroArgumentConstructor(Class<?> cls) {
    return ClassUtils.getConstructor(cls, new Class[] { },
        zeroArgumentConstructors);
  }

  private static IdentityHashMap<Class, Constructor> zeroArgumentConstructors =
      new IdentityHashMap<Class, Constructor>();
}
TOP

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

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.