/******************************************************************************* * 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; } }