001: /*
002: * Copyright 2004-2006 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.compass.core.util;
018:
019: import java.beans.Introspector;
020: import java.lang.reflect.Array;
021: import java.lang.reflect.Constructor;
022: import java.lang.reflect.Field;
023: import java.lang.reflect.Member;
024: import java.lang.reflect.Method;
025: import java.lang.reflect.Modifier;
026: import java.util.HashSet;
027: import java.util.Set;
028:
029: /**
030: * Miscellaneous class utility methods. Mainly for internal use within the
031: * framework; consider Jakarta's Commons Lang for a more comprehensive suite
032: * of utilities.
033: *
034: * @author kimchy
035: */
036: public abstract class ClassUtils {
037:
038: /**
039: * Suffix for array class names
040: */
041: public static final String ARRAY_SUFFIX = "[]";
042:
043: /**
044: * All primitive classes
045: */
046: private static Class[] PRIMITIVE_CLASSES = { boolean.class,
047: byte.class, char.class, short.class, int.class, long.class,
048: float.class, double.class };
049:
050: /**
051: * The package separator character '.'
052: */
053: private static final char PACKAGE_SEPARATOR_CHAR = '.';
054:
055: /**
056: * The inner class separator character '$'
057: */
058: private static final char INNER_CLASS_SEPARATOR_CHAR = '$';
059:
060: /**
061: * The CGLIB class separator character "$$"
062: */
063: private static final String CGLIB_CLASS_SEPARATOR_CHAR = "$$";
064:
065: /**
066: * An empty class array
067: */
068: private static final Class[] EMPTY_CLASS_ARRAY = new Class[0];
069:
070: /**
071: * Replacement for <code>Class.forName()</code> that also returns Class instances
072: * for primitives (like "int") and array class names (like "String[]").
073: *
074: * @param name the name of the Class
075: * @param classLoader the class loader to use
076: * @return Class instance for the supplied name
077: * @see java.lang.Class#forName
078: * @see java.lang.Thread#getContextClassLoader
079: */
080: public static Class forName(String name, ClassLoader classLoader)
081: throws ClassNotFoundException {
082: Class clazz = resolvePrimitiveClassName(name);
083: if (clazz != null) {
084: return clazz;
085: }
086: if (name.endsWith(ARRAY_SUFFIX)) {
087: // special handling for array class names
088: String elementClassName = name.substring(0, name.length()
089: - ARRAY_SUFFIX.length());
090: Class elementClass = ClassUtils.forName(elementClassName,
091: classLoader);
092: return Array.newInstance(elementClass, 0).getClass();
093: }
094: return Class.forName(name, true, classLoader);
095: }
096:
097: /**
098: * Resolve the given class name as primitive class, if appropriate.
099: *
100: * @param name the name of the potentially primitive class
101: * @return the primitive class, or <code>null</code> if the name does not denote
102: * a primitive class
103: */
104: public static Class resolvePrimitiveClassName(String name) {
105: // Most class names will be quite long, considering that they
106: // SHOULD sit in a package, so a length check is worthwhile.
107: if (name.length() <= 8) {
108: // could be a primitive - likely
109: for (int i = 0; i < PRIMITIVE_CLASSES.length; i++) {
110: Class clazz = PRIMITIVE_CLASSES[i];
111: if (clazz.getName().equals(name)) {
112: return clazz;
113: }
114: }
115: }
116: return null;
117: }
118:
119: /**
120: * Return the short string name of a Java class in decapitalized
121: * JavaBeans property format.
122: *
123: * @param clazz the class
124: * @return the short name rendered in a standard JavaBeans property format
125: * @see java.beans.Introspector#decapitalize(String)
126: */
127: public static String getShortNameAsProperty(Class clazz) {
128: return Introspector.decapitalize(getShortName(clazz));
129: }
130:
131: public static String getShortNameForField(Field field) {
132: return Introspector.decapitalize(field.getName());
133: }
134:
135: public static String getShortNameForMethod(Method method) {
136: String name = method.getName();
137: if (name.startsWith("is")) {
138: name = name.substring("is".length());
139: } else if (name.startsWith("get")) {
140: name = name.substring("get".length());
141: } else {
142: throw new IllegalArgumentException("Method ["
143: + method.getName()
144: + "] is not formed as a JavaBean property");
145: }
146: return Introspector.decapitalize(name);
147: }
148:
149: /**
150: * Get the class name without the qualified package name.
151: *
152: * @param clazz the class to get the short name for
153: * @return the class name of the class without the package name
154: * @throws IllegalArgumentException if the class is null
155: */
156: public static String getShortName(Class clazz) {
157: return getShortName(clazz.getName());
158: }
159:
160: /**
161: * Get the class name without the qualified package name.
162: *
163: * @param className the className to get the short name for
164: * @return the class name of the class without the package name
165: * @throws IllegalArgumentException if the className is empty
166: */
167: public static String getShortName(String className) {
168: Assert.hasLength(className, "class name must not be empty");
169: int lastDotIndex = className
170: .lastIndexOf(PACKAGE_SEPARATOR_CHAR);
171: int nameEndIndex = className
172: .indexOf(CGLIB_CLASS_SEPARATOR_CHAR);
173: if (nameEndIndex == -1) {
174: nameEndIndex = className.length();
175: }
176: String shortName = className.substring(lastDotIndex + 1,
177: nameEndIndex);
178: shortName = shortName.replace(INNER_CLASS_SEPARATOR_CHAR,
179: PACKAGE_SEPARATOR_CHAR);
180: return shortName;
181: }
182:
183: /**
184: * Return the qualified name of the given method, consisting of
185: * fully qualified interface/class name + "." + method name.
186: *
187: * @param method the method
188: * @return the qualified name of the method
189: */
190: public static String getQualifiedMethodName(Method method) {
191: Assert.notNull(method, "Method must not be empty");
192: return method.getDeclaringClass().getName() + "."
193: + method.getName();
194: }
195:
196: /**
197: * Determine whether the given class has a method with the given signature.
198: * Essentially translates <code>NoSuchMethodException</code> to "false".
199: *
200: * @param clazz the clazz to analyze
201: * @param methodName the name of the method
202: * @param paramTypes the parameter types of the method
203: */
204: public static boolean hasMethod(Class clazz, String methodName,
205: Class[] paramTypes) {
206: try {
207: clazz.getMethod(methodName, paramTypes);
208: return true;
209: } catch (NoSuchMethodException ex) {
210: return false;
211: }
212: }
213:
214: /**
215: * Return the number of methods with a given name (with any argument types),
216: * for the given class and/or its superclasses. Includes non-public methods.
217: *
218: * @param clazz the clazz to check
219: * @param methodName the name of the method
220: * @return the number of methods with the given name
221: */
222: public static int getMethodCountForName(Class clazz,
223: String methodName) {
224: int count = 0;
225: do {
226: for (int i = 0; i < clazz.getDeclaredMethods().length; i++) {
227: Method method = clazz.getDeclaredMethods()[i];
228: if (methodName.equals(method.getName())) {
229: count++;
230: }
231: }
232: clazz = clazz.getSuperclass();
233: } while (clazz != null);
234: return count;
235: }
236:
237: /**
238: * Does the given class and/or its superclasses at least have one or more
239: * methods (with any argument types)? Includes non-public methods.
240: *
241: * @param clazz the clazz to check
242: * @param methodName the name of the method
243: * @return whether there is at least one method with the given name
244: */
245: public static boolean hasAtLeastOneMethodWithName(Class clazz,
246: String methodName) {
247: do {
248: for (int i = 0; i < clazz.getDeclaredMethods().length; i++) {
249: Method method = clazz.getDeclaredMethods()[i];
250: if (methodName.equals(method.getName())) {
251: return true;
252: }
253: }
254: clazz = clazz.getSuperclass();
255: } while (clazz != null);
256: return false;
257: }
258:
259: /**
260: * Return a static method of a class.
261: *
262: * @param methodName the static method name
263: * @param clazz the class which defines the method
264: * @param args the parameter types to the method
265: * @return the static method, or <code>null</code> if no static method was found
266: * @throws IllegalArgumentException if the method name is blank or the clazz is null
267: */
268: public static Method getStaticMethod(Class clazz,
269: String methodName, Class[] args) {
270: try {
271: Method method = clazz.getDeclaredMethod(methodName, args);
272: if ((method.getModifiers() & Modifier.STATIC) != 0) {
273: return method;
274: }
275: } catch (NoSuchMethodException ex) {
276: // no op
277: }
278: return null;
279: }
280:
281: /**
282: * Return a path suitable for use with ClassLoader.getResource (also
283: * suitable for use with Class.getResource by prepending a slash ('/') to
284: * the return value. Built by taking the package of the specified class
285: * file, converting all dots ('.') to slashes ('/'), adding a trailing slash
286: * if necesssary, and concatenating the specified resource name to this.
287: * <br/>As such, this function may be used to build a path suitable for
288: * loading a resource file that is in the same package as a class file,
289: * although {link org.springframework.core.io.ClassPathResource} is usually
290: * even more convenient.
291: *
292: * @param clazz the Class whose package will be used as the base
293: * @param resourceName the resource name to append. A leading slash is optional.
294: * @return the built-up resource path
295: * @see java.lang.ClassLoader#getResource
296: * @see java.lang.Class#getResource
297: */
298: public static String addResourcePathToPackagePath(Class clazz,
299: String resourceName) {
300: if (!resourceName.startsWith("/")) {
301: return classPackageAsResourcePath(clazz) + "/"
302: + resourceName;
303: }
304: return classPackageAsResourcePath(clazz) + resourceName;
305: }
306:
307: /**
308: * Given an input class object, return a string which consists of the
309: * class's package name as a pathname, i.e., all dots ('.') are replaced by
310: * slashes ('/'). Neither a leading nor trailing slash is added. The result
311: * could be concatenated with a slash and the name of a resource, and fed
312: * directly to ClassLoader.getResource(). For it to be fed to Class.getResource,
313: * a leading slash would also have to be prepended to the return value.
314: *
315: * @param clazz the input class. A null value or the default (empty) package
316: * will result in an empty string ("") being returned.
317: * @return a path which represents the package name
318: * @see java.lang.ClassLoader#getResource
319: * @see java.lang.Class#getResource
320: */
321: public static String classPackageAsResourcePath(Class clazz) {
322: if (clazz == null || clazz.getPackage() == null) {
323: return "";
324: }
325: return clazz.getPackage().getName().replace('.', '/');
326: }
327:
328: /**
329: * Return all interfaces that the given object implements as array,
330: * including ones implemented by superclasses.
331: *
332: * @param object the object to analyse for interfaces
333: * @return all interfaces that the given object implements as array
334: */
335: public static Class[] getAllInterfaces(Object object) {
336: Set interfaces = getAllInterfacesAsSet(object);
337: return (Class[]) interfaces
338: .toArray(new Class[interfaces.size()]);
339: }
340:
341: /**
342: * Return all interfaces that the given class implements as array,
343: * including ones implemented by superclasses.
344: *
345: * @param clazz the class to analyse for interfaces
346: * @return all interfaces that the given object implements as array
347: */
348: public static Class[] getAllInterfacesForClass(Class clazz) {
349: Set interfaces = getAllInterfacesForClassAsSet(clazz);
350: return (Class[]) interfaces
351: .toArray(new Class[interfaces.size()]);
352: }
353:
354: /**
355: * Return all interfaces that the given object implements as List,
356: * including ones implemented by superclasses.
357: *
358: * @param object the object to analyse for interfaces
359: * @return all interfaces that the given object implements as List
360: */
361: public static Set getAllInterfacesAsSet(Object object) {
362: return getAllInterfacesForClassAsSet(object.getClass());
363: }
364:
365: /**
366: * Return all interfaces that the given class implements as Set,
367: * including ones implemented by superclasses.
368: *
369: * @param clazz the class to analyse for interfaces
370: * @return all interfaces that the given object implements as Set
371: */
372: public static Set getAllInterfacesForClassAsSet(Class clazz) {
373: Set interfaces = new HashSet();
374: while (clazz != null) {
375: for (int i = 0; i < clazz.getInterfaces().length; i++) {
376: Class ifc = clazz.getInterfaces()[i];
377: interfaces.add(ifc);
378: }
379: clazz = clazz.getSuperclass();
380: }
381: return interfaces;
382: }
383:
384: /**
385: * Returns <code>true</code> if the member is public and the class
386: * is public.
387: *
388: * @param clazz the class to check for public
389: * @param member the member to check for public
390: * @return <code>true</code> if the member is public and the class is public
391: */
392: public static boolean isPublic(Class clazz, Member member) {
393: return Modifier.isPublic(member.getModifiers())
394: && Modifier.isPublic(clazz.getModifiers());
395: }
396:
397: /**
398: * Returns <code>true</code> of the class is an abstract class. An
399: * abstract class is a either an abstract class or an interface.
400: *
401: * @param clazz the class to check for abstact
402: * @return <code>true</code> if the class is an abstract class
403: */
404: public static boolean isAbstractClass(Class clazz) {
405: int modifier = clazz.getModifiers();
406: return Modifier.isAbstract(modifier)
407: || Modifier.isInterface(modifier);
408: }
409:
410: /**
411: * Returns the default constructor for the class. If it is not visible,
412: * will set it to be accessible.
413: *
414: * @param clazz the class to get the default constructor
415: * @return the default constructor
416: */
417: public static Constructor getDefaultConstructor(Class clazz) {
418: if (isAbstractClass(clazz))
419: return null;
420: try {
421: Constructor constructor = clazz
422: .getDeclaredConstructor(EMPTY_CLASS_ARRAY);
423: if (!isPublic(clazz, constructor)) {
424: constructor.setAccessible(true);
425: }
426: return constructor;
427: } catch (NoSuchMethodException nme) {
428: return null;
429: }
430: }
431: }
|