/** * Helios, OpenSource Monitoring * Brought to you by the Helios Development Group * * Copyright 2007, Helios Development Group and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. * */ package org.helios.apmrouter.monitor.script.rhino; import javassist.*; import java.io.File; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.util.List; import java.util.Map; /** * <p>Title: NativeFactory</p> * <p>Description: The rhino class locations in Java bounce around so this class locates where they are and spins up a runtime compiled proxy to wrap the functions we need.</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.monitor.script.rhino.NativeFactory</code></p> */ public class NativeFactory { // import sun.org.mozilla.javascript.internal.NativeArray; // import sun.org.mozilla.javascript.internal.NativeObject; // import sun.org.mozilla.javascript.internal.ScriptableObject; /** The mozilla NativeArray class */ public static final Class<?> ARRAY_CLASS; /** The mozilla NativeObject class */ public static final Class<?> OBJECT_CLASS; /** The mozilla ScriptableObject class */ public static final Class<?> SCRIPTABLE_CLASS; /** The mozilla NativeArray ctor */ public static final Constructor<?> ARRAY_CTOR; /** The mozilla NativeObject ctor */ public static final Constructor<?> OBJECT_CTOR; /** The mozilla Native package name */ public static final String MOZ_PACKAGE; /** The javassist classloader for the mozilla classes */ public static final LoaderClassPath MOZ_CLASSLOADER; /** The tmp dir to write classes to */ public static final File TMP = new File(System.getProperty("java.io.tmpdir") + File.separator + "mozclasses"); /** The proxy object factory instance */ protected static final INativeFactory PROXY_FACTORY; static { Class<?> arr = null; Class<?> obj = null; Class<?> scr = null; Constructor<?> arrCtor = null; Constructor<?> objCtor = null; String packageName = "sun.org.mozilla.javascript.internal."; try { arr = Class.forName(packageName + "NativeArray"); obj = Class.forName(packageName + "NativeObject"); scr = Class.forName(packageName + "ScriptableObject"); arrCtor = arr.getConstructor(Object[].class); objCtor = obj.getConstructor(); } catch (Exception ex) { ex.printStackTrace(System.err); packageName = "sun.org.mozilla.javascript."; try { arr = Class.forName(packageName + "NativeArray"); obj = Class.forName(packageName + "NativeObject"); scr = Class.forName(packageName + "ScriptableObject"); arrCtor = arr.getConstructor(); objCtor = obj.getConstructor(); } catch (Exception ex2) { ex.printStackTrace(System.err); throw new RuntimeException("Failed to locate the rhino classes", ex2); } } MOZ_PACKAGE = packageName + ".*"; ARRAY_CLASS = arr; OBJECT_CLASS = obj; SCRIPTABLE_CLASS = scr; ARRAY_CTOR = arrCtor; OBJECT_CTOR = objCtor; MOZ_CLASSLOADER = new LoaderClassPath(OBJECT_CLASS.getClassLoader()); if(!TMP.exists()) { TMP.mkdirs(); } PROXY_FACTORY = buildProxies(); } private static INativeFactory buildProxies() { try { String packageName = NativeFactory.class.getPackage().getName(); ClassPool cp = new ClassPool(); cp.appendClassPath(MOZ_CLASSLOADER); cp.appendClassPath(new ClassClassPath(INativeObject.class)); cp.appendClassPath(new ClassClassPath(INativeArray.class)); cp.appendClassPath(new ClassClassPath(IScriptableObject.class)); cp.appendClassPath(new ClassClassPath(BaseNativeProxy.class)); cp.importPackage(MOZ_PACKAGE); return buildProxyFactory(packageName, cp, buildNativeObject(packageName, cp), buildNativeArray(packageName, cp) ); /* public Object getProperty(String name); public void putProperty(String name, Object obj); public IScriptableObject getUnderlying(); */ } catch (Exception ex) { ex.printStackTrace(System.err); throw new RuntimeException("Failed to build rhino proxies", ex); } } /** * <p>Title: BaseNativeProxy</p> * <p>Description: Contains static helper methods for the actual proxy classes</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.monitor.script.rhino.NativeFactory.BaseNativeProxy</code></p> */ public static class BaseNativeProxy { /** * Scans the passed array and converts any entries that are native objects into proxies. * @param arr The array to convert * @return the new array */ public static Object[] convertNatives(Object[] arr) { Object[] newArr = new Object[arr.length]; for(int i = 0; i < arr.length; i++) { newArr[i] = convertNative(arr[i]); } return newArr; } /** * Inspects the passed object and if it is a mozilla native object, returns the proxy wrapper, * otherwise returns the passed object * @param obj The object to inspect * @return a wrapper proxy or the passed object */ public static Object convertNative(Object obj) { if(obj==null) return null; if(OBJECT_CLASS.isInstance(obj)) { return newNativeObject(obj); } else if(ARRAY_CLASS.isInstance(obj)) { return newNativeArray(obj); } else { return obj; } } /** * Tests the passed object to see if it a proxy, and if so, returns the underlying * @param obj The object to test * @return The underlying native or the passed value */ public static Object recoverNative(Object obj) { if(obj==null) return null; if(obj instanceof INativeObject) { return ((INativeObject)obj).getUnderlying(); } else if(obj instanceof INativeArray) { return ((INativeArray)obj).getUnderlying(); } else if(obj.getClass().isArray()) { int len = Array.getLength(obj); Object[] newArray = new Object[len]; for(int i = 0; i < len; i++) { newArray[i] = recoverNative(Array.get(obj, i)); } return newArray; } return obj; } public static Object[] recoverNativeArr(Object obj) { return (Object[])recoverNative(obj); } /** * Renders the contents of a map in a string. * @param map The map to render * @return the rendered string */ public static String mapToString(Map<?,?> map) { StringBuilder b = new StringBuilder(map.getClass().getSimpleName()); for(Map.Entry<?, ?> m: map.entrySet()) { Object value = m.getValue(); if(value instanceof INativeObject) { b.append("\n\t").append(m.getKey()).append(":").append(mapToString((Map)((INativeObject)value).getUnderlying())); } else { b.append("\n\t").append(m.getKey()).append(":").append(m.getValue()); } } return b.toString(); } /** * Renders the contents of a list in a string. * @param list The list to render * @return the rendered string */ public static String listToString(List<?> list) { StringBuilder b = new StringBuilder(list.getClass().getSimpleName()); for(Object value: list) { if(value instanceof INativeArray) { b.append("\n\t").append(listToString((List)((INativeArray)value).getUnderlying())); } else { b.append("\n\t").append(value); } } return b.toString(); } } /** * Builds the native NativeObject proxy class * @param packageName The package name * @param cp The class pool * @return the native NativeObject proxy class * @throws Exception thrown on any errors building the class */ protected static Class<?> buildNativeObject(String packageName, ClassPool cp) throws Exception { CtClass clazz = cp.makeClass(packageName + "." + "NativeObject", cp.get(BaseNativeProxy.class.getName())); CtField internalField = new CtField(cp.get(OBJECT_CLASS.getName()), "internal", clazz); clazz.addField(internalField); CtConstructor zeroParamCtor = new CtConstructor(new CtClass[]{}, clazz); zeroParamCtor.setBody("internal = new " + OBJECT_CLASS.getName() + "();"); clazz.addConstructor(zeroParamCtor); CtConstructor internalParamCtor = new CtConstructor(new CtClass[]{cp.get(IScriptableObject.class.getName())}, clazz); internalParamCtor.setBody("internal = (" + OBJECT_CLASS.getName() + ")$1.getUnderlying();"); clazz.addConstructor(internalParamCtor); CtConstructor internalParamCtor2 = new CtConstructor(new CtClass[]{cp.get(Object.class.getName())}, clazz); internalParamCtor2.setBody("internal = (" + OBJECT_CLASS.getName() + ")$1;"); clazz.addConstructor(internalParamCtor2); CtMethod gp = new CtMethod(cp.get(Object.class.getName()), "getProperty", new CtClass[]{cp.get(String.class.getName())}, clazz); gp.setBody("return convertNative(" + SCRIPTABLE_CLASS.getName() + ".getProperty(internal,$1));"); clazz.addMethod(gp); CtMethod pp = new CtMethod(CtClass.voidType, "putProperty", new CtClass[]{cp.get(String.class.getName()), cp.get(Object.class.getName())}, clazz); pp.setBody("return " + SCRIPTABLE_CLASS.getName() + ".putProperty(internal, $1, recoverNative($2));"); clazz.addMethod(pp); CtMethod gi = new CtMethod(cp.get(Object.class.getName()), "getUnderlying", new CtClass[]{}, clazz); gi.setBody("return internal;"); clazz.addMethod(gi); CtMethod gi2 = new CtMethod(cp.get(OBJECT_CLASS.getName()), "getNativeUnderlying", new CtClass[]{}, clazz); gi2.setBody("return internal;"); clazz.addMethod(gi2); CtMethod ts = new CtMethod(cp.get(String.class.getName()), "toString", new CtClass[]{}, clazz); ts.setBody("return internal.toString();"); clazz.addMethod(ts); CtMethod hp = new CtMethod(CtClass.booleanType, "hasProperty", new CtClass[]{cp.get(String.class.getName())}, clazz); hp.setBody("return " + SCRIPTABLE_CLASS.getName() + ".hasProperty(internal, $1);"); clazz.addMethod(hp); CtMethod hp2 = new CtMethod(CtClass.booleanType, "hasProperty", new CtClass[]{CtClass.intType}, clazz); hp2.setBody("return " + SCRIPTABLE_CLASS.getName() + ".hasProperty(internal, $1);"); clazz.addMethod(hp2); CtMethod gai = new CtMethod(cp.get(Object[].class.getName()), "getAllIds", new CtClass[]{}, clazz); gai.setBody("return convertNatives(internal.getAllIds());"); clazz.addMethod(gai); clazz.addInterface(cp.get(INativeObject.class.getName())); if(TMP.exists() && TMP.isDirectory()) { clazz.writeFile(TMP.getAbsolutePath()); } return clazz.toClass(); } /** * Builds the native NativeArray proxy class * @param packageName The package name * @param cp The class pool * @return the native NativeArray proxy class * @throws Exception thrown on any errors building the class */ protected static Class<?> buildNativeArray(String packageName, ClassPool cp) throws Exception { CtClass clazz = cp.makeClass(packageName + "." + "NativeArray", cp.get(BaseNativeProxy.class.getName())); CtField internalField = new CtField(cp.get(ARRAY_CLASS.getName()), "internal", clazz); clazz.addField(internalField); CtConstructor arrayParamCtor = new CtConstructor(new CtClass[]{cp.get(Object[].class.getName())}, clazz); arrayParamCtor.setBody("internal = new " + ARRAY_CLASS.getName() + "(recoverNativeArr($1));"); clazz.addConstructor(arrayParamCtor); CtConstructor internalParamCtor = new CtConstructor(new CtClass[]{cp.get(IScriptableObject.class.getName())}, clazz); internalParamCtor.setBody("internal = (" + ARRAY_CLASS.getName() + ")$1.getUnderlying();"); clazz.addConstructor(internalParamCtor); CtConstructor internalParamCtor2 = new CtConstructor(new CtClass[]{cp.get(Object.class.getName())}, clazz); internalParamCtor2.setBody("internal = (" + ARRAY_CLASS.getName() + ")$1;"); clazz.addConstructor(internalParamCtor2); CtMethod gi = new CtMethod(cp.get(Object.class.getName()), "getUnderlying", new CtClass[]{}, clazz); gi.setBody("return internal;"); clazz.addMethod(gi); CtMethod gi2 = new CtMethod(cp.get(ARRAY_CLASS.getName()), "getNativeUnderlying", new CtClass[]{}, clazz); gi2.setBody("return internal;"); clazz.addMethod(gi2); CtMethod ap = new CtMethod(cp.get(Object.class.getName()), "get", new CtClass[]{CtClass.intType}, clazz); ap.setBody("return convertNative(" + SCRIPTABLE_CLASS.getName() + ".getProperty(internal,$1));"); clazz.addMethod(ap); CtMethod sp = new CtMethod(CtClass.intType, "size", new CtClass[]{}, clazz); sp.setBody("return (int)internal.getLength();"); clazz.addMethod(sp); CtMethod ts = new CtMethod(cp.get(String.class.getName()), "toString", new CtClass[]{}, clazz); ts.setBody("return internal.toString();"); clazz.addMethod(ts); CtMethod hp = new CtMethod(CtClass.booleanType, "hasProperty", new CtClass[]{cp.get(String.class.getName())}, clazz); hp.setBody("return " + SCRIPTABLE_CLASS.getName() + ".hasProperty(internal, $1);"); clazz.addMethod(hp); CtMethod hp2 = new CtMethod(CtClass.booleanType, "hasProperty", new CtClass[]{CtClass.intType}, clazz); hp2.setBody("return " + SCRIPTABLE_CLASS.getName() + ".hasProperty(internal, $1);"); clazz.addMethod(hp2); CtMethod gai = new CtMethod(cp.get(Object[].class.getName()), "getAllIds", new CtClass[]{}, clazz); gai.setBody("return convertNatives(internal.getAllIds());"); clazz.addMethod(gai); clazz.addInterface(cp.get(INativeArray.class.getName())); if(TMP.exists() && TMP.isDirectory()) { clazz.debugWriteFile(TMP.getAbsolutePath()); } return clazz.toClass(); } /** * Builds the proxy factory class * @param packageName The package name * @param cp The class pool * @param noClass The native object proxy class * @param naClass The native array proxy class * @return the proxy factory * @throws Exception thrown on any error */ protected static INativeFactory buildProxyFactory(String packageName, ClassPool cp, Class<?> noClass, Class<?> naClass) throws Exception { cp.appendClassPath(new ClassClassPath(noClass)); cp.appendClassPath(new ClassClassPath(naClass)); cp.importPackage(new StringBuilder(packageName).deleteCharAt(packageName.length()-1).toString()); CtClass clazz = cp.makeClass(packageName + "." + "ProxyFactory", cp.get(BaseNativeProxy.class.getName())); CtMethod f = new CtMethod(cp.get(INativeObject.class.getName()), "newNativeObject", new CtClass[]{}, clazz); f.setBody("return new " + packageName + ".NativeObject();"); clazz.addMethod(f); f = new CtMethod(cp.get(INativeObject.class.getName()), "newNativeObject", new CtClass[]{cp.get(IScriptableObject.class.getName())}, clazz); f.setBody("return new " + packageName + ".NativeObject($1);"); clazz.addMethod(f); f = new CtMethod(cp.get(INativeObject.class.getName()), "newNativeObject", new CtClass[]{cp.get(Object.class.getName())}, clazz); f.setBody("return new " + packageName + ".NativeObject($1);"); clazz.addMethod(f); f = new CtMethod(cp.get(INativeArray.class.getName()), "newNativeArray", new CtClass[]{}, clazz); f.setBody("return new " + packageName + ".NativeArray(new Object[0]);"); clazz.addMethod(f); f = new CtMethod(cp.get(INativeArray.class.getName()), "newNativeArray", new CtClass[]{cp.get(IScriptableObject.class.getName())}, clazz); f.setBody("return new " + packageName + ".NativeArray($1);"); clazz.addMethod(f); f = new CtMethod(cp.get(INativeArray.class.getName()), "newNativeArray", new CtClass[]{cp.get(Object.class.getName())}, clazz); f.setBody("return new " + packageName + ".NativeArray(recoverNative($1));"); clazz.addMethod(f); f = new CtMethod(cp.get(INativeArray.class.getName()), "newNativeArray", new CtClass[]{cp.get(Object[].class.getName())}, clazz); f.setBody("return new " + packageName + ".NativeArray($1);"); clazz.addMethod(f); clazz.addInterface(cp.get(INativeFactory.class.getName())); if(TMP.exists() && TMP.isDirectory()) { clazz.writeFile(TMP.getAbsolutePath()); } return (INativeFactory) clazz.toClass().newInstance(); } // public interface INativeFactory { // public INativeObject newNativeObject(); // public INativeObject newNativeObject(Object internal); // public INativeArray newNativeArray(); // public INativeArray newNativeArray(Object internal); // public INativeArray newNativeArray(Object[] elements); // // } public static void main(String[] args) { log("Moz Classes Test"); INativeObject no = newNativeObject(); log("Obj:" + no.getClass().getName() + " Internal:" + no.getUnderlying().getClass().getName()); no.putProperty("foo", "foo"); no.putProperty("three", 3); INativeObject no2 = newNativeObject(); no2.putProperty("foobar", "snafu"); no.putProperty("me", no2); log("Foo:" + no.getProperty("foo")); log("Three:" + no.getProperty("three")); INativeArray na = newNativeArray(); log("Arr:" + na.getClass().getName() + " Internal:" + na.getUnderlying().getClass().getName()); na = newNativeArray(new Object[]{"one", "two", "three", no}); for(int i = 0; i < na.size(); i++) { log(na.get(i)); } log("Arr toStr:" + na.toString()); log("======================================="); Object foo = na.getUnderlying(); newNativeArray(foo); } public static void log(Object msg) { System.out.println(msg); } public static INativeObject newNativeObject() { return PROXY_FACTORY.newNativeObject(); } public static INativeObject newNativeObject(IScriptableObject internal) { return PROXY_FACTORY.newNativeObject(internal); } public static INativeObject newNativeObject(Object internal) { return PROXY_FACTORY.newNativeObject(internal); } public static INativeArray newNativeArray() { return PROXY_FACTORY.newNativeArray(); } public static INativeArray newNativeArray(IScriptableObject internal) { return PROXY_FACTORY.newNativeArray(internal); } public static INativeArray newNativeArray(Object internal) { return PROXY_FACTORY.newNativeArray(internal); } /** * Determines if the passed object is an instance of the rhino NativeArray class * @param obj The object to test * @return true if the object is an instance of the rhino NativeArray class, false otherwise */ public static boolean isNativeArray(Object obj) { if(obj==null) return false; return ARRAY_CLASS.isInstance(obj); } public static INativeArray newNativeArray(Object[] elements) { return PROXY_FACTORY.newNativeArray(elements); } public static IScriptableObject newScriptable(Object obj) { if(obj==null) throw new IllegalArgumentException("The passed object was null", new Throwable()); if(OBJECT_CLASS.isInstance(obj)) { return PROXY_FACTORY.newNativeObject(obj); } else if(ARRAY_CLASS.isInstance(obj)) { return PROXY_FACTORY.newNativeArray(obj); } else { throw new IllegalArgumentException("The passed object of type [" + obj.getClass().getName() + "] was not a rhino NativeObject or Native Array", new Throwable()); } } public static IScriptableObject convertToNative(Object obj) { if(obj==null) throw new IllegalArgumentException("The passed object was null", new Throwable()); if(OBJECT_CLASS.isInstance(obj)) { return newNativeObject(obj); } else if(ARRAY_CLASS.isInstance(obj)) { return newNativeArray(obj); } else if(obj instanceof IScriptableObject) { return (IScriptableObject)obj; } else { throw new IllegalArgumentException("The passed object of type [" + obj.getClass().getName() + "] was not a rhino NativeObject or Native Array", new Throwable()); } } }