blob: 85fc2067d835f29d4df9a063a2bcc65cd5ce03fb [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.directory.shared.ldap.util;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* <p>
* Operates on classes without using reflection.
* </p>
* <p>
* This class handles invalid <code>null</code> inputs as best it can. Each
* method documents its behaviour in more detail.
* </p>
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
public class ClassUtils
{
private static final String EMPTY = "";
/**
* <p>
* The package separator character: <code>'&#x2e;' == .</code>.
* </p>
*/
public static final char PACKAGE_SEPARATOR_CHAR = '.';
/**
* <p>
* The package separator String: <code>"&#x2e;"</code>.
* </p>
*/
public static final String PACKAGE_SEPARATOR = String.valueOf( PACKAGE_SEPARATOR_CHAR );
/**
* <p>
* The inner class separator character: <code>'$' == $</code>.
* </p>
*/
public static final char INNER_CLASS_SEPARATOR_CHAR = '$';
/**
* <p>
* The inner class separator String: <code>"$"</code>.
* </p>
*/
public static final String INNER_CLASS_SEPARATOR = String.valueOf( INNER_CLASS_SEPARATOR_CHAR );
/**
* Maps primitive <code>Class</code>es to their corresponding wrapper
* <code>Class</code>.
*/
private static Map<Class<?>,Class<?>> primitiveWrapperMap = new HashMap<Class<?>,Class<?>>();
static
{
primitiveWrapperMap.put( Boolean.TYPE, Boolean.class );
primitiveWrapperMap.put( Byte.TYPE, Byte.class );
primitiveWrapperMap.put( Character.TYPE, Character.class );
primitiveWrapperMap.put( Short.TYPE, Short.class );
primitiveWrapperMap.put( Integer.TYPE, Integer.class );
primitiveWrapperMap.put( Long.TYPE, Long.class );
primitiveWrapperMap.put( Double.TYPE, Double.class );
primitiveWrapperMap.put( Float.TYPE, Float.class );
}
/**
* <p>
* ClassUtils instances should NOT be constructed in standard programming.
* Instead, the class should be used as
* <code>ClassUtils.getShortClassName(cls)</code>.
* </p>
* <p>
* This constructor is public to permit tools that require a JavaBean
* instance to operate.
* </p>
*/
public ClassUtils()
{
}
// Short class name
// ----------------------------------------------------------------------
/**
* <p>
* Gets the class name minus the package name for an <code>Object</code>.
* </p>
*
* @param object
* the class to get the short name for, may be null
* @param valueIfNull
* the value to return if null
* @return the class name of the object without the package name, or the
* null value
*/
public static String getShortClassName( Object object, String valueIfNull )
{
if ( object == null )
{
return valueIfNull;
}
return getShortClassName( object.getClass().getName() );
}
/**
* <p>
* Gets the class name minus the package name from a <code>Class</code>.
* </p>
*
* @param cls
* the class to get the short name for.
* @return the class name without the package name or an empty string
*/
public static String getShortClassName( Class<?> cls )
{
if ( cls == null )
{
return EMPTY;
}
return getShortClassName( cls.getName() );
}
/**
* <p>
* Gets the class name minus the package name from a String.
* </p>
* <p>
* The string passed in is assumed to be a class name - it is not checked.
* </p>
*
* @param className
* the className to get the short name for
* @return the class name of the class without the package name or an empty
* string
*/
public static String getShortClassName( String className )
{
if ( className == null )
{
return EMPTY;
}
if ( className.length() == 0 )
{
return EMPTY;
}
char[] chars = className.toCharArray();
int lastDot = 0;
for ( int i = 0; i < chars.length; i++ )
{
if ( chars[i] == PACKAGE_SEPARATOR_CHAR )
{
lastDot = i + 1;
}
else if ( chars[i] == INNER_CLASS_SEPARATOR_CHAR )
{ // handle inner classes
chars[i] = PACKAGE_SEPARATOR_CHAR;
}
}
return new String( chars, lastDot, chars.length - lastDot );
}
// Package name
// ----------------------------------------------------------------------
/**
* <p>
* Gets the package name of an <code>Object</code>.
* </p>
*
* @param object
* the class to get the package name for, may be null
* @param valueIfNull
* the value to return if null
* @return the package name of the object, or the null value
*/
public static String getPackageName( Object object, String valueIfNull )
{
if ( object == null )
{
return valueIfNull;
}
return getPackageName( object.getClass().getName() );
}
/**
* <p>
* Gets the package name of a <code>Class</code>.
* </p>
*
* @param cls
* the class to get the package name for, may be
* <code>null</code>.
* @return the package name or an empty string
*/
public static String getPackageName( Class<?> cls )
{
if ( cls == null )
{
return EMPTY;
}
return getPackageName( cls.getName() );
}
/**
* <p>
* Gets the package name from a <code>String</code>.
* </p>
* <p>
* The string passed in is assumed to be a class name - it is not checked.
* </p>
* <p>
* If the class is unpackaged, return an empty string.
* </p>
*
* @param className
* the className to get the package name for, may be
* <code>null</code>
* @return the package name or an empty string
*/
public static String getPackageName( String className )
{
if ( className == null )
{
return EMPTY;
}
int i = className.lastIndexOf( PACKAGE_SEPARATOR_CHAR );
if ( i == -1 )
{
return EMPTY;
}
return className.substring( 0, i );
}
// Superclasses/Superinterfaces
// ----------------------------------------------------------------------
/**
* <p>
* Gets a <code>List</code> of superclasses for the given class.
* </p>
*
* @param cls
* the class to look up, may be <code>null</code>
* @return the <code>List</code> of superclasses in order going up from
* this one <code>null</code> if null input
*/
public static List<Class<?>> getAllSuperclasses( Class<?> cls )
{
if ( cls == null )
{
return null;
}
List<Class<?>> classes = new ArrayList<Class<?>>();
Class<?> superclass = cls.getSuperclass();
while ( superclass != null )
{
classes.add( superclass );
superclass = superclass.getSuperclass();
}
return classes;
}
/**
* <p>
* Gets a <code>List</code> of all interfaces implemented by the given
* class and its superclasses.
* </p>
* <p>
* The order is determined by looking through each interface in turn as
* declared in the source file and following its hierarchy up. Then each
* superclass is considered in the same way. Later duplicates are ignored,
* so the order is maintained.
* </p>
*
* @param cls
* the class to look up, may be <code>null</code>
* @return the <code>List</code> of interfaces in order, <code>null</code>
* if null input
*/
public static List<Class<?>> getAllInterfaces( Class<?> cls )
{
if ( cls == null )
{
return null;
}
List<Class<?>> list = new ArrayList<Class<?>>();
while ( cls != null )
{
Class<?>[] interfaces = cls.getInterfaces();
for ( Class<?> interf:interfaces )
{
if ( list.contains( interf ) == false )
{
list.add( interf );
}
for ( Class<?> superIntf:getAllInterfaces( interf ) )
{
if ( list.contains( superIntf ) == false )
{
list.add( superIntf );
}
}
}
cls = cls.getSuperclass();
}
return list;
}
// Convert list
// ----------------------------------------------------------------------
/**
* <p>
* Given a <code>List</code> of class names, this method converts them
* into classes.
* </p>
* <p>
* A new <code>List</code> is returned. If the class name cannot be found,
* <code>null</code> is stored in the <code>List</code>. If the class
* name in the <code>List</code> is <code>null</code>,
* <code>null</code> is stored in the output <code>List</code>.
* </p>
*
* @param classNames
* the classNames to change
* @return a <code>List</code> of Class objects corresponding to the class
* names, <code>null</code> if null input
* @throws ClassCastException
* if classNames contains a non String entry
*/
public static List<Class<?>> convertClassNamesToClasses( List<String> classNames )
{
if ( classNames == null )
{
return null;
}
List<Class<?>> classes = new ArrayList<Class<?>>( classNames.size() );
for ( String className:classNames )
{
try
{
classes.add( Class.forName( className ) );
}
catch ( Exception ex )
{
classes.add( null );
}
}
return classes;
}
/**
* <p>
* Given a <code>List</code> of <code>Class</code> objects, this method
* converts them into class names.
* </p>
* <p>
* A new <code>List</code> is returned. <code>null</code> objects will
* be copied into the returned list as <code>null</code>.
* </p>
*
* @param classes
* the classes to change
* @return a <code>List</code> of class names corresponding to the Class
* objects, <code>null</code> if null input
* @throws ClassCastException
* if <code>classes</code> contains a non-<code>Class</code>
* entry
*/
public static List<String> convertClassesToClassNames( List<Class<?>> classes )
{
if ( classes == null )
{
return null;
}
List<String> classNames = new ArrayList<String>( classes.size() );
for ( Class<?> clazz:classes )
{
if ( clazz == null )
{
classNames.add( null );
}
else
{
classNames.add( clazz.getName() );
}
}
return classNames;
}
// Is assignable
// ----------------------------------------------------------------------
/**
* <p>
* Checks if an array of Classes can be assigned to another array of
* Classes.
* </p>
* <p>
* This method calls {@link #isAssignable(Class, Class) isAssignable} for
* each Class pair in the input arrays. It can be used to check if a set of
* arguments (the first parameter) are suitably compatible with a set of
* method parameter types (the second parameter).
* </p>
* <p>
* Unlike the {@link Class#isAssignableFrom(java.lang.Class)} method, this
* method takes into account widenings of primitive classes and
* <code>null</code>s.
* </p>
* <p>
* Primitive widenings allow an int to be assigned to a <code>long</code>,
* <code>float</code> or <code>double</code>. This method returns the
* correct result for these cases.
* </p>
* <p>
* <code>Null</code> may be assigned to any reference type. This method
* will return <code>true</code> if <code>null</code> is passed in and
* the toClass is non-primitive.
* </p>
* <p>
* Specifically, this method tests whether the type represented by the
* specified <code>Class</code> parameter can be converted to the type
* represented by this <code>Class</code> object via an identity
* conversion widening primitive or widening reference conversion. See
* <em><a href="http://java.sun.com/docs/books/jls/">The Java Language Specification</a></em>,
* sections 5.1.1, 5.1.2 and 5.1.4 for details.
* </p>
*
* @param classArray
* the array of Classes to check, may be <code>null</code>
* @param toClassArray
* the array of Classes to try to assign into, may be
* <code>null</code>
* @return <code>true</code> if assignment possible
*/
public static boolean isAssignable( Class<?>[] classArray, Class<?>[] toClassArray )
{
if ( ArrayUtils.isSameLength( classArray, toClassArray ) == false )
{
return false;
}
if ( classArray == null )
{
classArray = ArrayUtils.EMPTY_CLASS_ARRAY;
}
if ( toClassArray == null )
{
toClassArray = ArrayUtils.EMPTY_CLASS_ARRAY;
}
for ( int i = 0; i < classArray.length; i++ )
{
if ( isAssignable( classArray[i], toClassArray[i] ) == false )
{
return false;
}
}
return true;
}
/**
* <p>
* Checks if one <code>Class</code> can be assigned to a variable of
* another <code>Class</code>.
* </p>
* <p>
* Unlike the {@link Class#isAssignableFrom(java.lang.Class)} method, this
* method takes into account widenings of primitive classes and
* <code>null</code>s.
* </p>
* <p>
* Primitive widenings allow an int to be assigned to a long, float or
* double. This method returns the correct result for these cases.
* </p>
* <p>
* <code>Null</code> may be assigned to any reference type. This method
* will return <code>true</code> if <code>null</code> is passed in and
* the toClass is non-primitive.
* </p>
* <p>
* Specifically, this method tests whether the type represented by the
* specified <code>Class</code> parameter can be converted to the type
* represented by this <code>Class</code> object via an identity
* conversion widening primitive or widening reference conversion. See
* <em><a href="http://java.sun.com/docs/books/jls/">The Java Language Specification</a></em>,
* sections 5.1.1, 5.1.2 and 5.1.4 for details.
* </p>
*
* @param cls
* the Class to check, may be null
* @param toClass
* the Class to try to assign into, returns false if null
* @return <code>true</code> if assignment possible
*/
public static boolean isAssignable( Class<?> cls, Class<?> toClass )
{
if ( toClass == null )
{
return false;
}
// have to check for null, as isAssignableFrom doesn't
if ( cls == null )
{
return !( toClass.isPrimitive() );
}
if ( cls.equals( toClass ) )
{
return true;
}
if ( cls.isPrimitive() )
{
if ( toClass.isPrimitive() == false )
{
return false;
}
if ( Integer.TYPE.equals( cls ) )
{
return Long.TYPE.equals( toClass ) || Float.TYPE.equals( toClass ) || Double.TYPE.equals( toClass );
}
if ( Long.TYPE.equals( cls ) )
{
return Float.TYPE.equals( toClass ) || Double.TYPE.equals( toClass );
}
if ( Boolean.TYPE.equals( cls ) )
{
return false;
}
if ( Double.TYPE.equals( cls ) )
{
return false;
}
if ( Float.TYPE.equals( cls ) )
{
return Double.TYPE.equals( toClass );
}
if ( Character.TYPE.equals( cls ) )
{
return Integer.TYPE.equals( toClass ) || Long.TYPE.equals( toClass ) || Float.TYPE.equals( toClass )
|| Double.TYPE.equals( toClass );
}
if ( Short.TYPE.equals( cls ) )
{
return Integer.TYPE.equals( toClass ) || Long.TYPE.equals( toClass ) || Float.TYPE.equals( toClass )
|| Double.TYPE.equals( toClass );
}
if ( Byte.TYPE.equals( cls ) )
{
return Short.TYPE.equals( toClass ) || Integer.TYPE.equals( toClass ) || Long.TYPE.equals( toClass )
|| Float.TYPE.equals( toClass ) || Double.TYPE.equals( toClass );
}
// should never get here
return false;
}
return toClass.isAssignableFrom( cls );
}
/**
* <p>
* Converts the specified primitive Class object to its corresponding
* wrapper Class object.
* </p>
*
* @param cls
* the class to convert, may be null
* @return the wrapper class for <code>cls</code> or <code>cls</code> if
* <code>cls</code> is not a primitive. <code>null</code> if
* null input.
*/
public static Class<?> primitiveToWrapper( Class<?> cls )
{
Class<?> convertedClass = cls;
if ( cls != null && cls.isPrimitive() )
{
convertedClass = primitiveWrapperMap.get( cls );
}
return convertedClass;
}
/**
* <p>
* Converts the specified array of primitive Class objects to an array of
* its corresponding wrapper Class objects.
* </p>
*
* @param classes
* the class array to convert, may be null or empty
* @return an array which contains for each given class, the wrapper class
* or the original class if class is not a primitive.
* <code>null</code> if null input. Empty array if an empty array
* passed in.
*/
public static Class<?>[] primitivesToWrappers( Class<?>[] classes )
{
if ( classes == null )
{
return null;
}
if ( classes.length == 0 )
{
return ArrayUtils.EMPTY_CLASS_ARRAY;
}
Class<?>[] convertedClasses = new Class[classes.length];
for ( int i = 0; i < classes.length; i++ )
{
convertedClasses[i] = primitiveToWrapper( classes[i] );
}
return convertedClasses;
}
// Inner class
// ----------------------------------------------------------------------
/**
* <p>
* Is the specified class an inner class or static nested class.
* </p>
*
* @param cls
* the class to check, may be null
* @return <code>true</code> if the class is an inner or static nested
* class, false if not or <code>null</code>
*/
public static boolean isInnerClass( Class<?> cls )
{
if ( cls == null )
{
return false;
}
return ( cls.getName().indexOf( INNER_CLASS_SEPARATOR_CHAR ) >= 0 );
}
// -----------------------------------------------------------------------
/**
* Compares two <code>Class</code>s by name.
*/
private static class ClassNameComparator implements Comparator<Class<?>>
{
/**
* Compares two <code>Class</code>s by name.
*
* @throws ClassCastException
* If <code>o1</code> or <code>o2</code> are not
* <code>Class</code> instances.
*/
public int compare( Class<?> class1, Class<?> class2 )
{
if ( class1 == null )
{
return class2 == null ? 0 : -1;
}
if ( class2 == null )
{
return 1;
}
return class1.getName().compareTo( class2.getName() );
}
}
/**
* Compares two <code>Class</code>s by name.
*/
public static final Comparator<Class<?>> CLASS_NAME_COMPARATOR = new ClassNameComparator();
/**
* Compares two <code>Package</code>s by name.
*/
private static class PackageNameComparator implements Comparator<Package>
{
/**
* Compares two <code>Package</code>s by name.
*
* @throws ClassCastException
* If <code>o1</code> or <code>o2</code> are not
* <code>Package</code> instances.
*/
public int compare( Package package1, Package package2 )
{
if ( package1 == null )
{
return package2 == null ? 0 : -1;
}
if ( package2 == null )
{
return 1;
}
return package1.getName().compareTo( package2.getName() );
}
}
/**
* Compares two <code>Package</code>s by name.
*/
public static final Comparator<Package> PACKAGE_NAME_COMPARATOR = new PackageNameComparator();
}