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 }