/* | |
* $Id: DefaultConverter.java 161 2012-10-06 13:53:02Z andre@naef.com $ | |
* See LICENSE.txt for license terms. | |
*/ | |
package com.naef.jnlua; | |
import java.lang.reflect.Array; | |
import java.math.BigDecimal; | |
import java.math.BigInteger; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Map; | |
import com.naef.jnlua.util.AbstractTableList; | |
import com.naef.jnlua.util.AbstractTableMap; | |
/** | |
* Default implementation of the <code>Converter</code> interface. | |
*/ | |
public class DefaultConverter implements Converter { | |
// -- Static | |
/** | |
* Raw byte array. | |
*/ | |
private static final boolean RAW_BYTE_ARRAY = Boolean.parseBoolean(System | |
.getProperty(DefaultConverter.class.getPackage().getName() | |
+ ".rawByteArray")); | |
/** | |
* Static instance. | |
*/ | |
private static final DefaultConverter INSTANCE = new DefaultConverter(); | |
/** | |
* Boolean distance map. | |
*/ | |
private static final Map<Class<?>, Integer> BOOLEAN_DISTANCE_MAP = new HashMap<Class<?>, Integer>(); | |
static { | |
BOOLEAN_DISTANCE_MAP.put(Boolean.class, new Integer(1)); | |
BOOLEAN_DISTANCE_MAP.put(Boolean.TYPE, new Integer(1)); | |
BOOLEAN_DISTANCE_MAP.put(Object.class, new Integer(2)); | |
} | |
/** | |
* Number distance map. | |
*/ | |
private static final Map<Class<?>, Integer> NUMBER_DISTANCE_MAP = new HashMap<Class<?>, Integer>(); | |
static { | |
NUMBER_DISTANCE_MAP.put(Byte.class, new Integer(1)); | |
NUMBER_DISTANCE_MAP.put(Byte.TYPE, new Integer(1)); | |
NUMBER_DISTANCE_MAP.put(Short.class, new Integer(1)); | |
NUMBER_DISTANCE_MAP.put(Short.TYPE, new Integer(1)); | |
NUMBER_DISTANCE_MAP.put(Integer.class, new Integer(1)); | |
NUMBER_DISTANCE_MAP.put(Integer.TYPE, new Integer(1)); | |
NUMBER_DISTANCE_MAP.put(Long.class, new Integer(1)); | |
NUMBER_DISTANCE_MAP.put(Long.TYPE, new Integer(1)); | |
NUMBER_DISTANCE_MAP.put(Float.class, new Integer(1)); | |
NUMBER_DISTANCE_MAP.put(Float.TYPE, new Integer(1)); | |
NUMBER_DISTANCE_MAP.put(Double.class, new Integer(1)); | |
NUMBER_DISTANCE_MAP.put(Double.TYPE, new Integer(1)); | |
NUMBER_DISTANCE_MAP.put(BigInteger.class, new Integer(1)); | |
NUMBER_DISTANCE_MAP.put(BigDecimal.class, new Integer(1)); | |
NUMBER_DISTANCE_MAP.put(Character.class, new Integer(1)); | |
NUMBER_DISTANCE_MAP.put(Character.TYPE, new Integer(1)); | |
NUMBER_DISTANCE_MAP.put(Object.class, new Integer(2)); | |
NUMBER_DISTANCE_MAP.put(String.class, new Integer(3)); | |
if (!RAW_BYTE_ARRAY) { | |
NUMBER_DISTANCE_MAP.put(byte[].class, new Integer(3)); | |
} | |
} | |
/** | |
* String distance map. | |
*/ | |
private static final Map<Class<?>, Integer> STRING_DISTANCE_MAP = new HashMap<Class<?>, Integer>(); | |
static { | |
STRING_DISTANCE_MAP.put(String.class, new Integer(1)); | |
if (!RAW_BYTE_ARRAY) { | |
STRING_DISTANCE_MAP.put(byte[].class, new Integer(1)); | |
} | |
STRING_DISTANCE_MAP.put(Object.class, new Integer(2)); | |
STRING_DISTANCE_MAP.put(Byte.class, new Integer(3)); | |
STRING_DISTANCE_MAP.put(Byte.TYPE, new Integer(3)); | |
STRING_DISTANCE_MAP.put(Short.class, new Integer(3)); | |
STRING_DISTANCE_MAP.put(Short.TYPE, new Integer(3)); | |
STRING_DISTANCE_MAP.put(Integer.class, new Integer(3)); | |
STRING_DISTANCE_MAP.put(Integer.TYPE, new Integer(3)); | |
STRING_DISTANCE_MAP.put(Long.class, new Integer(3)); | |
STRING_DISTANCE_MAP.put(Long.TYPE, new Integer(3)); | |
STRING_DISTANCE_MAP.put(Float.class, new Integer(3)); | |
STRING_DISTANCE_MAP.put(Float.TYPE, new Integer(3)); | |
STRING_DISTANCE_MAP.put(Double.class, new Integer(3)); | |
STRING_DISTANCE_MAP.put(Double.TYPE, new Integer(3)); | |
STRING_DISTANCE_MAP.put(BigInteger.class, new Integer(3)); | |
STRING_DISTANCE_MAP.put(BigDecimal.class, new Integer(3)); | |
STRING_DISTANCE_MAP.put(Character.class, new Integer(3)); | |
STRING_DISTANCE_MAP.put(Character.TYPE, new Integer(3)); | |
} | |
/** | |
* Function distance map. | |
*/ | |
private static final Map<Class<?>, Integer> FUNCTION_DISTANCE_MAP = new HashMap<Class<?>, Integer>(); | |
static { | |
FUNCTION_DISTANCE_MAP.put(JavaFunction.class, new Integer(1)); | |
FUNCTION_DISTANCE_MAP.put(Object.class, new Integer(2)); | |
} | |
/** | |
* Lua value converters. | |
*/ | |
private static final Map<Class<?>, LuaValueConverter<?>> LUA_VALUE_CONVERTERS = new HashMap<Class<?>, LuaValueConverter<?>>(); | |
static { | |
LuaValueConverter<Boolean> booleanConverter = new LuaValueConverter<Boolean>() { | |
@Override | |
public Boolean convert(LuaState luaState, int index) { | |
return Boolean.valueOf(luaState.toBoolean(index)); | |
} | |
}; | |
LUA_VALUE_CONVERTERS.put(Boolean.class, booleanConverter); | |
LUA_VALUE_CONVERTERS.put(Boolean.TYPE, booleanConverter); | |
LuaValueConverter<Byte> byteConverter = new LuaValueConverter<Byte>() { | |
@Override | |
public Byte convert(LuaState luaState, int index) { | |
return Byte.valueOf((byte) luaState.toInteger(index)); | |
} | |
}; | |
LUA_VALUE_CONVERTERS.put(Byte.class, byteConverter); | |
LUA_VALUE_CONVERTERS.put(Byte.TYPE, byteConverter); | |
LuaValueConverter<Short> shortConverter = new LuaValueConverter<Short>() { | |
@Override | |
public Short convert(LuaState luaState, int index) { | |
return Short.valueOf((short) luaState.toInteger(index)); | |
} | |
}; | |
LUA_VALUE_CONVERTERS.put(Short.class, shortConverter); | |
LUA_VALUE_CONVERTERS.put(Short.TYPE, shortConverter); | |
LuaValueConverter<Integer> integerConverter = new LuaValueConverter<Integer>() { | |
@Override | |
public Integer convert(LuaState luaState, int index) { | |
return Integer.valueOf(luaState.toInteger(index)); | |
} | |
}; | |
LUA_VALUE_CONVERTERS.put(Integer.class, integerConverter); | |
LUA_VALUE_CONVERTERS.put(Integer.TYPE, integerConverter); | |
LuaValueConverter<Long> longConverter = new LuaValueConverter<Long>() { | |
@Override | |
public Long convert(LuaState luaState, int index) { | |
return Long.valueOf((long) luaState.toNumber(index)); | |
} | |
}; | |
LUA_VALUE_CONVERTERS.put(Long.class, longConverter); | |
LUA_VALUE_CONVERTERS.put(Long.TYPE, longConverter); | |
LuaValueConverter<Float> floatConverter = new LuaValueConverter<Float>() { | |
@Override | |
public Float convert(LuaState luaState, int index) { | |
return Float.valueOf((float) luaState.toNumber(index)); | |
} | |
}; | |
LUA_VALUE_CONVERTERS.put(Float.class, floatConverter); | |
LUA_VALUE_CONVERTERS.put(Float.TYPE, floatConverter); | |
LuaValueConverter<Double> doubleConverter = new LuaValueConverter<Double>() { | |
@Override | |
public Double convert(LuaState luaState, int index) { | |
return Double.valueOf(luaState.toNumber(index)); | |
} | |
}; | |
LUA_VALUE_CONVERTERS.put(Double.class, doubleConverter); | |
LUA_VALUE_CONVERTERS.put(Double.TYPE, doubleConverter); | |
LuaValueConverter<BigInteger> bigIntegerConverter = new LuaValueConverter<BigInteger>() { | |
@Override | |
public BigInteger convert(LuaState luaState, int index) { | |
return BigDecimal.valueOf(luaState.toNumber(index)) | |
.setScale(0, BigDecimal.ROUND_HALF_EVEN).toBigInteger(); | |
} | |
}; | |
LUA_VALUE_CONVERTERS.put(BigInteger.class, bigIntegerConverter); | |
LuaValueConverter<BigDecimal> bigDecimalConverter = new LuaValueConverter<BigDecimal>() { | |
@Override | |
public BigDecimal convert(LuaState luaState, int index) { | |
return BigDecimal.valueOf(luaState.toNumber(index)); | |
} | |
}; | |
LUA_VALUE_CONVERTERS.put(BigDecimal.class, bigDecimalConverter); | |
LuaValueConverter<Character> characterConverter = new LuaValueConverter<Character>() { | |
@Override | |
public Character convert(LuaState luaState, int index) { | |
return Character.valueOf((char) luaState.toInteger(index)); | |
} | |
}; | |
LUA_VALUE_CONVERTERS.put(Character.class, characterConverter); | |
LUA_VALUE_CONVERTERS.put(Character.TYPE, characterConverter); | |
LuaValueConverter<String> stringConverter = new LuaValueConverter<String>() { | |
@Override | |
public String convert(LuaState luaState, int index) { | |
return luaState.toString(index); | |
} | |
}; | |
LUA_VALUE_CONVERTERS.put(String.class, stringConverter); | |
if (!RAW_BYTE_ARRAY) { | |
LuaValueConverter<byte[]> byteArrayConverter = new LuaValueConverter<byte[]>() { | |
@Override | |
public byte[] convert(LuaState luaState, int index) { | |
return luaState.toByteArray(index); | |
} | |
}; | |
LUA_VALUE_CONVERTERS.put(byte[].class, byteArrayConverter); | |
} | |
} | |
/** | |
* Java object converters. | |
*/ | |
private static final Map<Class<?>, JavaObjectConverter<?>> JAVA_OBJECT_CONVERTERS = new HashMap<Class<?>, JavaObjectConverter<?>>(); | |
static { | |
JavaObjectConverter<Boolean> booleanConverter = new JavaObjectConverter<Boolean>() { | |
@Override | |
public void convert(LuaState luaState, Boolean booleanValue) { | |
luaState.pushBoolean(booleanValue.booleanValue()); | |
} | |
}; | |
JAVA_OBJECT_CONVERTERS.put(Boolean.class, booleanConverter); | |
JAVA_OBJECT_CONVERTERS.put(Boolean.TYPE, booleanConverter); | |
JavaObjectConverter<Number> numberConverter = new JavaObjectConverter<Number>() { | |
@Override | |
public void convert(LuaState luaState, Number number) { | |
luaState.pushNumber(number.doubleValue()); | |
} | |
}; | |
JAVA_OBJECT_CONVERTERS.put(Byte.class, numberConverter); | |
JAVA_OBJECT_CONVERTERS.put(Byte.TYPE, numberConverter); | |
JAVA_OBJECT_CONVERTERS.put(Short.class, numberConverter); | |
JAVA_OBJECT_CONVERTERS.put(Short.TYPE, numberConverter); | |
JAVA_OBJECT_CONVERTERS.put(Integer.class, numberConverter); | |
JAVA_OBJECT_CONVERTERS.put(Integer.TYPE, numberConverter); | |
JAVA_OBJECT_CONVERTERS.put(Long.class, numberConverter); | |
JAVA_OBJECT_CONVERTERS.put(Long.TYPE, numberConverter); | |
JAVA_OBJECT_CONVERTERS.put(Float.class, numberConverter); | |
JAVA_OBJECT_CONVERTERS.put(Float.TYPE, numberConverter); | |
JAVA_OBJECT_CONVERTERS.put(Double.class, numberConverter); | |
JAVA_OBJECT_CONVERTERS.put(Double.TYPE, numberConverter); | |
JAVA_OBJECT_CONVERTERS.put(BigInteger.class, numberConverter); | |
JAVA_OBJECT_CONVERTERS.put(BigDecimal.class, numberConverter); | |
JavaObjectConverter<Character> characterConverter = new JavaObjectConverter<Character>() { | |
@Override | |
public void convert(LuaState luaState, Character character) { | |
luaState.pushInteger(character.charValue()); | |
} | |
}; | |
JAVA_OBJECT_CONVERTERS.put(Character.class, characterConverter); | |
JAVA_OBJECT_CONVERTERS.put(Character.TYPE, characterConverter); | |
JavaObjectConverter<String> stringConverter = new JavaObjectConverter<String>() { | |
@Override | |
public void convert(LuaState luaState, String string) { | |
luaState.pushString(string); | |
} | |
}; | |
JAVA_OBJECT_CONVERTERS.put(String.class, stringConverter); | |
if (!RAW_BYTE_ARRAY) { | |
JavaObjectConverter<byte[]> byteArrayConverter = new JavaObjectConverter<byte[]>() { | |
@Override | |
public void convert(LuaState luaState, byte[] byteArray) { | |
luaState.pushByteArray(byteArray); | |
} | |
}; | |
JAVA_OBJECT_CONVERTERS.put(byte[].class, byteArrayConverter); | |
} | |
} | |
// -- Static methods | |
/** | |
* Returns the instance of this class. | |
* | |
* @return the instance | |
*/ | |
public static DefaultConverter getInstance() { | |
return INSTANCE; | |
} | |
// -- Construction | |
/** | |
* Singleton. | |
*/ | |
private DefaultConverter() { | |
} | |
// -- Java converter methods | |
@Override | |
public int getTypeDistance(LuaState luaState, int index, Class<?> formalType) { | |
// Handle none | |
LuaType luaType = luaState.type(index); | |
if (luaType == null) { | |
return Integer.MAX_VALUE; | |
} | |
// Handle void | |
if (formalType == Void.TYPE) { | |
return Integer.MAX_VALUE; | |
} | |
// Handle Lua value proxy | |
if (formalType == LuaValueProxy.class) { | |
return 0; | |
} | |
// Handle Lua types | |
switch (luaType) { | |
case NIL: | |
return 1; | |
case BOOLEAN: | |
Integer distance = BOOLEAN_DISTANCE_MAP.get(formalType); | |
if (distance != null) { | |
return distance.intValue(); | |
} | |
break; | |
case NUMBER: | |
distance = NUMBER_DISTANCE_MAP.get(formalType); | |
if (distance != null) { | |
return distance.intValue(); | |
} | |
break; | |
case STRING: | |
distance = STRING_DISTANCE_MAP.get(formalType); | |
if (distance != null) { | |
return distance.intValue(); | |
} | |
break; | |
case TABLE: | |
if (formalType == Map.class || formalType == List.class | |
|| formalType.isArray()) { | |
return 1; | |
} | |
if (formalType == Object.class) { | |
return 2; | |
} | |
break; | |
case FUNCTION: | |
if (luaState.isJavaFunction(index)) { | |
distance = FUNCTION_DISTANCE_MAP.get(formalType); | |
if (distance != null) { | |
return distance.intValue(); | |
} | |
} | |
break; | |
case USERDATA: | |
Object object = luaState.toJavaObjectRaw(index); | |
if (object != null) { | |
Class<?> type; | |
if (object instanceof TypedJavaObject) { | |
TypedJavaObject typedJavaObject = (TypedJavaObject) object; | |
if (typedJavaObject.isStrong()) { | |
if (formalType.isAssignableFrom(typedJavaObject | |
.getClass())) { | |
return 1; | |
} | |
} | |
type = typedJavaObject.getType(); | |
} else { | |
type = object.getClass(); | |
} | |
if (formalType.isAssignableFrom(type)) { | |
return 1; | |
} | |
} | |
break; | |
} | |
// Handle object | |
if (formalType == Object.class) { | |
return Integer.MAX_VALUE - 1; | |
} | |
// Unsupported conversion | |
return Integer.MAX_VALUE; | |
} | |
@SuppressWarnings("unchecked") | |
@Override | |
public <T> T convertLuaValue(LuaState luaState, int index, | |
Class<T> formalType) { | |
// Handle none | |
LuaType luaType = luaState.type(index); | |
if (luaType == null) { | |
throw new IllegalArgumentException("undefined index: " + index); | |
} | |
// Handle void | |
if (formalType == Void.TYPE) { | |
throw new ClassCastException(String.format( | |
"cannot convert %s to %s", luaState.typeName(index), | |
formalType.getCanonicalName())); | |
} | |
// Handle Lua value proxy | |
if (formalType == LuaValueProxy.class) { | |
return (T) luaState.getProxy(index); | |
} | |
// Handle Lua types | |
switch (luaType) { | |
case NIL: | |
return null; | |
case BOOLEAN: | |
LuaValueConverter<?> luaValueConverter; | |
luaValueConverter = LUA_VALUE_CONVERTERS.get(formalType); | |
if (luaValueConverter != null) { | |
return (T) luaValueConverter.convert(luaState, index); | |
} | |
if (formalType == Object.class) { | |
return (T) Boolean.valueOf(luaState.toBoolean(index)); | |
} | |
break; | |
case NUMBER: | |
luaValueConverter = LUA_VALUE_CONVERTERS.get(formalType); | |
if (luaValueConverter != null) { | |
return (T) luaValueConverter.convert(luaState, index); | |
} | |
if (formalType == Object.class) { | |
return (T) Double.valueOf(luaState.toNumber(index)); | |
} | |
break; | |
case STRING: | |
luaValueConverter = LUA_VALUE_CONVERTERS.get(formalType); | |
if (luaValueConverter != null) { | |
return (T) luaValueConverter.convert(luaState, index); | |
} | |
if (formalType == Object.class) { | |
return (T) luaState.toString(index); | |
} | |
break; | |
case TABLE: | |
if (formalType == Map.class || formalType == Object.class) { | |
final LuaValueProxy luaValueProxy = luaState.getProxy(index); | |
return (T) new AbstractTableMap<Object>() { | |
@Override | |
protected Object convertKey(int index) { | |
return getLuaState().toJavaObject(index, Object.class); | |
} | |
@Override | |
public LuaState getLuaState() { | |
return luaValueProxy.getLuaState(); | |
} | |
@Override | |
public void pushValue() { | |
luaValueProxy.pushValue(); | |
} | |
}; | |
} | |
if (formalType == List.class) { | |
final LuaValueProxy luaValueProxy = luaState.getProxy(index); | |
return (T) new AbstractTableList() { | |
@Override | |
public LuaState getLuaState() { | |
return luaValueProxy.getLuaState(); | |
} | |
@Override | |
public void pushValue() { | |
luaValueProxy.pushValue(); | |
} | |
}; | |
} | |
if (formalType.isArray()) { | |
int length = luaState.rawLen(index); | |
Class<?> componentType = formalType.getComponentType(); | |
Object array = Array.newInstance(formalType.getComponentType(), | |
length); | |
for (int i = 0; i < length; i++) { | |
luaState.rawGet(index, i + 1); | |
try { | |
Array.set(array, i, | |
convertLuaValue(luaState, -1, componentType)); | |
} finally { | |
luaState.pop(1); | |
} | |
} | |
return (T) array; | |
} | |
break; | |
case FUNCTION: | |
if (luaState.isJavaFunction(index)) { | |
if (formalType == JavaFunction.class | |
|| formalType == Object.class) { | |
return (T) luaState.toJavaFunction(index); | |
} | |
} | |
break; | |
case USERDATA: | |
Object object = luaState.toJavaObjectRaw(index); | |
if (object != null) { | |
if (object instanceof TypedJavaObject) { | |
TypedJavaObject typedJavaObject = (TypedJavaObject) object; | |
if (typedJavaObject.isStrong()) { | |
if (formalType.isAssignableFrom(typedJavaObject | |
.getClass())) { | |
return (T) typedJavaObject; | |
} | |
} | |
return (T) ((TypedJavaObject) object).getObject(); | |
} else { | |
return (T) object; | |
} | |
} | |
break; | |
} | |
// Handle object | |
if (formalType == Object.class) { | |
return (T) luaState.getProxy(index); | |
} | |
// Unsupported conversion | |
throw new ClassCastException(String.format("cannot convert %s to %s", | |
luaState.typeName(index), formalType.getCanonicalName())); | |
} | |
@SuppressWarnings("unchecked") | |
@Override | |
public void convertJavaObject(LuaState luaState, Object object) { | |
// Handle null | |
if (object == null) { | |
luaState.pushNil(); | |
return; | |
} | |
// Handle known Java types | |
JavaObjectConverter<Object> javaObjectConverter = (JavaObjectConverter<Object>) JAVA_OBJECT_CONVERTERS | |
.get(object.getClass()); | |
if (javaObjectConverter != null) { | |
javaObjectConverter.convert(luaState, object); | |
return; | |
} | |
if (object instanceof JavaFunction) { | |
luaState.pushJavaFunction((JavaFunction) object); | |
return; | |
} | |
if (object instanceof LuaValueProxy) { | |
LuaValueProxy luaValueProxy = (LuaValueProxy) object; | |
if (!luaValueProxy.getLuaState().equals(luaState)) { | |
throw new IllegalArgumentException( | |
"Lua value proxy is from a different Lua state"); | |
} | |
luaValueProxy.pushValue(); | |
return; | |
} | |
// Push as is | |
luaState.pushJavaObjectRaw(object); | |
} | |
// -- Nested types | |
/** | |
* Converts Lua values. | |
*/ | |
private interface LuaValueConverter<T> { | |
/** | |
* Converts a Lua value to a Java object. | |
*/ | |
public T convert(LuaState luaState, int index); | |
} | |
/** | |
* Converts Java object. | |
*/ | |
private interface JavaObjectConverter<T> { | |
/** | |
* Converts a Java object to a Lua value. | |
*/ | |
public void convert(LuaState luaState, T object); | |
} | |
} |