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