All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.metawidget.util.ClassUtils Maven / Gradle / Ivy

There is a newer version: 4.2
Show newest version
// Metawidget (licensed under LGPL)
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package org.metawidget.util;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.security.AccessControlException;
import java.util.Map;
import java.util.Set;

import org.metawidget.util.simple.StringUtils;

/**
 * Utilities for working with Classes.
 *
 * @author Richard Kennard
 */

public final class ClassUtils {

	//
	// Public statics
	//

	public static final String	JAVABEAN_SET_PREFIX	= "set";

	public static final String	JAVABEAN_GET_PREFIX	= "get";

	public static final String	JAVABEAN_IS_PREFIX	= "is";

	/**
	 * Lookup JavaBean-convention getter without using java.beans, as that package is
	 * not available on all target platforms.
	 */

	public static Method getReadMethod( Class clazz, String property ) {

		String propertyUppercased = StringUtils.capitalize( property );

		try {
			return clazz.getMethod( JAVABEAN_GET_PREFIX + propertyUppercased );
		} catch ( Exception e1 ) {
			try {
				Method method = clazz.getMethod( JAVABEAN_IS_PREFIX + propertyUppercased );

				// As per section 8.3.2 (Boolean properties) of The JavaBeans API specification,
				// 'is' only applies to boolean (little 'b')

				if ( boolean.class.equals( method.getReturnType() ) ) {
					return method;
				}

			} catch ( Exception e2 ) {
				// Fall through
			}
		}

		throw new RuntimeException( "No such method " + JAVABEAN_GET_PREFIX + propertyUppercased + " (or boolean " + JAVABEAN_IS_PREFIX + propertyUppercased + ") on " + clazz );
	}

	/**
	 * Lookup JavaBean-convention setter without using java.beans, as that package is
	 * not available on all target platforms.
	 */

	public static Method getWriteMethod( Class clazz, String property, Class type ) {

		String propertyUppercased = StringUtils.capitalize( property );

		// First, try and match based on subtypes of the property type

		Class typeSuper = type;

		while ( typeSuper != null ) {
			try {
				return clazz.getMethod( JAVABEAN_SET_PREFIX + propertyUppercased, typeSuper );
			} catch ( Exception e ) {
				typeSuper = typeSuper.getSuperclass();
			}
		}

		// Next, try and match based on interfaces of the property type

		for ( Class anInterface : type.getInterfaces() ) {
			try {
				return clazz.getMethod( JAVABEAN_SET_PREFIX + propertyUppercased, anInterface );
			} catch ( Exception e ) {
				// Keep trying
			}
		}

		throw new RuntimeException( "No such method " + JAVABEAN_SET_PREFIX + propertyUppercased + "( " + type.getName() + " ) on " + clazz );
	}

	/**
	 * Get the value of the JavaBean-convention property without using java.beans, as
	 * that package is not available on all target platforms.
	 */

	@SuppressWarnings( "unchecked" )
	public static  T getProperty( Object base, String property ) {

		try {
			Method method = getReadMethod( base.getClass(), property );
			return (T) method.invoke( base );
		} catch ( Exception e ) {
			if ( base == null ) {
				throw new RuntimeException( "Unable to get '" + property + "' because base is null", e );
			}

			throw new RuntimeException( "Unable to get '" + property + "' of '" + base + "' (" + base.getClass() + ")", e );
		}
	}

	/**
	 * Set the value of the JavaBean-convention property without using java.beans, as
	 * that package is not available on all target platforms.
	 */

	public static void setProperty( Object base, String property, Object value ) {

		try {
			Class baseClass = base.getClass();

			// Determine the type based on the 'read' method, not the value.getClass(), because
			// that is unreliable for 'Integer' versus 'int'

			Method method = getReadMethod( baseClass, property );
			method = getWriteMethod( baseClass, property, method.getReturnType() );
			method.invoke( base, value );
		} catch ( Exception e ) {
			throw new RuntimeException( "Unable to set '" + property + "' of '" + base + "' to '" + value + "'", e );
		}
	}

	/**
	 * Returns true if the given class is the Object-wrapper of a primitive type (eg.
	 * Integer for int).
	 * 

* We want to be able to distinguish these, because we can't usefully drill into them and may do * better to 'inspect from parent' (see UIMetawidget). */ public static boolean isPrimitiveWrapper( Class clazz ) { if ( Number.class.isAssignableFrom( clazz ) ) { return true; } if ( Boolean.class.isAssignableFrom( clazz ) ) { return true; } if ( Character.class.isAssignableFrom( clazz ) ) { return true; } return false; } /** * Returns the wrapper class for a primitive class (eg. Integer.class for * int.class) */ public static Class getWrapperClass( Class clazz ) { if ( byte.class.equals( clazz ) ) { return Byte.class; } if ( short.class.equals( clazz ) ) { return Short.class; } if ( int.class.equals( clazz ) ) { return Integer.class; } if ( long.class.equals( clazz ) ) { return Long.class; } if ( float.class.equals( clazz ) ) { return Float.class; } if ( double.class.equals( clazz ) ) { return Double.class; } if ( boolean.class.equals( clazz ) ) { return Boolean.class; } if ( char.class.equals( clazz ) ) { return Character.class; } throw new RuntimeException( clazz + " is not a primitive type" ); } /** * Gracefully test whether a class exists. Returns true or false, rather than throwing * ClassNotFoundException. */ public static boolean classExists( String clazz ) { try { Class.forName( clazz, false, null ); return true; } catch ( ClassNotFoundException e ) { return false; } catch ( AccessControlException e ) { // Not accessible (eg. running in an applet) return false; } } public static Object parseNumber( Class clazz, String value ) { if ( value == null || "".equals( value ) ) { return null; } if ( byte.class.equals( clazz ) ) { return Byte.parseByte( value ); } if ( Byte.class.equals( clazz ) ) { return Byte.valueOf( value ); } if ( short.class.equals( clazz ) ) { return Short.parseShort( value ); } if ( Short.class.equals( clazz ) ) { return Short.valueOf( value ); } if ( int.class.equals( clazz ) ) { return Integer.parseInt( value ); } if ( Integer.class.equals( clazz ) ) { return Integer.valueOf( value ); } if ( long.class.equals( clazz ) ) { return Long.parseLong( value ); } if ( Long.class.equals( clazz ) ) { return Long.valueOf( value ); } if ( float.class.equals( clazz ) ) { return Float.parseFloat( value ); } if ( Float.class.equals( clazz ) ) { return Float.valueOf( value ); } if ( double.class.equals( clazz ) ) { return Double.parseDouble( value ); } if ( Double.class.equals( clazz ) ) { return Double.valueOf( value ); } throw new RuntimeException( clazz + " is not a number type" ); } public static Object getNumberMinValue( Class clazz ) { if ( byte.class.equals( clazz ) || Byte.class.equals( clazz ) ) { return Byte.MIN_VALUE; } if ( short.class.equals( clazz ) || Short.class.equals( clazz ) ) { return Short.MIN_VALUE; } if ( int.class.equals( clazz ) || Integer.class.equals( clazz ) ) { return Integer.MIN_VALUE; } if ( int.class.equals( clazz ) || Integer.class.equals( clazz ) ) { return Integer.MIN_VALUE; } if ( long.class.equals( clazz ) || Long.class.equals( clazz ) ) { return Long.MIN_VALUE; } if ( float.class.equals( clazz ) || Float.class.equals( clazz ) ) { return -Float.MAX_VALUE; } if ( double.class.equals( clazz ) || Double.class.equals( clazz ) ) { return -Double.MAX_VALUE; } throw new RuntimeException( clazz + " is not a number type" ); } public static Object getNumberMaxValue( Class clazz ) { if ( byte.class.equals( clazz ) || Byte.class.equals( clazz ) ) { return Byte.MAX_VALUE; } if ( short.class.equals( clazz ) || Short.class.equals( clazz ) ) { return Short.MAX_VALUE; } if ( int.class.equals( clazz ) || Integer.class.equals( clazz ) ) { return Integer.MAX_VALUE; } if ( int.class.equals( clazz ) || Integer.class.equals( clazz ) ) { return Integer.MAX_VALUE; } if ( long.class.equals( clazz ) || Long.class.equals( clazz ) ) { return Long.MAX_VALUE; } if ( float.class.equals( clazz ) || Float.class.equals( clazz ) ) { return Float.MAX_VALUE; } if ( double.class.equals( clazz ) || Double.class.equals( clazz ) ) { return Double.MAX_VALUE; } throw new RuntimeException( clazz + " is not a number type" ); } /** * When dealing with multiple isolated ClassLoaders, sometimes the object being inspected may * reference a class that is not available to any of our own ClassLoaders. Therefore * Class.forName will always fail. To cope with this, we * record 'alien' ClassLoaders as and when we encounter them. *

* This is a static Set, rather than a ThreadLocal, because we couldn't find a good place to * reset the ThreadLocal. */ /* package private */static final Set ALIEN_CLASSLOADERS = CollectionUtils.newHashSet(); /** * When dealing with multiple isolated ClassLoaders, sometimes the object being inspected may * reference a class that is not available to any of our own ClassLoaders. Therefore * Class.forName will always fail. To cope with this, we record 'alien' * ClassLoaders as and when we encounter them */ public static void registerAlienClassLoader( ClassLoader classLoader ) { // JDK classes like java.util.ArrayList have no classloader if ( classLoader == null ) { return; } // No need to register, as not alien? if ( classLoader.equals( Thread.currentThread().getContextClassLoader() ) ) { return; } if ( classLoader.equals( ClassUtils.class.getClassLoader() ) ) { return; } synchronized ( ALIEN_CLASSLOADERS ) { ALIEN_CLASSLOADERS.add( classLoader ); } } /** * Replacement for Class.forName() that: *

    *
  • supports primitives (int, long, etc)
  • *
  • returns null if there is no such class (eg. if the name is a symbolic type, * such as 'Login Screen')
  • *
*/ public static Class niceForName( String className ) { return niceForName( className, null ); } /** * Replacement for Class.forName() that: *
    *
  • returns null if there is no such class (eg. if the name is a symbolic type, * such as 'Login Screen')
  • *
  • supports primitives (int, long, etc)
  • *
  • supports parameterized types (strips off the <...>)
  • *
*

* This method first tries to load the class using the given ClassLoader (if any). If that * fails, it then tries the Thread's current ClassLoader (this works best for EJB/WAR splits * where, say, metawidget-core and metawidget-annotations are located in the EJB/lib and the * other modules are located in the WAR/lib). If that fails, it tries ClassUtils' ClassLoader. * If that fails, it tries our alien ClassLoader. * * @param classLoader * the specific ClassLoader to use to try and load this class. In general clients * should use the other form of this method, which will default to trying the current * Thread's ClassLoader. Can be null. * @throws NullPointerException * if className is null */ public static Class niceForName( String className, ClassLoader classLoader ) { String classNameToUse = className; // Support parameterized type int indexOf = classNameToUse.indexOf( '<' ); if ( indexOf != -1 ) { classNameToUse = classNameToUse.substring( 0, indexOf ); } // Try given ClassLoader (may be none) try { if ( classLoader != null ) { return Class.forName( classNameToUse, false, classLoader ); } } catch ( ClassNotFoundException e ) { // Fall through and try other ClassLoaders } // Try given Thread ClassLoader (may be none, such as on Android) ClassLoader threadClassLoader = Thread.currentThread().getContextClassLoader(); try { if ( threadClassLoader != null && !threadClassLoader.equals( classLoader ) ) { return Class.forName( classNameToUse, false, threadClassLoader ); } } catch ( ClassNotFoundException e ) { // Fall through and try other ClassLoaders } // Try our own ClassLoader (if different to threadClassLoader) ClassLoader thisClassLoader = ClassUtils.class.getClassLoader(); try { if ( !thisClassLoader.equals( threadClassLoader ) && !thisClassLoader.equals( classLoader ) ) { return Class.forName( classNameToUse, false, thisClassLoader ); } } catch ( ClassNotFoundException e ) { // Fall through and try other ClassLoaders } // Try our alien ClassLoaders synchronized ( ALIEN_CLASSLOADERS ) { for ( ClassLoader alienClassLoader : ALIEN_CLASSLOADERS ) { try { return Class.forName( classNameToUse, false, alienClassLoader ); } catch ( ClassNotFoundException e ) { // Fall through and try other ClassLoaders } } } return getPrimitive( classNameToUse ); } public static boolean isPrimitive( String className ) { return ( getPrimitive( className ) != null ); } /** * Gets the 'simple' name of the class, given the fully qualified name. *

* Named after Class.getSimpleName. */ public static String getSimpleName( String qualifiedClassName ) { return StringUtils.substringAfterLast( qualifiedClassName, StringUtils.SEPARATOR_DOT_CHAR ); } /** * Similar to Method.getDeclaringClass, but traverses the class heirarchy to find * the class that originally declared this method, in the event the implementation has * been overridden. */ public static Class getOriginalDeclaringClass( Method method ) { Class declaringClass = method.getDeclaringClass(); Class superclass = declaringClass.getSuperclass(); while ( superclass != null ) { try { superclass.getDeclaredMethod( method.getName(), method.getParameterTypes() ); declaringClass = superclass; } catch ( Exception e ) { // Not in this superclass, but may be in super-superclass } superclass = superclass.getSuperclass(); } return declaringClass; } /** * Converts com.foo.Bar into /com/foo. The leading forward slash can * be important in some module loading environments (e.g. for JBoss Forge). */ public static String getPackagesAsFolderNames( Class clazz ) { return StringUtils.SEPARATOR_FORWARD_SLASH_CHAR + clazz.getPackage().getName().replace( StringUtils.SEPARATOR_DOT_CHAR, StringUtils.SEPARATOR_FORWARD_SLASH_CHAR ); } /** * Gets the given annotationClass defined on the given method. If no annotations are * defined at all (not just annotationClass) but the method is overridden, searches up the class * heirarchy to original versions of the method and checks for annotationClass. *

* We work off no annotations being defined (rather than just annotationClass) because * we still want to support explicitly overriding methods in order to suppress annotations (such * as UiHidden). This isn't perfect, because the overridden method still has to * define at least one annotation. *

* This approach is important for proxied classes, which don't always retain annotations. */ public static T getOriginalAnnotation( Method method, Class annotationClass ) { Map, Annotation> cache = ORIGINAL_ANNOTATION_CACHE.get( method ); if ( cache == null ) { cache = CollectionUtils.newWeakHashMap(); ORIGINAL_ANNOTATION_CACHE.put( method, cache ); } // Must synchronize, as WeakHashMap is not Thread safe and WeakHashMap:383 contains an // infinite while: while (e != null && !(e.hash == h && eq(k, e.get()))) synchronized ( cache ) { // Use 'containsKey', because we may cache it as being 'null' if ( cache.containsKey( annotationClass ) ) { @SuppressWarnings( "unchecked" ) T annotation = (T) cache.get( annotationClass ); return annotation; } T annotation = internalGetOriginalAnnotation( method, annotationClass ); cache.put( annotationClass, annotation ); return annotation; } } /** * Gets the given genericReturnType defined on the given method. If no such genericReturnType is * defined but the method is overridden, searches up the class heirarchy to original versions of * the method, and checks for genericReturnType. *

* This approach is important for proxied classes, which don't always retain genetics. */ public static Type getOriginalGenericReturnType( Method method ) { Method methodToUse = method; String name = methodToUse.getName(); Class[] parameterTypes = methodToUse.getParameterTypes(); do { // If this method has a ParameterizedType, return it... Type type = methodToUse.getGenericReturnType(); if ( type instanceof ParameterizedType ) { return type; } // ...otherwise traverse up the hierarchy Class superclass = methodToUse.getDeclaringClass().getSuperclass(); methodToUse = null; while ( superclass != null ) { try { methodToUse = superclass.getDeclaredMethod( name, parameterTypes ); break; } catch ( Exception e ) { // Not in this superclass, but may be in super-superclass } superclass = superclass.getSuperclass(); } } while ( methodToUse != null ); // No generic return type found. Return normal return method.getGenericReturnType(); } /** * Gets the given genericParameterTypes defined on the given method. If no such * genericParameterTypes is defined but the method is overridden, searches up the class * heirarchy to original versions of the method, and checks for genericParameterTypes. *

* This approach is important for proxied classes, which don't always retain genetics. */ public static Type[] getOriginalGenericParameterTypes( Method method ) { Method methodToUse = method; String name = methodToUse.getName(); Class[] parameterTypes = methodToUse.getParameterTypes(); do { // If this method has a ParameterizedType, return it... Type[] type = methodToUse.getGenericParameterTypes(); if ( type[0] instanceof ParameterizedType ) { return type; } // ...otherwise traverse up the hierarchy Class superclass = methodToUse.getDeclaringClass().getSuperclass(); methodToUse = null; while ( superclass != null ) { try { methodToUse = superclass.getDeclaredMethod( name, parameterTypes ); break; } catch ( Exception e ) { // Not in this superclass, but may be in super-superclass } superclass = superclass.getSuperclass(); } } while ( methodToUse != null ); // No generic parameter types found. Return normal return method.getGenericParameterTypes(); } /** * Converts a java.lang.reflect.Type, as returned by getGenericType * into a String representation. */ public static String getGenericTypeAsString( Type type ) { if ( !( type instanceof ParameterizedType ) ) { return null; } Type[] typeActuals = null; try { typeActuals = ( (ParameterizedType) type ).getActualTypeArguments(); } catch ( Exception e ) { // Android 1.1_r1 fails here with a ClassNotFoundException } if ( typeActuals == null || typeActuals.length == 0 ) { return null; } StringBuilder builder = new StringBuilder(); for ( Type typeActual : typeActuals ) { // Android 1.1_r1 sometimes provides null typeActuals while // testing the AddressBook application if ( typeActual == null ) { continue; } if ( builder.length() > 0 ) { builder.append( StringUtils.SEPARATOR_COMMA ); } if ( typeActual instanceof Class ) { builder.append( ( (Class) typeActual ).getName() ); } else { builder.append( typeActual.toString() ); } } return builder.toString(); } // // Private statics // private static final Map, Annotation>> ORIGINAL_ANNOTATION_CACHE = CollectionUtils.newHashMap(); /** * We found getOriginalAnnotation to be around 10x slower that just * method.getAnnotation, so we cache it. */ private static T internalGetOriginalAnnotation( Method method, Class annotationClass ) { Method methodToUse = method; String name = methodToUse.getName(); Class[] parameterTypes = methodToUse.getParameterTypes(); // If no annotations are defined at all, traverse up the hierarchy while ( methodToUse.getAnnotations().length == 0 ) { Class superclass = methodToUse.getDeclaringClass().getSuperclass(); methodToUse = null; while ( superclass != null ) { try { methodToUse = superclass.getDeclaredMethod( name, parameterTypes ); break; } catch ( Exception e ) { // Not in this superclass, but may be in super-superclass } superclass = superclass.getSuperclass(); } if ( methodToUse == null ) { break; } } // If this method has the annotation, return it if ( methodToUse != null ) { T annotation = methodToUse.getAnnotation( annotationClass ); if ( annotation != null ) { return annotation; } } // Try interfaces too, in case annotation is defined there for ( Class iface : method.getDeclaringClass().getInterfaces() ) { try { methodToUse = iface.getDeclaredMethod( name, parameterTypes ); T annotation = methodToUse.getAnnotation( annotationClass ); if ( annotation != null ) { return annotation; } } catch ( Exception e ) { // Not in this interface } } // No annotation found return null; } private static Class getPrimitive( String className ) { if ( "byte".equals( className ) ) { return byte.class; } if ( "short".equals( className ) ) { return short.class; } if ( "int".equals( className ) ) { return int.class; } if ( "long".equals( className ) ) { return long.class; } if ( "float".equals( className ) ) { return float.class; } if ( "double".equals( className ) ) { return double.class; } if ( "boolean".equals( className ) ) { return boolean.class; } if ( "char".equals( className ) ) { return char.class; } return null; } // // Private constructor // private ClassUtils() { // Can never be called } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy