001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.util;
018:
019: import java.beans.Introspector;
020: import java.lang.reflect.Array;
021: import java.lang.reflect.Constructor;
022: import java.lang.reflect.Method;
023: import java.lang.reflect.Modifier;
024: import java.lang.reflect.Proxy;
025: import java.util.ArrayList;
026: import java.util.Arrays;
027: import java.util.Collection;
028: import java.util.Collections;
029: import java.util.HashMap;
030: import java.util.HashSet;
031: import java.util.Iterator;
032: import java.util.LinkedHashSet;
033: import java.util.List;
034: import java.util.Map;
035: import java.util.Set;
036:
037: import org.apache.commons.logging.Log;
038: import org.apache.commons.logging.LogFactory;
039:
040: /**
041: * Miscellaneous class utility methods. Mainly for internal use within the
042: * framework; consider Jakarta's Commons Lang for a more comprehensive suite
043: * of class utilities.
044: *
045: * @author Keith Donald
046: * @author Rob Harrop
047: * @author Juergen Hoeller
048: * @since 1.1
049: * @see TypeUtils
050: * @see ReflectionUtils
051: */
052: public abstract class ClassUtils {
053:
054: /** Suffix for array class names: "[]" */
055: public static final String ARRAY_SUFFIX = "[]";
056:
057: /** Prefix for internal array class names: "[L" */
058: private static final String INTERNAL_ARRAY_PREFIX = "[L";
059:
060: /** The package separator character '.' */
061: private static final char PACKAGE_SEPARATOR = '.';
062:
063: /** The inner class separator character '$' */
064: private static final char INNER_CLASS_SEPARATOR = '$';
065:
066: /** The CGLIB class separator character "$$" */
067: public static final String CGLIB_CLASS_SEPARATOR = "$$";
068:
069: /** The ".class" file suffix */
070: public static final String CLASS_FILE_SUFFIX = ".class";
071:
072: private static final Log logger = LogFactory
073: .getLog(ClassUtils.class);
074:
075: /**
076: * Map with primitive wrapper type as key and corresponding primitive
077: * type as value, for example: Integer.class -> int.class.
078: */
079: private static final Map primitiveWrapperTypeMap = new HashMap(8);
080:
081: /**
082: * Map with primitive type name as key and corresponding primitive
083: * type as value, for example: "int" -> "int.class".
084: */
085: private static final Map primitiveTypeNameMap = new HashMap(16);
086:
087: static {
088: primitiveWrapperTypeMap.put(Boolean.class, boolean.class);
089: primitiveWrapperTypeMap.put(Byte.class, byte.class);
090: primitiveWrapperTypeMap.put(Character.class, char.class);
091: primitiveWrapperTypeMap.put(Double.class, double.class);
092: primitiveWrapperTypeMap.put(Float.class, float.class);
093: primitiveWrapperTypeMap.put(Integer.class, int.class);
094: primitiveWrapperTypeMap.put(Long.class, long.class);
095: primitiveWrapperTypeMap.put(Short.class, short.class);
096:
097: Set primitiveTypeNames = new HashSet(16);
098: primitiveTypeNames.addAll(primitiveWrapperTypeMap.values());
099: primitiveTypeNames.addAll(Arrays.asList(new Class[] {
100: boolean[].class, byte[].class, char[].class,
101: double[].class, float[].class, int[].class,
102: long[].class, short[].class }));
103: for (Iterator it = primitiveTypeNames.iterator(); it.hasNext();) {
104: Class primitiveClass = (Class) it.next();
105: primitiveTypeNameMap.put(primitiveClass.getName(),
106: primitiveClass);
107: }
108: }
109:
110: /**
111: * Return the default ClassLoader to use: typically the thread context
112: * ClassLoader, if available; the ClassLoader that loaded the ClassUtils
113: * class will be used as fallback.
114: * <p>Call this method if you intend to use the thread context ClassLoader
115: * in a scenario where you absolutely need a non-null ClassLoader reference:
116: * for example, for class path resource loading (but not necessarily for
117: * <code>Class.forName</code>, which accepts a <code>null</code> ClassLoader
118: * reference as well).
119: * @return the default ClassLoader (never <code>null</code>)
120: * @see java.lang.Thread#getContextClassLoader()
121: */
122: public static ClassLoader getDefaultClassLoader() {
123: ClassLoader cl = null;
124: try {
125: cl = Thread.currentThread().getContextClassLoader();
126: } catch (Throwable ex) {
127: logger
128: .debug(
129: "Cannot access thread context ClassLoader - falling back to system class loader",
130: ex);
131: }
132: if (cl == null) {
133: // No thread context class loader -> use class loader of this class.
134: cl = ClassUtils.class.getClassLoader();
135: }
136: return cl;
137: }
138:
139: /**
140: * Determine whether the {@link Class} identified by the supplied name is present
141: * and can be loaded. Will return <code>false</code> if either the class or
142: * one of its dependencies is not present or cannot be loaded.
143: * @param className the name of the class to check
144: * @return whether the specified class is present
145: */
146: public static boolean isPresent(String className) {
147: return isPresent(className, getDefaultClassLoader());
148: }
149:
150: /**
151: * Determine whether the {@link Class} identified by the supplied name is present
152: * and can be loaded. Will return <code>false</code> if either the class or
153: * one of its dependencies is not present or cannot be loaded.
154: * @param className the name of the class to check
155: * @param classLoader the class loader to use
156: * (may be <code>null</code>, which indicates the default class loader)
157: * @return whether the specified class is present
158: */
159: public static boolean isPresent(String className,
160: ClassLoader classLoader) {
161: try {
162: forName(className, classLoader);
163: return true;
164: } catch (Throwable ex) {
165: if (logger.isDebugEnabled()) {
166: logger
167: .debug("Class ["
168: + className
169: + "] or one of its dependencies is not present: "
170: + ex);
171: }
172: return false;
173: }
174: }
175:
176: /**
177: * Replacement for <code>Class.forName()</code> that also returns Class instances
178: * for primitives (like "int") and array class names (like "String[]").
179: * <p>Always uses the default class loader: that is, preferably the thread context
180: * class loader, or the ClassLoader that loaded the ClassUtils class as fallback.
181: * @param name the name of the Class
182: * @return Class instance for the supplied name
183: * @throws ClassNotFoundException if the class was not found
184: * @throws LinkageError if the class file could not be loaded
185: * @see Class#forName(String, boolean, ClassLoader)
186: * @see #getDefaultClassLoader()
187: */
188: public static Class forName(String name)
189: throws ClassNotFoundException, LinkageError {
190: return forName(name, getDefaultClassLoader());
191: }
192:
193: /**
194: * Replacement for <code>Class.forName()</code> that also returns Class instances
195: * for primitives (like "int") and array class names (like "String[]").
196: * @param name the name of the Class
197: * @param classLoader the class loader to use
198: * (may be <code>null</code>, which indicates the default class loader)
199: * @return Class instance for the supplied name
200: * @throws ClassNotFoundException if the class was not found
201: * @throws LinkageError if the class file could not be loaded
202: * @see Class#forName(String, boolean, ClassLoader)
203: */
204: public static Class forName(String name, ClassLoader classLoader)
205: throws ClassNotFoundException, LinkageError {
206: Assert.notNull(name, "Name must not be null");
207:
208: Class clazz = resolvePrimitiveClassName(name);
209: if (clazz != null) {
210: return clazz;
211: }
212:
213: // "java.lang.String[]" style arrays
214: if (name.endsWith(ARRAY_SUFFIX)) {
215: String elementClassName = name.substring(0, name.length()
216: - ARRAY_SUFFIX.length());
217: Class elementClass = forName(elementClassName, classLoader);
218: return Array.newInstance(elementClass, 0).getClass();
219: }
220:
221: // "[Ljava.lang.String;" style arrays
222: int internalArrayMarker = name.indexOf(INTERNAL_ARRAY_PREFIX);
223: if (internalArrayMarker != -1 && name.endsWith(";")) {
224: String elementClassName = null;
225: if (internalArrayMarker == 0) {
226: elementClassName = name.substring(INTERNAL_ARRAY_PREFIX
227: .length(), name.length() - 1);
228: } else if (name.startsWith("[")) {
229: elementClassName = name.substring(1);
230: }
231: Class elementClass = forName(elementClassName, classLoader);
232: return Array.newInstance(elementClass, 0).getClass();
233: }
234:
235: ClassLoader classLoaderToUse = classLoader;
236: if (classLoaderToUse == null) {
237: classLoaderToUse = getDefaultClassLoader();
238: }
239: return classLoaderToUse.loadClass(name);
240: }
241:
242: /**
243: * Resolve the given class name into a Class instance. Supports
244: * primitives (like "int") and array class names (like "String[]").
245: * <p>This is effectively equivalent to the <code>forName</code>
246: * method with the same arguments, with the only difference being
247: * the exceptions thrown in case of class loading failure.
248: * @param className the name of the Class
249: * @param classLoader the class loader to use
250: * (may be <code>null</code>, which indicates the default class loader)
251: * @return Class instance for the supplied name
252: * @throws IllegalArgumentException if the class name was not resolvable
253: * (that is, the class could not be found or the class file could not be loaded)
254: * @see #forName(String, ClassLoader)
255: */
256: public static Class resolveClassName(String className,
257: ClassLoader classLoader) throws IllegalArgumentException {
258: try {
259: return forName(className, classLoader);
260: } catch (ClassNotFoundException ex) {
261: throw new IllegalArgumentException("Cannot find class ["
262: + className + "]. Root cause: " + ex);
263: } catch (LinkageError ex) {
264: throw new IllegalArgumentException(
265: "Error loading class ["
266: + className
267: + "]: problem with class file or dependent class. Root cause: "
268: + ex);
269: }
270: }
271:
272: /**
273: * Resolve the given class name as primitive class, if appropriate,
274: * according to the JVM's naming rules for primitive classes.
275: * <p>Also supports the JVM's internal class names for primitive arrays.
276: * Does <i>not</i> support the "[]" suffix notation for primitive arrays;
277: * this is only supported by {@link #forName}.
278: * @param name the name of the potentially primitive class
279: * @return the primitive class, or <code>null</code> if the name does not denote
280: * a primitive class or primitive array class
281: */
282: public static Class resolvePrimitiveClassName(String name) {
283: Class result = null;
284: // Most class names will be quite long, considering that they
285: // SHOULD sit in a package, so a length check is worthwhile.
286: if (name != null && name.length() <= 8) {
287: // Could be a primitive - likely.
288: result = (Class) primitiveTypeNameMap.get(name);
289: }
290: return result;
291: }
292:
293: /**
294: * Return the user-defined class for the given instance: usually simply
295: * the class of the given instance, but the original class in case of a
296: * CGLIB-generated subclass.
297: * @param instance the instance to check
298: * @return the user-defined class
299: */
300: public static Class getUserClass(Object instance) {
301: Assert.notNull(instance, "Instance must not be null");
302: return getUserClass(instance.getClass());
303: }
304:
305: /**
306: * Return the user-defined class for the given class: usually simply the given
307: * class, but the original class in case of a CGLIB-generated subclass.
308: * @param clazz the class to check
309: * @return the user-defined class
310: */
311: public static Class getUserClass(Class clazz) {
312: return (clazz != null
313: && clazz.getName().indexOf(CGLIB_CLASS_SEPARATOR) != -1 ? clazz
314: .getSuperclass()
315: : clazz);
316: }
317:
318: /**
319: * Get the class name without the qualified package name.
320: * @param className the className to get the short name for
321: * @return the class name of the class without the package name
322: * @throws IllegalArgumentException if the className is empty
323: */
324: public static String getShortName(String className) {
325: Assert.hasLength(className, "Class name must not be empty");
326: int lastDotIndex = className.lastIndexOf(PACKAGE_SEPARATOR);
327: int nameEndIndex = className.indexOf(CGLIB_CLASS_SEPARATOR);
328: if (nameEndIndex == -1) {
329: nameEndIndex = className.length();
330: }
331: String shortName = className.substring(lastDotIndex + 1,
332: nameEndIndex);
333: shortName = shortName.replace(INNER_CLASS_SEPARATOR,
334: PACKAGE_SEPARATOR);
335: return shortName;
336: }
337:
338: /**
339: * Get the class name without the qualified package name.
340: * @param clazz the class to get the short name for
341: * @return the class name of the class without the package name
342: */
343: public static String getShortName(Class clazz) {
344: return getShortName(getQualifiedName(clazz));
345: }
346:
347: /**
348: * Return the short string name of a Java class in decapitalized JavaBeans
349: * property format. Strips the outer class name in case of an inner class.
350: * @param clazz the class
351: * @return the short name rendered in a standard JavaBeans property format
352: * @see java.beans.Introspector#decapitalize(String)
353: */
354: public static String getShortNameAsProperty(Class clazz) {
355: String shortName = ClassUtils.getShortName(clazz);
356: int dotIndex = shortName.lastIndexOf('.');
357: shortName = (dotIndex != -1 ? shortName.substring(dotIndex + 1)
358: : shortName);
359: return Introspector.decapitalize(shortName);
360: }
361:
362: /**
363: * Determine the name of the class file, relative to the containing
364: * package: e.g. "String.class"
365: * @param clazz the class
366: * @return the file name of the ".class" file
367: */
368: public static String getClassFileName(Class clazz) {
369: Assert.notNull(clazz, "Class must not be null");
370: String className = clazz.getName();
371: int lastDotIndex = className.lastIndexOf(PACKAGE_SEPARATOR);
372: return className.substring(lastDotIndex + 1)
373: + CLASS_FILE_SUFFIX;
374: }
375:
376: /**
377: * Determine the name of the package of the given class:
378: * e.g. "java.lang" for the <code>java.lang.String</code> class.
379: * @param clazz the class
380: * @return the package name, or the empty String if the class
381: * is defined in the default package
382: */
383: public static String getPackageName(Class clazz) {
384: Assert.notNull(clazz, "Class must not be null");
385: String className = clazz.getName();
386: int lastDotIndex = className.lastIndexOf(PACKAGE_SEPARATOR);
387: return (lastDotIndex != -1 ? className.substring(0,
388: lastDotIndex) : "");
389: }
390:
391: /**
392: * Return the qualified name of the given class: usually simply
393: * the class name, but component type class name + "[]" for arrays.
394: * @param clazz the class
395: * @return the qualified name of the class
396: */
397: public static String getQualifiedName(Class clazz) {
398: Assert.notNull(clazz, "Class must not be null");
399: if (clazz.isArray()) {
400: return getQualifiedNameForArray(clazz);
401: } else {
402: return clazz.getName();
403: }
404: }
405:
406: /**
407: * Build a nice qualified name for an array:
408: * component type class name + "[]".
409: * @param clazz the array class
410: * @return a qualified name for the array class
411: */
412: private static String getQualifiedNameForArray(Class clazz) {
413: StringBuffer buffer = new StringBuffer();
414: while (clazz.isArray()) {
415: clazz = clazz.getComponentType();
416: buffer.append(ClassUtils.ARRAY_SUFFIX);
417: }
418: buffer.insert(0, clazz.getName());
419: return buffer.toString();
420: }
421:
422: /**
423: * Return the qualified name of the given method, consisting of
424: * fully qualified interface/class name + "." + method name.
425: * @param method the method
426: * @return the qualified name of the method
427: */
428: public static String getQualifiedMethodName(Method method) {
429: Assert.notNull(method, "Method must not be null");
430: return method.getDeclaringClass().getName() + "."
431: + method.getName();
432: }
433:
434: /**
435: * Determine whether the given class has a constructor with the given signature.
436: * <p>Essentially translates <code>NoSuchMethodException</code> to "false".
437: * @param clazz the clazz to analyze
438: * @param paramTypes the parameter types of the method
439: * @return whether the class has a corresponding constructor
440: * @see java.lang.Class#getMethod
441: */
442: public static boolean hasConstructor(Class clazz, Class[] paramTypes) {
443: return (getConstructorIfAvailable(clazz, paramTypes) != null);
444: }
445:
446: /**
447: * Determine whether the given class has a constructor with the given signature,
448: * and return it if available (else return <code>null</code>).
449: * <p>Essentially translates <code>NoSuchMethodException</code> to <code>null</code>.
450: * @param clazz the clazz to analyze
451: * @param paramTypes the parameter types of the method
452: * @return the constructor, or <code>null</code> if not found
453: * @see java.lang.Class#getConstructor
454: */
455: public static Constructor getConstructorIfAvailable(Class clazz,
456: Class[] paramTypes) {
457: Assert.notNull(clazz, "Class must not be null");
458: try {
459: return clazz.getConstructor(paramTypes);
460: } catch (NoSuchMethodException ex) {
461: return null;
462: }
463: }
464:
465: /**
466: * Determine whether the given class has a method with the given signature.
467: * <p>Essentially translates <code>NoSuchMethodException</code> to "false".
468: * @param clazz the clazz to analyze
469: * @param methodName the name of the method
470: * @param paramTypes the parameter types of the method
471: * @return whether the class has a corresponding method
472: * @see java.lang.Class#getMethod
473: */
474: public static boolean hasMethod(Class clazz, String methodName,
475: Class[] paramTypes) {
476: return (getMethodIfAvailable(clazz, methodName, paramTypes) != null);
477: }
478:
479: /**
480: * Determine whether the given class has a method with the given signature,
481: * and return it if available (else return <code>null</code>).
482: * <p>Essentially translates <code>NoSuchMethodException</code> to <code>null</code>.
483: * @param clazz the clazz to analyze
484: * @param methodName the name of the method
485: * @param paramTypes the parameter types of the method
486: * @return the method, or <code>null</code> if not found
487: * @see java.lang.Class#getMethod
488: */
489: public static Method getMethodIfAvailable(Class clazz,
490: String methodName, Class[] paramTypes) {
491: Assert.notNull(clazz, "Class must not be null");
492: Assert.notNull(methodName, "Method name must not be null");
493: try {
494: return clazz.getMethod(methodName, paramTypes);
495: } catch (NoSuchMethodException ex) {
496: return null;
497: }
498: }
499:
500: /**
501: * Return the number of methods with a given name (with any argument types),
502: * for the given class and/or its superclasses. Includes non-public methods.
503: * @param clazz the clazz to check
504: * @param methodName the name of the method
505: * @return the number of methods with the given name
506: */
507: public static int getMethodCountForName(final Class clazz,
508: final String methodName) {
509: Assert.notNull(clazz, "Class must not be null");
510: Assert.notNull(methodName, "Method name must not be null");
511: int count = 0;
512: final Method[] declaredMethods = clazz.getDeclaredMethods();
513: for (int i = 0; i < declaredMethods.length; i++) {
514: Method method = declaredMethods[i];
515: if (methodName.equals(method.getName())) {
516: count++;
517: }
518: }
519: Class[] ifcs = clazz.getInterfaces();
520: for (int i = 0; i < ifcs.length; i++) {
521: count += getMethodCountForName(ifcs[i], methodName);
522: }
523: if (clazz.getSuperclass() != null) {
524: count += getMethodCountForName(clazz.getSuperclass(),
525: methodName);
526: }
527: return count;
528: }
529:
530: /**
531: * Does the given class and/or its superclasses at least have one or more
532: * methods (with any argument types)? Includes non-public methods.
533: * @param clazz the clazz to check
534: * @param methodName the name of the method
535: * @return whether there is at least one method with the given name
536: */
537: public static boolean hasAtLeastOneMethodWithName(
538: final Class clazz, final String methodName) {
539: Assert.notNull(clazz, "Class must not be null");
540: Assert.notNull(methodName, "Method name must not be null");
541: final Method[] declaredMethods = clazz.getDeclaredMethods();
542: for (int i = 0; i < declaredMethods.length; i++) {
543: Method method = declaredMethods[i];
544: if (method.getName().equals(methodName)) {
545: return true;
546: }
547: }
548: Class[] ifcs = clazz.getInterfaces();
549: for (int i = 0; i < ifcs.length; i++) {
550: if (hasAtLeastOneMethodWithName(ifcs[i], methodName)) {
551: return true;
552: }
553: }
554: return (clazz.getSuperclass() != null && hasAtLeastOneMethodWithName(
555: clazz.getSuperclass(), methodName));
556: }
557:
558: /**
559: * Given a method, which may come from an interface, and a target class used
560: * in the current reflective invocation, find the corresponding target method
561: * if there is one. E.g. the method may be <code>IFoo.bar()</code> and the
562: * target class may be <code>DefaultFoo</code>. In this case, the method may be
563: * <code>DefaultFoo.bar()</code>. This enables attributes on that method to be found.
564: * <p><b>NOTE:</b> In contrast to {@link org.springframework.aop.support.AopUtils#getMostSpecificMethod},
565: * this method does <i>not</i> resolve Java 5 bridge methods automatically.
566: * Call {@link org.springframework.core.BridgeMethodResolver#findBridgedMethod}
567: * if bridge method resolution is desirable (e.g. for obtaining metadata from
568: * the original method definition).
569: * @param method the method to be invoked, which may come from an interface
570: * @param targetClass the target class for the current invocation.
571: * May be <code>null</code> or may not even implement the method.
572: * @return the specific target method, or the original method if the
573: * <code>targetClass</code> doesn't implement it or is <code>null</code>
574: * @see org.springframework.aop.support.AopUtils#getMostSpecificMethod
575: */
576: public static Method getMostSpecificMethod(Method method,
577: Class targetClass) {
578: if (method != null && targetClass != null) {
579: try {
580: method = targetClass.getMethod(method.getName(), method
581: .getParameterTypes());
582: } catch (NoSuchMethodException ex) {
583: // Perhaps the target class doesn't implement this method:
584: // that's fine, just use the original method.
585: }
586: }
587: return method;
588: }
589:
590: /**
591: * Return a static method of a class.
592: * @param methodName the static method name
593: * @param clazz the class which defines the method
594: * @param args the parameter types to the method
595: * @return the static method, or <code>null</code> if no static method was found
596: * @throws IllegalArgumentException if the method name is blank or the clazz is null
597: */
598: public static Method getStaticMethod(Class clazz,
599: String methodName, Class[] args) {
600: Assert.notNull(clazz, "Class must not be null");
601: Assert.notNull(methodName, "Method name must not be null");
602: try {
603: Method method = clazz.getDeclaredMethod(methodName, args);
604: if ((method.getModifiers() & Modifier.STATIC) != 0) {
605: return method;
606: }
607: } catch (NoSuchMethodException ex) {
608: }
609: return null;
610: }
611:
612: /**
613: * Check if the given class represents a primitive wrapper,
614: * i.e. Boolean, Byte, Character, Short, Integer, Long, Float, or Double.
615: * @param clazz the class to check
616: * @return whether the given class is a primitive wrapper class
617: */
618: public static boolean isPrimitiveWrapper(Class clazz) {
619: Assert.notNull(clazz, "Class must not be null");
620: return primitiveWrapperTypeMap.containsKey(clazz);
621: }
622:
623: /**
624: * Check if the given class represents a primitive (i.e. boolean, byte,
625: * char, short, int, long, float, or double) or a primitive wrapper
626: * (i.e. Boolean, Byte, Character, Short, Integer, Long, Float, or Double).
627: * @param clazz the class to check
628: * @return whether the given class is a primitive or primitive wrapper class
629: */
630: public static boolean isPrimitiveOrWrapper(Class clazz) {
631: Assert.notNull(clazz, "Class must not be null");
632: return (clazz.isPrimitive() || isPrimitiveWrapper(clazz));
633: }
634:
635: /**
636: * Check if the given class represents an array of primitives,
637: * i.e. boolean, byte, char, short, int, long, float, or double.
638: * @param clazz the class to check
639: * @return whether the given class is a primitive array class
640: */
641: public static boolean isPrimitiveArray(Class clazz) {
642: Assert.notNull(clazz, "Class must not be null");
643: return (clazz.isArray() && clazz.getComponentType()
644: .isPrimitive());
645: }
646:
647: /**
648: * Check if the given class represents an array of primitive wrappers,
649: * i.e. Boolean, Byte, Character, Short, Integer, Long, Float, or Double.
650: * @param clazz the class to check
651: * @return whether the given class is a primitive wrapper array class
652: */
653: public static boolean isPrimitiveWrapperArray(Class clazz) {
654: Assert.notNull(clazz, "Class must not be null");
655: return (clazz.isArray() && isPrimitiveWrapper(clazz
656: .getComponentType()));
657: }
658:
659: /**
660: * Check if the right-hand side type may be assigned to the left-hand side
661: * type, assuming setting by reflection. Considers primitive wrapper
662: * classes as assignable to the corresponding primitive types.
663: * @param lhsType the target type
664: * @param rhsType the value type that should be assigned to the target type
665: * @return if the target type is assignable from the value type
666: * @see TypeUtils#isAssignable
667: */
668: public static boolean isAssignable(Class lhsType, Class rhsType) {
669: Assert.notNull(lhsType, "Left-hand side type must not be null");
670: Assert
671: .notNull(rhsType,
672: "Right-hand side type must not be null");
673: return (lhsType.isAssignableFrom(rhsType) || lhsType
674: .equals(primitiveWrapperTypeMap.get(rhsType)));
675: }
676:
677: /**
678: * Determine if the given type is assignable from the given value,
679: * assuming setting by reflection. Considers primitive wrapper classes
680: * as assignable to the corresponding primitive types.
681: * @param type the target type
682: * @param value the value that should be assigned to the type
683: * @return if the type is assignable from the value
684: */
685: public static boolean isAssignableValue(Class type, Object value) {
686: Assert.notNull(type, "Type must not be null");
687: return (value != null ? isAssignable(type, value.getClass())
688: : !type.isPrimitive());
689: }
690:
691: /**
692: * Convert a "/"-based resource path to a "."-based fully qualified class name.
693: * @param resourcePath the resource path pointing to a class
694: * @return the corresponding fully qualified class name
695: */
696: public static String convertResourcePathToClassName(
697: String resourcePath) {
698: return resourcePath.replace('/', '.');
699: }
700:
701: /**
702: * Convert a "."-based fully qualified class name to a "/"-based resource path.
703: * @param className the fully qualified class name
704: * @return the corresponding resource path, pointing to the class
705: */
706: public static String convertClassNameToResourcePath(String className) {
707: return className.replace('.', '/');
708: }
709:
710: /**
711: * Return a path suitable for use with <code>ClassLoader.getResource</code>
712: * (also suitable for use with <code>Class.getResource</code> by prepending a
713: * slash ('/') to the return value. Built by taking the package of the specified
714: * class file, converting all dots ('.') to slashes ('/'), adding a trailing slash
715: * if necesssary, and concatenating the specified resource name to this.
716: * <br/>As such, this function may be used to build a path suitable for
717: * loading a resource file that is in the same package as a class file,
718: * although {@link org.springframework.core.io.ClassPathResource} is usually
719: * even more convenient.
720: * @param clazz the Class whose package will be used as the base
721: * @param resourceName the resource name to append. A leading slash is optional.
722: * @return the built-up resource path
723: * @see java.lang.ClassLoader#getResource
724: * @see java.lang.Class#getResource
725: */
726: public static String addResourcePathToPackagePath(Class clazz,
727: String resourceName) {
728: Assert.notNull(resourceName, "Resource name must not be null");
729: if (!resourceName.startsWith("/")) {
730: return classPackageAsResourcePath(clazz) + "/"
731: + resourceName;
732: }
733: return classPackageAsResourcePath(clazz) + resourceName;
734: }
735:
736: /**
737: * Given an input class object, return a string which consists of the
738: * class's package name as a pathname, i.e., all dots ('.') are replaced by
739: * slashes ('/'). Neither a leading nor trailing slash is added. The result
740: * could be concatenated with a slash and the name of a resource, and fed
741: * directly to <code>ClassLoader.getResource()</code>. For it to be fed to
742: * <code>Class.getResource</code> instead, a leading slash would also have
743: * to be prepended to the returned value.
744: * @param clazz the input class. A <code>null</code> value or the default
745: * (empty) package will result in an empty string ("") being returned.
746: * @return a path which represents the package name
747: * @see ClassLoader#getResource
748: * @see Class#getResource
749: */
750: public static String classPackageAsResourcePath(Class clazz) {
751: if (clazz == null) {
752: return "";
753: }
754: String className = clazz.getName();
755: int packageEndIndex = className.lastIndexOf('.');
756: if (packageEndIndex == -1) {
757: return "";
758: }
759: String packageName = className.substring(0, packageEndIndex);
760: return packageName.replace('.', '/');
761: }
762:
763: /**
764: * Build a String that consists of the names of the classes/interfaces
765: * in the given array.
766: * <p>Basically like <code>AbstractCollection.toString()</code>, but stripping
767: * the "class "/"interface " prefix before every class name.
768: * @param classes a Collection of Class objects (may be <code>null</code>)
769: * @return a String of form "[com.foo.Bar, com.foo.Baz]"
770: * @see java.util.AbstractCollection#toString()
771: */
772: public static String classNamesToString(Class[] classes) {
773: return classNamesToString(Arrays.asList(classes));
774: }
775:
776: /**
777: * Build a String that consists of the names of the classes/interfaces
778: * in the given collection.
779: * <p>Basically like <code>AbstractCollection.toString()</code>, but stripping
780: * the "class "/"interface " prefix before every class name.
781: * @param classes a Collection of Class objects (may be <code>null</code>)
782: * @return a String of form "[com.foo.Bar, com.foo.Baz]"
783: * @see java.util.AbstractCollection#toString()
784: */
785: public static String classNamesToString(Collection classes) {
786: if (CollectionUtils.isEmpty(classes)) {
787: return "[]";
788: }
789: StringBuffer sb = new StringBuffer("[");
790: for (Iterator it = classes.iterator(); it.hasNext();) {
791: Class clazz = (Class) it.next();
792: sb.append(clazz.getName());
793: if (it.hasNext()) {
794: sb.append(", ");
795: }
796: }
797: sb.append("]");
798: return sb.toString();
799: }
800:
801: /**
802: * Return all interfaces that the given instance implements as array,
803: * including ones implemented by superclasses.
804: * @param instance the instance to analyse for interfaces
805: * @return all interfaces that the given instance implements as array
806: */
807: public static Class[] getAllInterfaces(Object instance) {
808: Assert.notNull(instance, "Instance must not be null");
809: return getAllInterfacesForClass(instance.getClass());
810: }
811:
812: /**
813: * Return all interfaces that the given class implements as array,
814: * including ones implemented by superclasses.
815: * <p>If the class itself is an interface, it gets returned as sole interface.
816: * @param clazz the class to analyse for interfaces
817: * @return all interfaces that the given object implements as array
818: */
819: public static Class[] getAllInterfacesForClass(Class clazz) {
820: Assert.notNull(clazz, "Class must not be null");
821: if (clazz.isInterface()) {
822: return new Class[] { clazz };
823: }
824: List interfaces = new ArrayList();
825: while (clazz != null) {
826: for (int i = 0; i < clazz.getInterfaces().length; i++) {
827: Class ifc = clazz.getInterfaces()[i];
828: if (!interfaces.contains(ifc)) {
829: interfaces.add(ifc);
830: }
831: }
832: clazz = clazz.getSuperclass();
833: }
834: return (Class[]) interfaces
835: .toArray(new Class[interfaces.size()]);
836: }
837:
838: /**
839: * Return all interfaces that the given instance implements as Set,
840: * including ones implemented by superclasses.
841: * @param instance the instance to analyse for interfaces
842: * @return all interfaces that the given instance implements as Set
843: */
844: public static Set getAllInterfacesAsSet(Object instance) {
845: Assert.notNull(instance, "Instance must not be null");
846: return getAllInterfacesForClassAsSet(instance.getClass());
847: }
848:
849: /**
850: * Return all interfaces that the given class implements as Set,
851: * including ones implemented by superclasses.
852: * <p>If the class itself is an interface, it gets returned as sole interface.
853: * @param clazz the class to analyse for interfaces
854: * @return all interfaces that the given object implements as Set
855: */
856: public static Set getAllInterfacesForClassAsSet(Class clazz) {
857: Assert.notNull(clazz, "Class must not be null");
858: if (clazz.isInterface()) {
859: return Collections.singleton(clazz);
860: }
861: Set interfaces = new LinkedHashSet();
862: while (clazz != null) {
863: for (int i = 0; i < clazz.getInterfaces().length; i++) {
864: Class ifc = clazz.getInterfaces()[i];
865: interfaces.add(ifc);
866: }
867: clazz = clazz.getSuperclass();
868: }
869: return interfaces;
870: }
871:
872: /**
873: * Create a composite interface Class for the given interfaces,
874: * implementing the given interfaces in one single Class.
875: * <p>This implementation builds a JDK proxy class for the given interfaces.
876: * @param interfaces the interfaces to merge
877: * @param classLoader the ClassLoader to create the composite Class in
878: * @return the merged interface as Class
879: * @see java.lang.reflect.Proxy#getProxyClass
880: */
881: public static Class createCompositeInterface(Class[] interfaces,
882: ClassLoader classLoader) {
883: Assert.notEmpty(interfaces, "Interfaces must not be empty");
884: Assert.notNull(classLoader, "ClassLoader must not be null");
885: return Proxy.getProxyClass(classLoader, interfaces);
886: }
887:
888: }
|