org.metawidget.util.ClassUtils Maven / Gradle / Ivy
// Metawidget
//
// 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.reflect.Method;
import java.security.AccessControlException;
import java.util.regex.Pattern;
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.uppercaseFirstLetter( 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.uppercaseFirstLetter( 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 ( clazz.equals( byte.class ) ) {
return Byte.class;
}
if ( clazz.equals( short.class ) ) {
return Short.class;
}
if ( clazz.equals( int.class ) ) {
return Integer.class;
}
if ( clazz.equals( long.class ) ) {
return Long.class;
}
if ( clazz.equals( float.class ) ) {
return Float.class;
}
if ( clazz.equals( double.class ) ) {
return Double.class;
}
if ( clazz.equals( boolean.class ) ) {
return Boolean.class;
}
if ( clazz.equals( char.class ) ) {
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;
}
}
/**
* Return the unproxied version of a class proxied by, say, CGLIB or Javassist.
*/
public static Class getUnproxiedClass( Class clazz ) {
return getUnproxiedClass( clazz, PROXY_PATTERN );
}
/**
* Return the unproxied version of a class proxied by, say, CGLIB or Javassist.
*
* Unproxying a class back to its original type is desirable so that
* BaseObjectInspector
-based and BaseXmlInspector
-based inspectors can
* merge to a common type.
*
* However, unproxying may not always be possible. If the proxied class is not an extension of
* some base class but simply a java.lang.Object
that implements one or more
* interfaces, we cannot know which interface is the 'right' one to return. In that class,
* getUnproxiedClass
just returns the original (proxied) class.
*/
public static Class getUnproxiedClass( Class clazz, Pattern proxyPattern ) {
if ( proxyPattern == null || !proxyPattern.matcher( clazz.getName() ).find() ) {
return clazz;
}
Class superclass = clazz.getSuperclass();
// If the proxied class is not an extension of some base class but simply a java.lang.Object
// that implements one or more interfaces, we cannot know which interface is the 'right' one
// to return. In that class, just return the original (proxied) class
if ( Object.class.equals( superclass ) ) {
return clazz;
}
return superclass;
}
/**
* 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')
*
*
* This method tries to load the class using 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.
*/
public static Class niceForName( String className ) {
// Try Thread.currentThread().getContextClassLoader(), in case metawidget.jar
// is in JRE/lib/ext (which it might be for desktop apps)
return niceForName( className, Thread.currentThread().getContextClassLoader() );
}
/**
* 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')
*
*
* @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
* @throws NullPointerException
* if className is null
*/
public static Class niceForName( String className, ClassLoader classLoader ) {
try {
if ( classLoader != null ) {
return Class.forName( className, false, classLoader );
}
} catch ( ClassNotFoundException e ) {
// Fall through and try other ClassLoader
}
try {
// There may be no contextClassLoader (eg. Android)
return Class.forName( className, false, ClassUtils.class.getClassLoader() );
} catch ( ClassNotFoundException e ) {
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;
}
}
/**
* Gets the 'simple' name of the class.
*
* Essentially a simplified version of Class.getSimpleName
, which is JDK
* 1.5-specific.
*/
public static String getSimpleName( Class clazz ) {
String className = clazz.getName();
int lastIndexOf = className.lastIndexOf( StringUtils.SEPARATOR_DOT_CHAR );
if ( lastIndexOf != -1 ) {
className = className.substring( lastIndexOf + 1 );
}
return className;
}
/**
* 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;
}
//
// Private statics
//
private static final Pattern PROXY_PATTERN = Pattern.compile( "ByCGLIB\\$\\$|_\\$\\$_javassist_" );
//
// Private constructor
//
private ClassUtils() {
// Can never be called
}
}