001    /*
002     * Copyright 2002-2004 the original author or authors.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005     * use this file except in compliance with the License. You may obtain a copy of
006     * 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, WITHOUT
012     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013     * License for the specific language governing permissions and limitations under
014     * the License.
015     */
016    package org.springframework.richclient.util;
017    
018    import java.lang.reflect.Field;
019    import java.lang.reflect.Method;
020    import java.lang.reflect.Modifier;
021    import java.math.BigInteger;
022    import java.net.InetAddress;
023    import java.net.URL;
024    import java.sql.Timestamp;
025    import java.util.Arrays;
026    import java.util.Calendar;
027    import java.util.Date;
028    import java.util.HashMap;
029    import java.util.HashSet;
030    import java.util.Map;
031    import java.util.Set;
032    
033    import org.apache.commons.logging.Log;
034    import org.apache.commons.logging.LogFactory;
035    import org.springframework.core.enums.LabeledEnum;
036    import org.springframework.core.style.StylerUtils;
037    
038    /**
039     * Misc static utility functions for java classes.
040     *
041     * @author Keith Donald
042     * @author Jim Moore
043     */
044    public class ClassUtils {
045            private static final Log logger = LogFactory.getLog(ClassUtils.class);
046    
047            private static Set simpleClasses = new HashSet();
048            static {
049                    simpleClasses.add(String.class);
050                    simpleClasses.add(Integer.class);
051                    simpleClasses.add(Float.class);
052                    simpleClasses.add(Double.class);
053                    simpleClasses.add(Long.class);
054                    simpleClasses.add(Short.class);
055                    simpleClasses.add(Byte.class);
056                    simpleClasses.add(BigInteger.class);
057                    simpleClasses.add(Date.class);
058                    simpleClasses.add(java.sql.Date.class);
059                    simpleClasses.add(Class.class);
060                    simpleClasses.add(Boolean.class);
061                    simpleClasses.add(Timestamp.class);
062                    simpleClasses.add(Calendar.class);
063                    simpleClasses.add(URL.class);
064                    simpleClasses.add(InetAddress.class);
065            }
066    
067            private ClassUtils() {
068            }
069    
070            /**
071             * Intializes the specified class if not initialized already.
072             *
073             * This is required for EnumUtils if the enum class has not yet been loaded.
074             */
075            public static void initializeClass(Class clazz) {
076                    try {
077                            Class.forName(clazz.getName(), true, Thread.currentThread().getContextClassLoader());
078                    }
079                    catch (ClassNotFoundException e) {
080                            throw new RuntimeException(e);
081                    }
082            }
083    
084            /**
085             * Returns the qualified class field name with the specified value. For
086             * example, with a class defined with a static field "NORMAL" with value =
087             * "0", passing in "0" would return: className.NORMAL.
088             *
089             * @return The qualified field.
090             */
091            public static String getClassFieldNameWithValue(Class clazz, Object value) {
092                    Field[] fields = clazz.getFields();
093                    for (int i = 0; i < fields.length; i++) {
094                            Field field = fields[i];
095                            try {
096                                    Object constant = field.get(null);
097                                    if (value.equals(constant)) {
098                                            return clazz.getName() + "." + field.getName();
099                                    }
100                            }
101                            catch (Exception e) {
102                                    e.printStackTrace();
103                            }
104                    }
105                    return null;
106            }
107    
108            /**
109             * Gets the field value for the specified qualified field name.
110             */
111            public static Object getFieldValue(String qualifiedFieldName) {
112                    Class clazz;
113                    try {
114                            clazz = classForName(ClassUtils.qualifier(qualifiedFieldName));
115                    }
116                    catch (ClassNotFoundException cnfe) {
117                            return null;
118                    }
119                    try {
120                            return clazz.getField(ClassUtils.unqualify(qualifiedFieldName)).get(null);
121                    }
122                    catch (Exception e) {
123                            return null;
124                    }
125            }
126    
127            /**
128             * Load the class with the specified name.
129             *
130             * @param name
131             * @return The loaded class.
132             * @throws ClassNotFoundException
133             */
134            public static Class classForName(String name) throws ClassNotFoundException {
135                    try {
136                            return Thread.currentThread().getContextClassLoader().loadClass(name);
137                    }
138                    catch (Exception e) {
139                            return Class.forName(name);
140                    }
141            }
142    
143            public static Method findMethod(String methodName, Class clazz, Class[] parmTypes) {
144                    try {
145                            return clazz.getMethod(methodName, parmTypes);
146                    }
147                    catch (NoSuchMethodException e) {
148                            return null;
149                    }
150            }
151    
152            public static String unqualify(String qualifiedName) {
153                    return ClassUtils.unqualify(qualifiedName, '.');
154            }
155    
156            /**
157             * Returns the unqualified class name of the specified class.
158             *
159             * @param clazz the class to get the name for
160             * @return The unqualified, short name.
161             */
162            public static String unqualify(Class clazz) {
163                    return unqualify(clazz.getName());
164            }
165    
166            public static String unqualify(String qualifiedName, char separator) {
167                    return qualifiedName.substring(qualifiedName.lastIndexOf(separator) + 1);
168            }
169    
170            /**
171             * Returns the qualifier for a name separated by dots. The qualified part is
172             * everything up to the last dot separator.
173             *
174             * @param qualifiedName The qualified name.
175             * @return The qualifier portion.
176             */
177            public static String qualifier(String qualifiedName) {
178                    int loc = qualifiedName.lastIndexOf('.');
179                    if (loc < 0)
180                            return "";
181    
182                    return qualifiedName.substring(0, loc);
183            }
184    
185            /**
186             * Check if the given class represents a primitive array.
187             */
188            public static boolean isPrimitiveArray(Class clazz) {
189                    return (clazz.isArray() && clazz.getComponentType().isPrimitive());
190            }
191    
192            /**
193             * Does the provided bean class represent a simple scalar property? A simple
194             * scalar property is considered a value property; that is, it is not
195             * another bean. Examples include primitives, primitive wrappers, Enums, and
196             * Strings.
197             */
198            public static boolean isSimpleScalar(Class clazz) {
199                    return clazz.isPrimitive() || simpleClasses.contains(clazz) || LabeledEnum.class.isAssignableFrom(clazz);
200            }
201    
202            public static Method getStaticMethod(String name, Class locatorClass, Class[] args) {
203                    try {
204                            logger.debug("Attempting to get method '" + name + "' on class " + locatorClass + " with arguments '"
205                                            + StylerUtils.style(args) + "'");
206                            Method method = locatorClass.getDeclaredMethod(name, args);
207                            if ((method.getModifiers() & Modifier.STATIC) != 0)
208                                    return method;
209    
210                            return null;
211                    }
212                    catch (NoSuchMethodException e) {
213                            return null;
214                    }
215            }
216    
217            private static final Map primativeToWrapperMap = new HashMap();
218            static {
219                    primativeToWrapperMap.put(boolean.class, Boolean.class);
220                    primativeToWrapperMap.put(char.class, Character.class);
221                    primativeToWrapperMap.put(byte.class, Byte.class);
222                    primativeToWrapperMap.put(short.class, Short.class);
223                    primativeToWrapperMap.put(int.class, Integer.class);
224                    primativeToWrapperMap.put(long.class, Long.class);
225                    primativeToWrapperMap.put(float.class, Float.class);
226                    primativeToWrapperMap.put(double.class, Double.class);
227            }
228    
229            /**
230             * Gets the equivalent class to convert to if the given clazz is a
231             * primitive.
232             *
233             * @param clazz Class to examin.
234             * @return the class to convert to or the inputted clazz.
235             */
236            public static Class convertPrimitiveToWrapper(Class clazz) {
237                    if (clazz == null || !clazz.isPrimitive())
238                            return clazz;
239    
240                    return (Class) primativeToWrapperMap.get(clazz);
241            }
242    
243            /**
244             * Given a {@link Map}where the keys are {@link Class}es, search the map
245             * for the closest match of the key to the <tt>typeClass</tt>. This is
246             * extremely useful to support polymorphism (and an absolute requirement to
247             * find proxied classes where classes are acting as keys in a map).
248             * <p />
249             *
250             * For example: If the Map has keys of Number.class and String.class, using
251             * a <tt>typeClass</tt> of Long.class will find the Number.class entry and
252             * return its value.
253             * <p />
254             *
255             * When doing the search, it looks for the most exact match it can, giving
256             * preference to interfaces over class inheritance. As a performance
257             * optimiziation, if it finds a match it stores the derived match in the map
258             * so it does not have to be derived again.
259             *
260             * @param typeClass the kind of class to search for
261             * @param classMap the map where the keys are of type Class
262             * @return null only if it can't find any match
263             */
264            public static Object getValueFromMapForClass(final Class typeClass, final Map classMap) {
265                    Object val = classMap.get(typeClass);
266                    if (val == null) {
267                            // search through the interfaces first
268                            val = getValueFromMapForInterfaces(typeClass, classMap);
269    
270                            if (val == null) {
271                                    // now go up through the inheritance hierarchy
272                                    val = getValueFromMapForSuperClass(typeClass, classMap);
273                            }
274    
275                            if (val == null) {
276                                    // not found anywhere
277                                    if (logger.isDebugEnabled()) {
278                                            logger.debug("Could not find a definition for " + typeClass + " in " + classMap.keySet());
279                                    }
280                                    return null;
281                            }
282    
283                            // remember this so it doesn't have to be looked-up again
284                            classMap.put(typeClass, val);
285                            return val;
286                    }
287                    return val;
288            }
289    
290            private static Object getValueFromMapForInterfaces(final Class typeClass, final Map classMap) {
291                    final Class[] interfaces = typeClass.getInterfaces();
292    
293                    if (logger.isDebugEnabled()) {
294                            logger.debug("searching through " + Arrays.asList(interfaces));
295                    }
296    
297                    for (int i = 0; i < interfaces.length; i++) {
298                            final Class anInterface = interfaces[i];
299                            final Object val = classMap.get(anInterface);
300                            if (val != null) {
301                                    return val;
302                            }
303                    }
304    
305                    // not found, but now check the parent interfaces
306                    for (int i = 0; i < interfaces.length; i++) {
307                            final Class anInterface = interfaces[i];
308                            final Object val = getValueFromMapForInterfaces(anInterface, classMap);
309                            if (val != null) {
310                                    return val;
311                            }
312                    }
313    
314                    return null;
315            }
316    
317            private static Object getValueFromMapForSuperClass(final Class typeClass, final Map classMap) {
318                    Class superClass = typeClass.getSuperclass();
319                    while (superClass != null) {
320                            if (logger.isDebugEnabled()) {
321                                    logger.debug("searching for " + superClass);
322                            }
323                            Object val = classMap.get(superClass);
324                            if (val != null) {
325                                    return val;
326                            }
327    
328                            // try the interfaces
329                            val = getValueFromMapForInterfaces(superClass, classMap);
330                            if (val != null) {
331                                    return val;
332                            }
333    
334                            superClass = superClass.getSuperclass();
335                    }
336                    return null;
337            }
338    
339            /**
340             * Is the given name a property in the class? In other words, does it have a
341             * setter and/or a getter method?
342             *
343             * @param theClass the class to look for the property in
344             * @param propertyName the name of the property
345             *
346             * @return true if there is either a setter or a getter for the property
347             *
348             * @throws IllegalArgumentException if either argument is null
349             */
350            public static boolean isAProperty(Class theClass, String propertyName) {
351                    if (theClass == null)
352                            throw new IllegalArgumentException("theClass == null");
353                    if (propertyName == null)
354                            throw new IllegalArgumentException("propertyName == null");
355    
356                    if (getReadMethod(theClass, propertyName) != null)
357                            return true;
358                    if (getWriteMethod(theClass, propertyName) != null)
359                            return true;
360                    return false;
361            }
362    
363            private static Method getReadMethod(Class theClass, String propertyName) {
364                    // handle "embedded/dotted" properties
365                    if (propertyName.indexOf('.') > -1) {
366                            final int index = propertyName.indexOf('.');
367                            final String firstPropertyName = propertyName.substring(0, index);
368                            final String restOfPropertyName = propertyName.substring(index + 1, propertyName.length());
369                            final Class firstPropertyClass = getPropertyClass(theClass, firstPropertyName);
370                            return getReadMethod(firstPropertyClass, restOfPropertyName);
371                    }
372    
373                    final String getterName = "get" + propertyName.substring(0, 1).toUpperCase()
374                                    + (propertyName.length() == 1 ? "" : propertyName.substring(1));
375    
376                    Method method = getMethod(theClass, getterName);
377                    if (method == null) {
378                            final String isserName = "is" + propertyName.substring(0, 1).toUpperCase()
379                                            + (propertyName.length() == 1 ? "" : propertyName.substring(1));
380                            method = getMethod(theClass, isserName);
381                    }
382    
383                    if (method == null) {
384                            logger.info("There is not a getter for " + propertyName + " in " + theClass);
385                            return null;
386                    }
387    
388                    if (!Modifier.isPublic(method.getModifiers())) {
389                            logger.warn("The getter for " + propertyName + " in " + theClass + " is not public: " + method);
390                            return null;
391                    }
392    
393                    if (Void.TYPE.equals(method.getReturnType())) {
394                            logger.warn("The getter for " + propertyName + " in " + theClass + " returns void: " + method);
395                            return null;
396                    }
397    
398                    if (method.getName().startsWith("is")
399                                    && !(Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType()))) {
400                            logger.warn("The getter for " + propertyName + " in " + theClass
401                                            + " uses the boolean naming convention but is not boolean: " + method);
402                            return null;
403                    }
404    
405                    return method;
406            }
407    
408            private static Method getMethod(final Class theClass, final String getterName) {
409                    try {
410                            return theClass.getMethod(getterName, null);
411                    }
412                    catch (NoSuchMethodException e) {
413                            return null;
414                    }
415            }
416    
417            private static Method getWriteMethod(Class theClass, String propertyName) {
418                    // handle "embedded/dotted" properties
419                    if (propertyName.indexOf('.') > -1) {
420                            final int index = propertyName.indexOf('.');
421                            final String firstPropertyName = propertyName.substring(0, index);
422                            final String restOfPropertyName = propertyName.substring(index + 1, propertyName.length());
423                            final Class firstPropertyClass = getPropertyClass(theClass, firstPropertyName);
424                            return getWriteMethod(firstPropertyClass, restOfPropertyName);
425                    }
426    
427                    final String setterName = "set" + propertyName.substring(0, 1).toUpperCase()
428                                    + (propertyName.length() == 1 ? "" : propertyName.substring(1));
429    
430                    final Method[] methods = theClass.getMethods();
431                    for (int i = 0; i < methods.length; i++) {
432                            Method method = methods[i];
433                            if (setterName.equals(method.getName()) && method.getParameterTypes().length == 1) {
434    
435                                    if (!Modifier.isPublic(method.getModifiers())) {
436                                            logger.warn("The setter for " + propertyName + " in " + theClass + " is not public: " + method);
437                                            return null;
438                                    }
439    
440                                    if (!Void.TYPE.equals(method.getReturnType())) {
441                                            logger.warn("The setter for " + propertyName + " in " + theClass + " is not void: " + method);
442                                            return null;
443                                    }
444    
445                                    return method;
446                            }
447                    }
448    
449                    logger.info("There is not a setter for " + propertyName + " in " + theClass);
450                    return null;
451            }
452    
453            /**
454             * Returns the class of the property.
455             * <p />
456             *
457             * For example, getPropertyClass(JFrame.class, "size") would return the
458             * java.awt.Dimension class.
459             *
460             * @param parentClass the class to look for the property in
461             * @param propertyName the name of the property
462             *
463             * @return the class of the property; never null
464             *
465             * @throws IllegalArgumentException if either argument is null
466             * @throws IllegalArgumentException <tt>propertyName</tt> is not a
467             * property of <tt>parentClass</tt>
468             */
469            public static Class getPropertyClass(Class parentClass, String propertyName) throws IllegalArgumentException {
470                    if (parentClass == null)
471                            throw new IllegalArgumentException("theClass == null");
472                    if (propertyName == null)
473                            throw new IllegalArgumentException("propertyName == null");
474    
475                    final Method getterMethod = getReadMethod(parentClass, propertyName);
476                    if (getterMethod != null) {
477                            return getterMethod.getReturnType();
478                    }
479    
480                    final Method setterMethod = getWriteMethod(parentClass, propertyName);
481                    if (setterMethod != null) {
482                            return setterMethod.getParameterTypes()[0];
483                    }
484    
485                    throw new IllegalArgumentException(propertyName + " is not a property of " + parentClass);
486            }
487    
488    }