PageRenderTime 120ms CodeModel.GetById 88ms app.highlight 27ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-5-pre1/org/gjt/sp/jedit/bsh/Types.java

#
Java | 530 lines | 253 code | 49 blank | 228 comment | 140 complexity | d5fff6aeecf4d871faab6bf214461a0b MD5 | raw file
  1/*****************************************************************************
  2 *                                                                           *
  3 *  This file is part of the BeanShell Java Scripting distribution.          *
  4 *  Documentation and updates may be found at http://www.beanshell.org/      *
  5 *                                                                           *
  6 *  Sun Public License Notice:                                               *
  7 *                                                                           *
  8 *  The contents of this file are subject to the Sun Public License Version  *
  9 *  1.0 (the "License"); you may not use this file except in compliance with *
 10 *  the License. A copy of the License is available at http://www.sun.com    * 
 11 *                                                                           *
 12 *  The Original Code is BeanShell. The Initial Developer of the Original    *
 13 *  Code is Pat Niemeyer. Portions created by Pat Niemeyer are Copyright     *
 14 *  (C) 2000.  All Rights Reserved.                                          *
 15 *                                                                           *
 16 *  GNU Public License Notice:                                               *
 17 *                                                                           *
 18 *  Alternatively, the contents of this file may be used under the terms of  *
 19 *  the GNU Lesser General Public License (the "LGPL"), in which case the    *
 20 *  provisions of LGPL are applicable instead of those above. If you wish to *
 21 *  allow use of your version of this file only under the  terms of the LGPL *
 22 *  and not to allow others to use your version of this file under the SPL,  *
 23 *  indicate your decision by deleting the provisions above and replace      *
 24 *  them with the notice and other provisions required by the LGPL.  If you  *
 25 *  do not delete the provisions above, a recipient may use your version of  *
 26 *  this file under either the SPL or the LGPL.                              *
 27 *                                                                           *
 28 *  Patrick Niemeyer (pat@pat.net)                                           *
 29 *  Author of Learning Java, O'Reilly & Associates                           *
 30 *  http://www.pat.net/~pat/                                                 *
 31 *                                                                           *
 32 *****************************************************************************/
 33
 34package org.gjt.sp.jedit.bsh;
 35
 36/**
 37	Static routines supporing type comparison and conversion in BeanShell.
 38
 39 The following are notes on type comparison and conversion in BeanShell.
 40
 41
 42*/
 43class Types 
 44{
 45	/*
 46		Type conversion identifiers.  An ASSIGNMENT allows conversions that would
 47		normally happen on assignment.  A CAST performs numeric conversions to smaller
 48		types (as in an explicit Java cast) and things allowed only in variable and array
 49		declarations (e.g. byte b = 42;)
 50	*/
 51	static final int CAST=0, ASSIGNMENT=1;
 52	
 53	static final int 
 54		JAVA_BASE_ASSIGNABLE = 1,
 55		JAVA_BOX_TYPES_ASSIGABLE = 2,
 56		JAVA_VARARGS_ASSIGNABLE = 3,
 57		BSH_ASSIGNABLE = 4;
 58
 59	static final int
 60		FIRST_ROUND_ASSIGNABLE = JAVA_BASE_ASSIGNABLE,
 61		LAST_ROUND_ASSIGNABLE = BSH_ASSIGNABLE;
 62
 63	/**
 64		Special value that indicates by identity that the result of a cast
 65		operation was a valid cast.  This is used by castObject() and
 66		castPrimitive() in the checkOnly mode of operation.  This value is a
 67		Primitive type so that it can be returned by castPrimitive.
 68	*/
 69	static Primitive VALID_CAST = new Primitive(1);
 70	static Primitive INVALID_CAST = new Primitive(-1);
 71
 72	/**
 73		Get the Java types of the arguments.
 74	*/
 75    public static Class[] getTypes( Object[] args )
 76    {
 77        if ( args == null )
 78            return new Class[0];
 79
 80        Class[] types = new Class[ args.length ];
 81
 82        for( int i=0; i<args.length; i++ )
 83        {
 84			if ( args[i] == null )
 85				types[i] = null;
 86            else
 87			if ( args[i] instanceof Primitive )
 88                types[i] = ((Primitive)args[i]).getType();
 89            else
 90                types[i] = args[i].getClass();
 91        }
 92
 93        return types;
 94    }
 95
 96	/**
 97	 Is the 'from' signature (argument types) assignable to the 'to'
 98	 signature (candidate method types)
 99	 This method handles the special case of null values in 'to' types
100	 indicating a loose type and matching anything.
101	 */
102	/* Should check for strict java here and limit to isJavaAssignable() */
103	static boolean isSignatureAssignable( Class[] from, Class[] to, int round )
104	{
105		if ( round != JAVA_VARARGS_ASSIGNABLE && from.length != to.length )
106			return false;
107
108		switch ( round )
109		{
110			case JAVA_BASE_ASSIGNABLE:
111				for( int i=0; i<from.length; i++ )
112					if ( !isJavaBaseAssignable( to[i], from[i] ) )
113						return false;
114				return true;
115			case JAVA_BOX_TYPES_ASSIGABLE:
116				for( int i=0; i<from.length; i++ )
117					if ( !isJavaBoxTypesAssignable( to[i], from[i] ) )
118						return false;
119				return true;
120			case JAVA_VARARGS_ASSIGNABLE:
121				return isSignatureVarargsAssignable( from, to );
122			case BSH_ASSIGNABLE:
123				for( int i=0; i<from.length; i++ )
124					if ( !isBshAssignable( to[i], from[i] ) )
125						return false;
126				return true;
127			default:
128				throw new InterpreterError("bad case");
129		}
130	}
131
132	private static boolean isSignatureVarargsAssignable(
133		Class[] from, Class[] to )
134	{
135		return false;
136	}
137
138	/**
139		Test if a conversion of the rhsType type to the lhsType type is legal via
140	 standard Java assignment conversion rules (i.e. without a cast).
141	 The rules include Java 5 autoboxing/unboxing.
142		<p/>
143
144		For Java primitive TYPE classes this method takes primitive promotion
145		into account.  The ordinary Class.isAssignableFrom() does not take
146		primitive promotion conversions into account.  Note that Java allows
147		additional assignments without a cast in combination with variable
148		declarations and array allocations.  Those are handled elsewhere
149	 	(maybe should be here with a flag?)
150		<p/>
151		This class accepts a null rhsType type indicating that the rhsType was the
152		value Primitive.NULL and allows it to be assigned to any reference lhsType
153		type (non primitive).
154		<p/>
155
156		Note that the getAssignableForm() method is the primary bsh method for
157		checking assignability.  It adds additional bsh conversions, etc.
158
159		@see #isBshAssignable( Class, Class )
160		@param lhsType assigning from rhsType to lhsType
161		@param rhsType assigning from rhsType to lhsType
162	*/
163	static boolean isJavaAssignable( Class lhsType, Class rhsType ) {
164		return isJavaBaseAssignable( lhsType, rhsType )
165			|| isJavaBoxTypesAssignable( lhsType, rhsType );
166	}
167
168	/**
169		Is the assignment legal via original Java (up to version 1.4)
170		assignment rules, not including auto-boxing/unboxing.
171	 @param rhsType may be null to indicate primitive null value
172	*/
173	static boolean isJavaBaseAssignable( Class lhsType, Class rhsType )
174	{
175		/*
176			Assignment to loose type, defer to bsh extensions
177			Note: we could shortcut this here:
178			if ( lhsType == null ) return true;
179			rather than forcing another round.  It's not strictly a Java issue,
180			so does it belong here?
181		*/
182		if ( lhsType == null )
183			return false;
184
185		// null rhs type corresponds to type of Primitive.NULL
186		// assignable to any object type
187		if ( rhsType == null )
188			return !lhsType.isPrimitive();
189
190		if ( lhsType.isPrimitive() && rhsType.isPrimitive() )
191		{
192			if ( lhsType == rhsType )
193				return true;
194
195			// handle primitive widening conversions - JLS 5.1.2
196			if ( (rhsType == Byte.TYPE) &&
197				(lhsType == Short.TYPE || lhsType == Integer.TYPE
198				|| lhsType == Long.TYPE || lhsType == Float.TYPE
199				|| lhsType == Double.TYPE))
200                    return true;
201
202            if ( (rhsType == Short.TYPE) &&
203				(lhsType == Integer.TYPE || lhsType == Long.TYPE ||
204                lhsType == Float.TYPE || lhsType == Double.TYPE))
205                    return true;
206
207            if ((rhsType == Character.TYPE) &&
208				(lhsType == Integer.TYPE || lhsType == Long.TYPE ||
209                lhsType == Float.TYPE || lhsType == Double.TYPE))
210                    return true;
211
212            if ((rhsType == Integer.TYPE) &&
213				(lhsType == Long.TYPE || lhsType == Float.TYPE ||
214                lhsType == Double.TYPE))
215                    return true;
216
217            if ((rhsType == Long.TYPE) &&
218				(lhsType == Float.TYPE || lhsType == Double.TYPE))
219                return true;
220
221            if ((rhsType == Float.TYPE) && (lhsType == Double.TYPE))
222                return true;
223        }
224        else
225            if ( lhsType.isAssignableFrom(rhsType) )
226                return true;
227
228        return false;
229    }
230
231	/**
232		Determine if the type is assignable via Java boxing/unboxing rules.
233	*/
234	static boolean isJavaBoxTypesAssignable(
235		Class lhsType, Class rhsType )
236	{
237		// Assignment to loose type... defer to bsh extensions
238		if ( lhsType == null )
239			return false;
240
241		// prim can be boxed and assigned to Object
242		if ( lhsType == Object.class )
243			return true;
244
245		// prim numeric type can be boxed and assigned to number
246		if ( lhsType == Number.class
247			&& rhsType != Character.TYPE
248			&& rhsType != Boolean.TYPE
249		)
250			return true;
251
252		// General case prim type to wrapper or vice versa.
253		// I don't know if this is faster than a flat list of 'if's like above.
254		// wrapperMap maps both prim to wrapper and wrapper to prim types,
255		// so this test is symmetric
256		if ( Primitive.wrapperMap.get( lhsType ) == rhsType )
257			return true;
258
259		return false;
260	}
261
262	/**
263	 Test if a type can be converted to another type via BeanShell
264	 extended syntax rules (a superset of Java conversion rules).
265	 */
266	static boolean isBshAssignable( Class toType, Class fromType )
267	{
268		try {
269			return castObject(
270				toType, fromType, null/*fromValue*/,
271				ASSIGNMENT, true/*checkOnly*/
272			) == VALID_CAST;
273		} catch ( UtilEvalError e ) {
274			// This should not happen with checkOnly true
275			throw new InterpreterError("err in cast check: "+e);
276		}
277	}
278
279	/**
280		Attempt to cast an object instance to a new type if possible via
281	 BeanShell extended syntax rules.  These rules are always a superset of
282	 Java conversion rules.  If you wish to impose context sensitive
283	 conversion rules then you must test before calling this method.
284	 <p/>
285
286		This method can handle fromValue Primitive types (representing
287		primitive casts) as well as fromValue object casts requiring interface
288		generation, etc.
289
290		@param toType the class type of the cast result, which may include
291		primitive types, e.g. Byte.TYPE
292
293		@param fromValue an Object or bsh.Primitive primitive value (including
294			Primitive.NULL or Primitive.VOID )
295
296		@see #isBshAssignable( Class, Class )
297	*/
298	public static Object castObject(
299		Object fromValue, Class toType, int operation )
300		throws UtilEvalError
301	{
302		if ( fromValue == null )
303			throw new InterpreterError("null fromValue");
304
305		Class fromType =
306			fromValue instanceof Primitive ?
307				((Primitive)fromValue).getType()
308				: fromValue.getClass();
309
310		return castObject(
311			toType, fromType, fromValue, operation, false/*checkonly*/ );
312	}
313
314	/**
315	 Perform a type conversion or test if a type conversion is possible with
316	 respect to BeanShell extended rules.  These rules are always a superset of
317	 the Java language rules, so this method can also perform (but not test)
318	 any Java language assignment or cast conversion.
319	 <p/>
320
321	 This method can perform the functionality of testing if an assignment
322	 or cast is ultimately possible (with respect to BeanShell) as well as the
323	 functionality of performing the necessary conversion of a value based
324	 on the specified target type.  This combined functionality is done for
325	 expediency and could be separated later.
326	 <p/>
327
328	 Other methods such as isJavaAssignable() should be used to determine the
329	 suitability of an assignment in a fine grained or restrictive way based
330	 on context before calling this method
331	 <p/>
332
333	 A CAST is stronger than an ASSIGNMENT operation in that it will attempt to
334	 perform primtive operations that cast to a smaller type. e.g. (byte)myLong;
335	 These are used in explicit primitive casts, primitive delclarations and
336	 array declarations. I don't believe there are any object conversions which are
337	 different between  ASSIGNMENT and CAST (e.g. scripted object to interface proxy
338	 in bsh is done on assignment as well as cast).
339	 <p/>
340
341	 This method does not obey strictJava(), you must test first before
342	 using this method if you care. (See #isJavaAssignable()).
343	 <p/>
344
345		@param toType the class type of the cast result, which may include
346			primitive types, e.g. Byte.TYPE.  toType may be null to indicate a
347			loose type assignment (which matches any fromType).
348
349		@param fromType is the class type of the value to be cast including
350			java primitive TYPE classes for primitives.
351			If fromValue is (or would be) Primitive.NULL then fromType should be null.
352
353		@param fromValue an Object or bsh.Primitive primitive value (including
354			Primitive.NULL or Primitive.VOID )
355
356		@param checkOnly If checkOnly is true then fromValue must be null.
357			FromType is checked for the cast to toType...
358			If checkOnly is false then fromValue must be non-null
359			(Primitive.NULL is ok) and the actual cast is performed.
360
361		@throws UtilEvalError on invalid assignment (when operation is
362			assignment ).
363
364		@throws UtilTargetError wrapping ClassCastException on cast error
365			(when operation is cast)
366
367		@param operation is Types.CAST or Types.ASSIGNMENT
368
369		@see org.gjt.sp.jedit.bsh.Primitive.getType()
370	*/
371	/*
372		Notes: This method is currently responsible for auto-boxing/unboxing
373		conversions...  Where does that need to go?
374	*/
375	private static Object castObject(
376		Class toType, Class fromType, Object fromValue,
377		int operation, boolean checkOnly )
378		throws UtilEvalError
379	{
380		/*
381			Lots of preconditions checked here...
382			Once things are running smoothly we might comment these out
383			(That's what assertions are for).
384		*/
385		if ( checkOnly && fromValue != null )
386			throw new InterpreterError("bad cast params 1");
387		if ( !checkOnly && fromValue == null )
388			throw new InterpreterError("bad cast params 2");
389		if ( fromType == Primitive.class )
390			throw new InterpreterError("bad from Type, need to unwrap");
391		if ( fromValue == Primitive.NULL && fromType != null )
392			throw new InterpreterError("inconsistent args 1");
393		if ( fromValue == Primitive.VOID && fromType != Void.TYPE )
394			throw new InterpreterError("inconsistent args 2");
395		if ( toType == Void.TYPE )
396			throw new InterpreterError("loose toType should be null");
397		
398		// assignment to loose type, void type, or exactly same type
399		if ( toType == null || toType == fromType )
400			return checkOnly ? VALID_CAST :
401				fromValue;
402
403		// Casting to primitive type
404        if ( toType.isPrimitive() )
405		{
406			if ( fromType == Void.TYPE || fromType == null 
407				|| fromType.isPrimitive() )
408			{
409				// Both primitives, do primitive cast
410				return Primitive.castPrimitive( 
411					toType, fromType, (Primitive)fromValue, 
412					checkOnly, operation );
413			} else
414			{
415				if ( Primitive.isWrapperType( fromType ) )
416				{
417					// wrapper to primitive
418					// Convert value to Primitive and check/cast it.
419
420					//Object r = checkOnly ? VALID_CAST :
421					Class unboxedFromType = Primitive.unboxType( fromType );
422					Primitive primFromValue;
423					if ( checkOnly ) 
424						primFromValue = null; // must be null in checkOnly
425					else
426						primFromValue = (Primitive)Primitive.wrap( 
427							fromValue, unboxedFromType );
428
429					return Primitive.castPrimitive( 
430						toType, unboxedFromType, primFromValue, 
431						checkOnly, operation );
432				} else
433				{
434					// Cannot cast from arbitrary object to primitive
435					if ( checkOnly )
436						return INVALID_CAST;
437					else
438						throw castError( toType, fromType, operation );
439				}
440			}
441        }
442
443		// Else, casting to reference type
444
445		// Casting from primitive or void (to reference type)
446		if ( fromType == Void.TYPE || fromType == null
447			|| fromType.isPrimitive() )
448		{
449			// cast from primitive to wrapper type
450			if ( Primitive.isWrapperType( toType )
451				&& fromType != Void.TYPE && fromType != null )
452			{
453				// primitive to wrapper type
454				return checkOnly ? VALID_CAST :
455					Primitive.castWrapper( 
456						Primitive.unboxType(toType), 
457						((Primitive)fromValue).getValue() );
458			}
459
460			// Primitive (not null or void) to Object.class type
461			if ( toType == Object.class 
462				&& fromType != Void.TYPE && fromType != null )
463			{
464				// box it
465				return checkOnly ? VALID_CAST :
466					((Primitive)fromValue).getValue();
467			}
468
469			// Primitive to arbitrary object type. 
470			// Allow Primitive.castToType() to handle it as well as cases of 
471			// Primitive.NULL and Primitive.VOID
472			return Primitive.castPrimitive( 
473				toType, fromType, (Primitive)fromValue, checkOnly, operation );
474		}
475
476		// If type already assignable no cast necessary
477		// We do this last to allow various errors above to be caught.
478		// e.g cast Primitive.Void to Object would pass this
479		if ( toType.isAssignableFrom( fromType ) )
480			return checkOnly ? VALID_CAST : 
481				fromValue;
482
483		// Can we use the proxy mechanism to cast a bsh.This to 
484		// the correct interface?
485		if ( toType.isInterface() 
486			&& org.gjt.sp.jedit.bsh.This.class.isAssignableFrom( fromType )
487			&& Capabilities.canGenerateInterfaces() 
488		)
489			return checkOnly ? VALID_CAST : 
490				((org.gjt.sp.jedit.bsh.This)fromValue).getInterface( toType );
491
492		// Both numeric wrapper types? 
493		// Try numeric style promotion wrapper cast
494		if ( Primitive.isWrapperType( toType ) 
495			&& Primitive.isWrapperType( fromType ) 
496		)
497			return checkOnly ? VALID_CAST :
498				Primitive.castWrapper( toType, fromValue );
499		
500		if ( checkOnly )
501			return INVALID_CAST;
502		else
503			throw castError( toType, fromType , operation  );
504	}
505
506	/**
507		Return a UtilEvalError or UtilTargetError wrapping a ClassCastException
508		describing an illegal assignment or illegal cast, respectively.	
509	*/
510    static UtilEvalError castError( 
511		Class lhsType, Class rhsType, int operation   ) 
512    {
513		return castError( 
514			Reflect.normalizeClassName(lhsType),
515			Reflect.normalizeClassName(rhsType), operation  );
516    }
517
518    static UtilEvalError castError( 
519		String lhs, String rhs, int operation   ) 
520    {
521		if ( operation == ASSIGNMENT )
522			return new UtilEvalError (
523				"Can't assign " + rhs + " to "+ lhs );
524
525		Exception cce = new ClassCastException(
526			"Cannot cast " + rhs + " to " + lhs );
527		return new UtilTargetError( cce );
528    }
529
530}