/* * Copyright 2008-2009 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code 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 General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package org.visage.runtime.liveconnect; import java.util.*; import com.sun.java.browser.plugin2.liveconnect.v1.*; import visage.reflect.*; public class VisageClassDelegate extends VisageTypeDelegate { public VisageClassDelegate(VisageClassType clazz, Bridge bridge) { this.clazz = clazz; this.bridge = bridge; } public boolean invoke(String methodName, Object receiver, Object[] arguments, boolean isStatic, boolean objectIsApplet, Result[] result) throws Exception { if (functionMap == null) collectFunctions(); FunctionBundle bundle = functionMap.get(methodName); if (bundle == null) { // Try again with the lower-case / case-insensitive version bundle = lowerCaseFunctionMap.get(methodName.toLowerCase()); } if (bundle == null) { throw new NoSuchMethodException(methodName + " in class: " + clazz.getName()); } result[0] = bundle.invoke(receiver, arguments); return true; } public boolean getField(String fieldName, Object receiver, boolean isStatic, boolean objectIsApplet, Result[] result) throws Exception { VisageValue val = getField0(fieldName, receiver, isStatic, objectIsApplet); if (val != null) { // FIXME: figure out skipUnboxing flag result[0] = new Result(unbox(val), false); } return true; } private VisageValue getField0(String fieldName, Object receiver, boolean isStatic, boolean objectIsApplet) throws Exception { if (varMap == null) { collectVariables(); } VisageVarMember var = varMap.get(fieldName); if (var == null) { // Try again with the lower-case / case-insensitive version var = lowerCaseVarMap.get(fieldName); } if (var == null) { throw new NoSuchFieldException(fieldName); } return var.getValue((VisageObjectValue) receiver); } public boolean setField(String fieldName, Object receiver, Object value, boolean isStatic, boolean objectIsApplet) throws Exception { setField0(fieldName, (VisageObjectValue) receiver, value, isStatic, objectIsApplet); return true; } private void setField0(String fieldName, VisageObjectValue receiver, Object value, boolean isStatic, boolean objectIsApplet) throws Exception { if (varMap == null) { collectVariables(); } VisageVarMember var = varMap.get(fieldName); if (var == null) { // Try again with the lower-case / case-insensitive version var = lowerCaseVarMap.get(fieldName); } if (var == null) { throw new NoSuchFieldException(fieldName); } var.setValue(receiver, (VisageValue) bridge.convert(value, var.getType())); } public boolean hasField(String fieldName, Object receiver, boolean isStatic, boolean objectIsApplet, boolean[] result) { result[0] = hasField0(fieldName, (VisageValue) receiver, objectIsApplet); return true; } private boolean hasField0(String fieldName, VisageValue receiver, boolean objectIsApplet) { if (varMap == null) { collectVariables(); } VisageVarMember var = varMap.get(fieldName); if (var == null) { // Try again with the lower-case / case-insensitive version var = lowerCaseVarMap.get(fieldName); } return (var != null); } public boolean hasMethod(String methodName, Object receiver, boolean isStatic, boolean objectIsApplet, boolean[] result) { result[0] = hasMethod0(methodName, (VisageObjectValue) receiver, objectIsApplet); return true; } private boolean hasMethod0(String methodName, VisageObjectValue receiver, boolean objectIsApplet) { if (functionMap == null) { collectFunctions(); } FunctionBundle bundle = (FunctionBundle) functionMap.get(methodName); if (bundle != null) { return true; } // Try again with the lower-case / case-insensitive version bundle = (FunctionBundle) lowerCaseFunctionMap.get(methodName.toLowerCase()); return (bundle != null); } public boolean hasFieldOrMethod(String name, Object receiver, boolean isStatic, boolean objectIsApplet, boolean[] result) { boolean res = (hasField0(name, (VisageObjectValue) receiver, objectIsApplet) || hasMethod0(name, (VisageObjectValue) receiver, objectIsApplet)); result[0] = res; return true; } public Object findClass(String name) { // FIXME return null; } public Object newInstance(Object clazz, Object[] arguments) throws Exception { // FIXME return null; } //---------------------------------------------------------------------- // Internals only below this point // private VisageClassType clazz; private Bridge bridge; // Map of the names of visible variables to the variables themselves private Map<String,VisageVarMember> varMap; // Map of the lower-case names of visible variables to the variables themselves private Map<String,VisageVarMember> lowerCaseVarMap; // Map of the names of visible functions to the FunctionBundles they correspond to private Map<String,FunctionBundle> functionMap; // Lower-case version of the map above private Map<String,FunctionBundle> lowerCaseFunctionMap; private void collectVariables() { List<VisageVarMember> vars = clazz.getVariables(true); Map<String,VisageVarMember> varMap = new HashMap<String,VisageVarMember>(); Map<String,VisageVarMember> lowerCaseVarMap = new HashMap<String,VisageVarMember>(); for (VisageVarMember var : vars) { varMap.put(var.getName(), var); // Lower-case / case-insensitive version as well lowerCaseVarMap.put(var.getName().toLowerCase(), var); } this.varMap = varMap; this.lowerCaseVarMap = lowerCaseVarMap; } private class FunctionInfo { private VisageFunctionMember function; private VisageType[] argumentTypes; private boolean returnsVoid; public FunctionInfo(VisageFunctionMember function) { this.function = function; // Query the argument types VisageFunctionType type = function.getType(); argumentTypes = new VisageType[type.minArgs()]; for (int i = 0; i < argumentTypes.length; i++) { argumentTypes[i] = type.getArgumentType(i); } VisageType retType = getReturnType(); if (retType.equals(voidType)) { returnsVoid = true; } } // We override equals() to filter out duplicate methods that // come to us through different points of the inheritance // hierarchy (i.e., an abstract base class as well as an // interface) public boolean equals(Object o) { if (o == null || (o.getClass() != getClass())) { return false; } FunctionInfo self = this; FunctionInfo other = (FunctionInfo) o; // We consider ourselves equal if the name, return type // and parameters match, ignoring the declaring class return (self.getName().equals(other.getName()) && self.getReturnType().equals(other.getReturnType()) && arraysEqual(self.getArgumentTypes(), other.getArgumentTypes())); } private boolean arraysEqual(VisageType[] params1, VisageType[] params2) { if ((params1 == null) != (params2 == null)) { return false; } if (params1 == null) { return true; } if (params1.length != params2.length) { return false; } for (int i = 0; i < params1.length; i++) { if (!params1[i].equals(params2[i])) { return false; } } return true; } public VisageFunctionMember getFunction() { return function; } public String getName() { return getFunction().getName(); } public VisageType[] getArgumentTypes() { return argumentTypes; } // This might do either an invoke() or a newInstance() operation; // in the case of newInstance(), the target is ignored and may be null public Object invoke(Object target, Object[] args) throws Exception { Object res = function.invoke((VisageObjectValue) target, (VisageValue[]) args); // Return Void.TYPE for methods returning void to // disambiguate null and void return values to the caller if (res == null && returnsVoid) return Void.TYPE; return res; } // This returns the return type for a Method or the declaring // class for a Constructor public VisageType getReturnType() { return function.getType().getReturnType(); } public String toString() { return function.toString(); } } // Represents a set of overloaded functions private class FunctionBundle { protected List<FunctionInfo> functions = new ArrayList<FunctionInfo>(); public void add(VisageFunctionMember function) { FunctionInfo info = new FunctionInfo(function); // Filter out duplicate methods early if (!functions.contains(info)) { functions.add(info); } } public Result invoke(Object target, Object[] arguments) throws Exception { FunctionInfo chosenInfo = null; FunctionInfo ambiguousInfo = null; VisageType[] chosenParameterTypes = null; int minNumConversions = 0; boolean ambiguous = false; for (Iterator iter = functions.iterator(); iter.hasNext(); ) { FunctionInfo info = (FunctionInfo) iter.next(); VisageType[] parameterTypes = info.getArgumentTypes(); if (arguments == null) { if (parameterTypes.length != 0) continue; } else if (parameterTypes.length != arguments.length) continue; // If this contains a negative number after analysis, // the argument lists aren't compatible int numConversions = 0; for (int i = 0; i < parameterTypes.length; i++) { Object arg = arguments[i]; VisageType expectedType = parameterTypes[i]; int cost = bridge.conversionCost(arg, expectedType); if (cost < 0) { numConversions = -1; break; } numConversions += cost; } if (numConversions >= 0) { if (chosenInfo == null || (numConversions < minNumConversions)) { chosenInfo = info; chosenParameterTypes = info.getArgumentTypes(); minNumConversions = numConversions; ambiguous = false; } else if (numConversions == minNumConversions) { ambiguous = true; ambiguousInfo = info; } } } if (chosenInfo == null) { throw new IllegalArgumentException("No method found matching name " + ((FunctionInfo) functions.get(0)).getName() + " and arguments " + argsToString(arguments)); } if (ambiguous) { throw new IllegalArgumentException("More than one method matching name " + ((FunctionInfo) functions.get(0)).getName() + " and arguments " + argsToString(arguments) + "\n Method 1: " + chosenInfo.getFunction().toString() + "\n Method 2: " + ambiguousInfo.getFunction().toString()); } // Convert all arguments VisageValue[] newArgs = null; if (arguments != null) { newArgs = new VisageValue[arguments.length]; for (int i = 0; i < arguments.length; i++) { newArgs[i] = (VisageValue) bridge.convert(arguments[i], chosenParameterTypes[i]); } } else { // Visage reflection mechanism doesn't like null argument array newArgs = new VisageValue[0]; } Object ret = chosenInfo.invoke(target, newArgs); // Convert certain VisageValues back to Java values (primitives, Strings) return new Result(unbox(ret), false); } } private void collectFunctions() { List<VisageFunctionMember> funcs = clazz.getFunctions(true); Map<String,FunctionBundle> funcMap = new HashMap<String,FunctionBundle>(); Map<String,FunctionBundle> lowerCaseFuncMap = new HashMap<String,FunctionBundle>(); for (VisageFunctionMember func : funcs) { FunctionBundle bundle = funcMap.get(func.getName()); if (bundle == null) { bundle = new FunctionBundle(); funcMap.put(func.getName(), bundle); } bundle.add(func); // Lower-case / case-insensitive version as well (note // that the MemberBundle might contain different or more // entries than the case-sensitive one) String lowerCaseName = func.getName().toLowerCase(); bundle = lowerCaseFuncMap.get(lowerCaseName); if (bundle == null) { bundle = new FunctionBundle(); lowerCaseFuncMap.put(lowerCaseName, bundle); } bundle.add(func); } this.functionMap = funcMap; this.lowerCaseFunctionMap = lowerCaseFuncMap; } private static String argsToString(Object[] arguments) { StringBuffer buf = new StringBuffer("["); if (arguments != null) { for (int i = 0; i < arguments.length; i++) { if (i > 0) buf.append(", "); Object arg = arguments[i]; String className = null; if (arg != null) className = arg.getClass().getName(); buf.append(className); } } buf.append("]"); return buf.toString(); } }