/*
* 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 netscape.javascript.*;
import com.sun.java.browser.plugin2.liveconnect.v1.*;
import visage.reflect.*;
public class VisageConversionDelegate implements ConversionDelegate {
private VisageLocal.Context context = VisageLocal.getContext();
private Bridge bridge;
// Support for conversion of arbitrary Objects to Strings
// This has a higher conversion cost than normal conversions
// Note that this basically assumes the maximum number of incoming
// arguments is this value; could be smarter, but little benefit
private static final int TOSTRING_CONVERSION_PENALTY = 50;
// Support for conversion of JSObjects into Strings and arrays
// We prefer to do this as a last resort
private static final int JSOBJECT_CONVERSION_PENALTY =
TOSTRING_CONVERSION_PENALTY * TOSTRING_CONVERSION_PENALTY;
// Need to know about certain primitive types
private VisageType booleanType;
private VisageType charType;
private VisageType byteType;
private VisageType shortType;
private VisageType integerType;
private VisageType longType;
private VisageType floatType;
private VisageType doubleType;
public VisageConversionDelegate(Bridge bridge) {
this.bridge = bridge;
booleanType = context.getBooleanType();
charType = context.getCharacterType();
byteType = context.getByteType();
shortType = context.getShortType();
integerType = context.getIntegerType();
longType = context.getLongType();
floatType = context.getFloatType();
doubleType = context.getDoubleType();
}
public int conversionCost(Object arg, Object toType) {
if (!(toType instanceof VisageType)) {
return -1;
}
VisageType targetType = (VisageType) toType;
if (arg == null) {
if (targetType instanceof VisagePrimitiveType) {
// null arguments can not undergo unboxing conversions
return -1;
}
return 0;
}
// Primitive value conversions
if (targetType instanceof VisagePrimitiveType) {
if (targetType.equals(floatType)) {
if (arg instanceof Float)
return 0;
} else if (targetType.equals(doubleType)) {
if (arg instanceof Double)
return 0;
} else if (targetType.equals(booleanType)) {
if (arg instanceof Boolean)
return 0;
} else if (targetType.equals(integerType)) {
if (arg instanceof Integer)
return 0;
} else if (targetType.equals(longType)) {
if (arg instanceof Long)
return 0;
} else if (targetType.equals(shortType)) {
if (arg instanceof Short)
return 0;
} else if (targetType.equals(byteType)) {
if (arg instanceof Byte)
return 0;
} else if (targetType.equals(charType)) {
if (arg instanceof Character)
return 0;
}
if (arg instanceof String ||
arg instanceof Number ||
arg instanceof Character ||
arg instanceof Boolean) {
return 1;
}
// Not convertible
return -1;
}
if (arg instanceof JSObject) {
if (canConvert((JSObject) arg, targetType)) {
return JSOBJECT_CONVERSION_PENALTY;
} else {
return -1;
}
}
// FIXME: add any-to-String conversion
if (!(targetType instanceof VisageClassType)) {
// Don't know what's going on
return -1;
}
VisageType argType;
if (arg instanceof VisageValue) {
if (arg instanceof VisageObjectValue) {
argType = ((VisageObjectValue) arg).getClassType();
} else {
// Shouldn't happen if we know what is going on
return -1;
}
} else {
argType = context.makeClassRef(arg.getClass());
}
// Object type conversions
if (argType.equals(targetType)) {
return 0;
}
return conversionDistance((VisageClassType) argType, (VisageClassType) targetType);
}
private int conversionDistance(VisageClassType fromType, VisageClassType toType) {
int res = conversionDistance(fromType, toType, 0);
if (res == Integer.MAX_VALUE) {
// Not convertible
return -1;
}
return res;
}
private int conversionDistance(VisageClassType fromType, VisageClassType toType, int depth) {
if (fromType.equals(toType)) {
return depth;
}
// Walk up superclass hierarchy
List<VisageClassType> supers = fromType.getSuperClasses(false);
int minDepth = Integer.MAX_VALUE;
for (VisageClassType type : supers) {
minDepth = Math.min(minDepth, conversionDistance(type, toType, 1 + depth));
}
return minDepth;
}
public boolean convert(Object obj, Object toType, Object[] result) throws Exception {
if (!(toType instanceof VisageType)) {
return false;
}
VisageType targetType = (VisageType) toType;
if (obj == null) {
return true;
}
// Primitive value conversions
if (targetType instanceof VisagePrimitiveType) {
boolean isNumber = obj instanceof Number;
if (targetType.equals(floatType)) {
if (isNumber) {
result[0] = context.mirrorOf(((Number) obj).floatValue());
return true;
} else if (obj instanceof String) {
result[0] = context.mirrorOf(Float.valueOf((String) obj));
return true;
} else if (obj instanceof Boolean) {
result[0] = context.mirrorOf(((Boolean) obj).booleanValue() ? (float) 1.0 : (float) 0.0);
return true;
} else if (obj instanceof Character) {
result[0] = context.mirrorOf((float) ((Character) obj).charValue());
return true;
}
} else if (targetType.equals(doubleType)) {
if (isNumber) {
result[0] = context.mirrorOf(((Number) obj).doubleValue());
return true;
} else if (obj instanceof String) {
result[0] = context.mirrorOf(Double.valueOf((String) obj));
return true;
} else if (obj instanceof Boolean) {
result[0] = context.mirrorOf(((Boolean) obj).booleanValue() ? 1.0 : 0.0);
return true;
} else if (obj instanceof Character) {
result[0] = context.mirrorOf((double) ((Character) obj).charValue());
return true;
}
} else if (targetType.equals(integerType)) {
if (isNumber) {
result[0] = context.mirrorOf(((Number) obj).intValue());
return true;
} else if (obj instanceof String) {
result[0] = context.mirrorOf(Integer.valueOf((String) obj));
return true;
} else if (obj instanceof Boolean) {
result[0] = context.mirrorOf(((Boolean) obj).booleanValue() ? 1 : 0);
return true;
} else if (obj instanceof Character) {
result[0] = context.mirrorOf((int) ((Character) obj).charValue());
return true;
}
} else if (targetType.equals(longType)) {
if (isNumber) {
result[0] = context.mirrorOf(((Number) obj).longValue());
return true;
} else if (obj instanceof String) {
result[0] = context.mirrorOf(Long.valueOf((String) obj));
return true;
} else if (obj instanceof Boolean) {
result[0] = context.mirrorOf(((Boolean) obj).booleanValue() ? 1L : 0L);
return true;
} else if (obj instanceof Character) {
result[0] = context.mirrorOf((long) ((Character) obj).charValue());
return true;
}
} else if (targetType.equals(shortType)) {
if (isNumber) {
result[0] = context.mirrorOf(((Number) obj).shortValue());
return true;
} else if (obj instanceof String) {
result[0] = context.mirrorOf(Short.valueOf((String) obj));
return true;
} else if (obj instanceof Boolean) {
result[0] = context.mirrorOf(((Boolean) obj).booleanValue() ? (short) 1 : (short) 0);
return true;
} else if (obj instanceof Character) {
result[0] = context.mirrorOf((short) ((Character) obj).charValue());
return true;
}
} else if (targetType.equals(byteType)) {
if (isNumber) {
result[0] = context.mirrorOf(((Number) obj).byteValue());
return true;
} else if (obj instanceof String) {
result[0] = context.mirrorOf(Byte.valueOf((String) obj));
return true;
} else if (obj instanceof Boolean) {
result[0] = context.mirrorOf(((Boolean) obj).booleanValue() ? (byte) 1 : (byte) 0);
return true;
} else if (obj instanceof Character) {
result[0] = context.mirrorOf((byte) ((Character) obj).charValue());
return true;
}
} else if (targetType.equals(booleanType)) {
if (obj instanceof Boolean) {
result[0] = context.mirrorOf(((Boolean) obj).booleanValue());
return true;
} else if (isNumber) {
// Conversion as per Core JavaScript Guide 1.5
// FIXME: intermediate conversion to double may be a mistake
double d = ((Number) obj).doubleValue();
result[0] = context.mirrorOf(! (Double.isNaN(d) || d == 0));
return true;
} else if (obj instanceof String) {
result[0] = context.mirrorOf(((String) obj).length() != 0);
return true;
} else if (obj instanceof Character) {
result[0] = context.mirrorOf(((Character) obj).charValue() != 0);
return true;
}
}
// Inconvertible
throw inconvertible(obj, targetType);
}
if (obj instanceof JSObject) {
if (isStringType(targetType)) {
result[0] = context.mirrorOf(obj.toString());
} else if (targetType instanceof VisageSequenceType) {
try {
JSObject jsObj = (JSObject) obj;
VisageType componentType = ((VisageSequenceType) targetType).getComponentType();
int length = ((Number) jsObj.getMember("length")).intValue();
VisageValue[] values = new VisageValue[length];
Object[] tmp = new Object[1];
for (int i = 0; i < length; i++) {
Object element = null;
try {
element = jsObj.getSlot(i);
} catch (JSException e) {
// Support sparse JavaScript arrays
}
if (element != null) {
convert(element, componentType, tmp);
values[i] = (VisageValue) tmp[0];
}
}
result[0] = context.makeSequence(componentType, values);
return true;
} catch (Exception e) {
throw inconvertible(obj, targetType, e);
}
} else {
throw inconvertible(obj, targetType);
}
}
// FIXME: add any-to-String conversion
if (!(targetType instanceof VisageClassType)) {
// Don't know what's going on
throw inconvertible(obj, targetType);
}
VisageType argType;
if (obj instanceof VisageValue) {
if (obj instanceof VisageObjectValue) {
argType = ((VisageObjectValue) obj).getClassType();
} else {
// Shouldn't happen if we know what is going on
throw inconvertible(obj, targetType);
}
} else {
argType = context.makeClassRef(obj.getClass());
}
if (!targetType.isAssignableFrom(argType)) {
throw inconvertible(argType, targetType);
}
if (obj instanceof VisageValue) {
result[0] = (VisageValue) obj;
} else {
result[0] = context.mirrorOf(obj);
}
return true;
}
private boolean isStringType(VisageType targetType) {
return ((targetType instanceof VisageLocal.ClassType) &&
(((VisageLocal.ClassType) targetType).getJavaImplementationClass() == String.class));
}
// Indicates whether a JSObject can be converted to the target type
private boolean canConvert(JSObject obj, VisageType targetType) {
if (isStringType(targetType)) {
return true;
}
if (targetType instanceof VisageSequenceType) {
// See whether we have a chance of converting it; note
// that this is a risky conversion because it implies a
// lot of work that might go wrong (and we don't want to
// do it twice)
try {
obj.getMember("length");
return true;
} catch (JSException e) {
// Fall through
}
}
return false;
}
private static IllegalArgumentException inconvertible(Object object, VisageType toType) {
return inconvertible(object, toType, null);
}
private static IllegalArgumentException inconvertible(Object object, VisageType toType, Exception cause) {
IllegalArgumentException exc =
new IllegalArgumentException("Object " + object +
" can not be converted to " + toType);
if (cause != null) {
exc.initCause(cause);
}
return exc;
}
private static IllegalArgumentException inconvertible(VisageType objectType, VisageType targetType) {
return inconvertible(objectType, targetType, null);
}
private static IllegalArgumentException inconvertible(VisageType objectType, VisageType targetType, Exception cause) {
IllegalArgumentException exc =
new IllegalArgumentException("VisageType " + objectType.getName() +
" can not be converted to " + targetType.getName());
if (cause != null) {
exc.initCause(cause);
}
return exc;
}
}