/*******************************************************************************
* Copyright (c) 2009-2011 Luaj.org. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
package org.luaj.vm2.lib.jse;
import java.lang.reflect.Array;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.luaj.vm2.LuaString;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
/**
* Helper class to coerce values from lua to Java within the luajava library.
* <p>
* This class is primarily used by the {@link LuajavaLib},
* but can also be used directly when working with Java/lua bindings.
* <p>
* To coerce to specific Java values, generally the {@code toType()} methods
* on {@link LuaValue} may be used:
* <ul>
* <li>{@link LuaValue#toboolean()}</li>
* <li>{@link LuaValue#tobyte()}</li>
* <li>{@link LuaValue#tochar()}</li>
* <li>{@link LuaValue#toshort()}</li>
* <li>{@link LuaValue#toint()}</li>
* <li>{@link LuaValue#tofloat()}</li>
* <li>{@link LuaValue#todouble()}</li>
* <li>{@link LuaValue#tojstring()}</li>
* <li>{@link LuaValue#touserdata()}</li>
* <li>{@link LuaValue#touserdata(Class)}</li>
* </ul>
* <p>
* For data in lua tables, the various methods on {@link LuaTable} can be used directly
* to convert data to something more useful.
*
* @see LuajavaLib
* @see CoerceJavaToLua
*/
public class CoerceLuaToJava {
static int SCORE_NULL_VALUE = 0x10;
static int SCORE_WRONG_TYPE = 0x100;
static int SCORE_UNCOERCIBLE = 0x10000;
static interface Coercion {
public int score( LuaValue value );
public Object coerce( LuaValue value );
};
/**
* Coerce a LuaValue value to a specified java class
* @param value LuaValue to coerce
* @param clazz Class to coerce into
* @return Object of type clazz (or a subclass) with the corresponding value.
*/
public static Object coerce(LuaValue value, Class clazz) {
return getCoercion(clazz).coerce(value);
}
static final Map COERCIONS = Collections.synchronizedMap(new HashMap());
static final class BoolCoercion implements Coercion {
public String toString() {
return "BoolCoercion()";
}
public int score( LuaValue value ) {
switch ( value.type() ) {
case LuaValue.TBOOLEAN:
return 0;
}
return 1;
}
public Object coerce(LuaValue value) {
return value.toboolean()? Boolean.TRUE: Boolean.FALSE;
}
}
static final class NumericCoercion implements Coercion {
static final int TARGET_TYPE_BYTE = 0;
static final int TARGET_TYPE_CHAR = 1;
static final int TARGET_TYPE_SHORT = 2;
static final int TARGET_TYPE_INT = 3;
static final int TARGET_TYPE_LONG = 4;
static final int TARGET_TYPE_FLOAT = 5;
static final int TARGET_TYPE_DOUBLE = 6;
static final String[] TYPE_NAMES = { "byte", "char", "short", "int", "long", "float", "double" };
final int targetType;
public String toString() {
return "NumericCoercion("+TYPE_NAMES[targetType]+")";
}
NumericCoercion(int targetType) {
this.targetType = targetType;
}
public int score( LuaValue value ) {
int fromStringPenalty = 0;
if ( value.type() == LuaValue.TSTRING ) {
value = value.tonumber();
if ( value.isnil() ) {
return SCORE_UNCOERCIBLE;
}
fromStringPenalty = 4;
}
if ( value.isint() ) {
switch ( targetType ) {
case TARGET_TYPE_BYTE: {
int i = value.toint();
return fromStringPenalty + ((i==(byte)i)? 0: SCORE_WRONG_TYPE);
}
case TARGET_TYPE_CHAR: {
int i = value.toint();
return fromStringPenalty + ((i==(byte)i)? 1: (i==(char)i)? 0: SCORE_WRONG_TYPE);
}
case TARGET_TYPE_SHORT: {
int i = value.toint();
return fromStringPenalty +
((i==(byte)i)? 1: (i==(short)i)? 0: SCORE_WRONG_TYPE);
}
case TARGET_TYPE_INT: {
int i = value.toint();
return fromStringPenalty +
((i==(byte)i)? 2: ((i==(char)i) || (i==(short)i))? 1: 0);
}
case TARGET_TYPE_FLOAT: return fromStringPenalty + 1;
case TARGET_TYPE_LONG: return fromStringPenalty + 1;
case TARGET_TYPE_DOUBLE: return fromStringPenalty + 2;
default: return SCORE_WRONG_TYPE;
}
} else if ( value.isnumber() ) {
switch ( targetType ) {
case TARGET_TYPE_BYTE: return SCORE_WRONG_TYPE;
case TARGET_TYPE_CHAR: return SCORE_WRONG_TYPE;
case TARGET_TYPE_SHORT: return SCORE_WRONG_TYPE;
case TARGET_TYPE_INT: return SCORE_WRONG_TYPE;
case TARGET_TYPE_LONG: {
double d = value.todouble();
return fromStringPenalty + ((d==(long)d)? 0: SCORE_WRONG_TYPE);
}
case TARGET_TYPE_FLOAT: {
double d = value.todouble();
return fromStringPenalty + ((d==(float)d)? 0: SCORE_WRONG_TYPE);
}
case TARGET_TYPE_DOUBLE: {
double d = value.todouble();
return fromStringPenalty + (((d==(long)d) || (d==(float)d))? 1: 0);
}
default: return SCORE_WRONG_TYPE;
}
} else {
return SCORE_UNCOERCIBLE;
}
}
public Object coerce(LuaValue value) {
switch ( targetType ) {
case TARGET_TYPE_BYTE: return new Byte( (byte) value.toint() );
case TARGET_TYPE_CHAR: return new Character( (char) value.toint() );
case TARGET_TYPE_SHORT: return new Short( (short) value.toint() );
case TARGET_TYPE_INT: return new Integer( (int) value.toint() );
case TARGET_TYPE_LONG: return new Long( (long) value.todouble() );
case TARGET_TYPE_FLOAT: return new Float( (float) value.todouble() );
case TARGET_TYPE_DOUBLE: return new Double( (double) value.todouble() );
default: return null;
}
}
}
static final class StringCoercion implements Coercion {
public static final int TARGET_TYPE_STRING = 0;
public static final int TARGET_TYPE_BYTES = 1;
final int targetType;
public StringCoercion(int targetType) {
this.targetType = targetType;
}
public String toString() {
return "StringCoercion("+(targetType==TARGET_TYPE_STRING? "String": "byte[]")+")";
}
public int score(LuaValue value) {
switch ( value.type() ) {
case LuaValue.TSTRING:
return value.checkstring().isValidUtf8()?
(targetType==TARGET_TYPE_STRING? 0: 1):
(targetType==TARGET_TYPE_BYTES? 0: SCORE_WRONG_TYPE);
case LuaValue.TNIL:
return SCORE_NULL_VALUE;
default:
return targetType == TARGET_TYPE_STRING? SCORE_WRONG_TYPE: SCORE_UNCOERCIBLE;
}
}
public Object coerce(LuaValue value) {
if ( value.isnil() )
return null;
if ( targetType == TARGET_TYPE_STRING )
return value.tojstring();
LuaString s = value.checkstring();
byte[] b = new byte[s.m_length];
s.copyInto(0, b, 0, b.length);
return b;
}
}
static final class ArrayCoercion implements Coercion {
final Class componentType;
final Coercion componentCoercion;
public ArrayCoercion(Class componentType) {
this.componentType = componentType;
this.componentCoercion = getCoercion(componentType);
}
public String toString() {
return "ArrayCoercion("+componentType.getName()+")";
}
public int score(LuaValue value) {
switch ( value.type() ) {
case LuaValue.TTABLE:
return value.length()==0? 0: componentCoercion.score( value.get(1) );
case LuaValue.TUSERDATA:
return inheritanceLevels( componentType, value.touserdata().getClass().getComponentType() );
case LuaValue.TNIL:
return SCORE_NULL_VALUE;
default:
return SCORE_UNCOERCIBLE;
}
}
public Object coerce(LuaValue value) {
switch ( value.type() ) {
case LuaValue.TTABLE: {
int n = value.length();
Object a = Array.newInstance(componentType, n);
for ( int i=0; i<n; i++ )
Array.set(a, i, componentCoercion.coerce(value.get(i+1)));
return a;
}
case LuaValue.TUSERDATA:
return value.touserdata();
case LuaValue.TNIL:
return null;
default:
return null;
}
}
}
/**
* Determine levels of inheritance between a base class and a subclass
* @param baseclass base class to look for
* @param subclass class from which to start looking
* @return number of inheritance levels between subclass and baseclass,
* or SCORE_UNCOERCIBLE if not a subclass
*/
static final int inheritanceLevels( Class baseclass, Class subclass ) {
if ( subclass == null )
return SCORE_UNCOERCIBLE;
if ( baseclass == subclass )
return 0;
int min = Math.min( SCORE_UNCOERCIBLE, inheritanceLevels( baseclass, subclass.getSuperclass() ) + 1 );
Class[] ifaces = subclass.getInterfaces();
for ( int i=0; i<ifaces.length; i++ )
min = Math.min(min, inheritanceLevels(baseclass, ifaces[i]) + 1 );
return min;
}
static final class ObjectCoercion implements Coercion {
final Class targetType;
ObjectCoercion(Class targetType) {
this.targetType = targetType;
}
public String toString() {
return "ObjectCoercion("+targetType.getName()+")";
}
public int score(LuaValue value) {
switch ( value.type() ) {
case LuaValue.TNUMBER:
return inheritanceLevels( targetType, value.isint()? Integer.class: Double.class );
case LuaValue.TBOOLEAN:
return inheritanceLevels( targetType, Boolean.class );
case LuaValue.TSTRING:
return inheritanceLevels( targetType, String.class );
case LuaValue.TUSERDATA:
return inheritanceLevels( targetType, value.touserdata().getClass() );
case LuaValue.TNIL:
return SCORE_NULL_VALUE;
default:
return inheritanceLevels( targetType, value.getClass() );
}
}
public Object coerce(LuaValue value) {
switch ( value.type() ) {
case LuaValue.TNUMBER:
return value.isint()? (Object)new Integer(value.toint()): (Object)new Double(value.todouble());
case LuaValue.TBOOLEAN:
return value.toboolean()? Boolean.TRUE: Boolean.FALSE;
case LuaValue.TSTRING:
return value.tojstring();
case LuaValue.TUSERDATA:
return value.optuserdata(targetType, null);
case LuaValue.TNIL:
return null;
default:
return value;
}
}
}
static {
Coercion boolCoercion = new BoolCoercion();
Coercion byteCoercion = new NumericCoercion(NumericCoercion.TARGET_TYPE_BYTE);
Coercion charCoercion = new NumericCoercion(NumericCoercion.TARGET_TYPE_CHAR);
Coercion shortCoercion = new NumericCoercion(NumericCoercion.TARGET_TYPE_SHORT);
Coercion intCoercion = new NumericCoercion(NumericCoercion.TARGET_TYPE_INT);
Coercion longCoercion = new NumericCoercion(NumericCoercion.TARGET_TYPE_LONG);
Coercion floatCoercion = new NumericCoercion(NumericCoercion.TARGET_TYPE_FLOAT);
Coercion doubleCoercion = new NumericCoercion(NumericCoercion.TARGET_TYPE_DOUBLE);
Coercion stringCoercion = new StringCoercion(StringCoercion.TARGET_TYPE_STRING);
Coercion bytesCoercion = new StringCoercion(StringCoercion.TARGET_TYPE_BYTES);
COERCIONS.put( Boolean.TYPE, boolCoercion );
COERCIONS.put( Boolean.class, boolCoercion );
COERCIONS.put( Byte.TYPE, byteCoercion );
COERCIONS.put( Byte.class, byteCoercion );
COERCIONS.put( Character.TYPE, charCoercion );
COERCIONS.put( Character.class, charCoercion );
COERCIONS.put( Short.TYPE, shortCoercion );
COERCIONS.put( Short.class, shortCoercion );
COERCIONS.put( Integer.TYPE, intCoercion );
COERCIONS.put( Integer.class, intCoercion );
COERCIONS.put( Long.TYPE, longCoercion );
COERCIONS.put( Long.class, longCoercion );
COERCIONS.put( Float.TYPE, floatCoercion );
COERCIONS.put( Float.class, floatCoercion );
COERCIONS.put( Double.TYPE, doubleCoercion );
COERCIONS.put( Double.class, doubleCoercion );
COERCIONS.put( String.class, stringCoercion );
COERCIONS.put( byte[].class, bytesCoercion );
}
static Coercion getCoercion(Class c) {
Coercion co = (Coercion) COERCIONS.get( c );
if ( co != null ) {
return co;
}
if ( c.isArray() ) {
Class typ = c.getComponentType();
co = new ArrayCoercion(c.getComponentType());
} else {
co = new ObjectCoercion(c);
}
COERCIONS.put( c, co );
return co;
}
}