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 }