1 /*
   2  * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.javafx.fxml;
  27 
  28 import java.lang.reflect.InvocationTargetException;
  29 import java.lang.reflect.Method;
  30 import java.lang.reflect.Modifier;
  31 import java.lang.reflect.ParameterizedType;
  32 import java.lang.reflect.Type;
  33 import java.lang.reflect.TypeVariable;
  34 import java.math.BigDecimal;
  35 import java.math.BigInteger;
  36 import java.util.*;
  37 
  38 import java.lang.reflect.*;
  39 import java.security.AccessController;
  40 import java.security.PrivilegedAction;
  41 
  42 import javafx.beans.value.ObservableValue;
  43 import sun.reflect.misc.FieldUtil;
  44 import sun.reflect.misc.MethodUtil;
  45 import sun.reflect.misc.ReflectUtil;
  46 
  47 /**
  48  * Exposes Java Bean properties of an object via the {@link Map} interface.
  49  * A call to {@link Map#get(Object)} invokes the getter for the corresponding
  50  * property, and a call to {@link Map#put(Object, Object)} invokes the
  51  * property's setter. Appending a "Property" suffix to the key returns the
  52  * corresponding property model.
  53  */
  54 public class BeanAdapter extends AbstractMap<String, Object> {
  55     private final Object bean;
  56 
  57     private static class MethodCache {
  58         private final Map<String, List<Method>> methods;
  59         private final MethodCache nextClassCache;
  60 
  61         private MethodCache(Map<String, List<Method>> methods, MethodCache nextClassCache) {
  62             this.methods = methods;
  63             this.nextClassCache = nextClassCache;
  64         }
  65 
  66         private Method getMethod(String name, Class<?>... parameterTypes) {
  67             List<Method> namedMethods = methods.get(name);
  68             if (namedMethods != null) {
  69                 for (int i = 0; i < namedMethods.size(); i++) {
  70                     Method namedMethod = namedMethods.get(i);
  71                     if (namedMethod.getName().equals(name)
  72                             && Arrays.equals(namedMethod.getParameterTypes(), parameterTypes)) {
  73                         return namedMethod;
  74                     }
  75                 }
  76             }
  77 
  78             return nextClassCache != null ? nextClassCache.getMethod(name, parameterTypes) : null;
  79         }
  80 
  81     }
  82 
  83     private static final HashMap<Class<?>, MethodCache> globalMethodCache =
  84         new HashMap<>();
  85 
  86     private final MethodCache localCache;
  87 
  88     public static final String GET_PREFIX = "get";
  89     public static final String IS_PREFIX = "is";
  90     public static final String SET_PREFIX = "set";
  91     public static final String PROPERTY_SUFFIX = "Property";
  92 
  93     public static final String VALUE_OF_METHOD_NAME = "valueOf";
  94 
  95     /**
  96      * Creates a new Bean adapter.
  97      *
  98      * @param bean
  99      * The Bean object to wrap.
 100      */
 101     public BeanAdapter(Object bean) {
 102         this.bean = bean;
 103 
 104         localCache = getClassMethodCache(bean.getClass());
 105     }
 106 
 107     private static MethodCache getClassMethodCache(final Class<?> type) {
 108         if (type == Object.class) {
 109             return null;
 110         }
 111         MethodCache classMethodCache;
 112         synchronized (globalMethodCache) {
 113             if ((classMethodCache = globalMethodCache.get(type)) != null) {
 114                 return classMethodCache;
 115             }
 116             Map<String, List<Method>> classMethods = new HashMap<>();
 117 
 118             ReflectUtil.checkPackageAccess(type);
 119             if (Modifier.isPublic(type.getModifiers())) {
 120                 // only interested in public methods in public classes in
 121                 // non-restricted packages
 122                 final Method[] declaredMethods =
 123                         AccessController.doPrivileged(
 124                                 new PrivilegedAction<Method[]>() {
 125                                     @Override
 126                                     public Method[] run() {
 127                                         return type.getDeclaredMethods();
 128                                     }
 129                                 });
 130                 for (int i = 0; i < declaredMethods.length; i++) {
 131                     Method method = declaredMethods[i];
 132                     int modifiers = method.getModifiers();
 133 
 134                     if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers)) {
 135                         String name = method.getName();
 136                         List<Method> namedMethods = classMethods.get(name);
 137 
 138                         if (namedMethods == null) {
 139                             namedMethods = new ArrayList<>();
 140                             classMethods.put(name, namedMethods);
 141                         }
 142 
 143                         namedMethods.add(method);
 144                     }
 145                 }
 146             }
 147             MethodCache cache = new MethodCache(classMethods, getClassMethodCache(type.getSuperclass()));
 148             globalMethodCache.put(type, cache);
 149             return cache;
 150         }
 151     }
 152 
 153     /**
 154      * Returns the Bean object this adapter wraps.
 155      *
 156      * @return
 157      * The Bean object, or <tt>null</tt> if no Bean has been set.
 158      */
 159     public Object getBean() {
 160         return bean;
 161     }
 162 
 163     private Method getGetterMethod(String key) {
 164         Method getterMethod = localCache.getMethod(getMethodName(GET_PREFIX, key));
 165 
 166         if (getterMethod == null) {
 167             getterMethod = localCache.getMethod(getMethodName(IS_PREFIX, key));
 168         }
 169 
 170         return getterMethod;
 171     }
 172 
 173     private Method getSetterMethod(String key) {
 174         Class<?> type = getType(key);
 175 
 176         if (type == null) {
 177             throw new UnsupportedOperationException("Cannot determine type for property.");
 178         }
 179 
 180         return localCache.getMethod(getMethodName(SET_PREFIX, key), type);
 181     }
 182 
 183     private static String getMethodName(String prefix, String key) {
 184         return prefix + Character.toUpperCase(key.charAt(0)) + key.substring(1);
 185     }
 186 
 187     /**
 188      * Invokes the getter method for the given property.
 189      *
 190      * @param key
 191      * The property name.
 192      *
 193      * @return
 194      * The value returned by the method, or <tt>null</tt> if no such method
 195      * exists.
 196      */
 197     @Override
 198     public Object get(Object key) {
 199         if (key == null) {
 200             throw new NullPointerException();
 201         }
 202 
 203         return get(key.toString());
 204     }
 205 
 206     private Object get(String key) {
 207         Method getterMethod = key.endsWith(PROPERTY_SUFFIX) ? localCache.getMethod(key) : getGetterMethod(key);
 208 
 209         Object value;
 210         if (getterMethod != null) {
 211             try {
 212                 value = ModuleHelper.invoke(getterMethod, bean, (Object[]) null);
 213             } catch (IllegalAccessException exception) {
 214                 throw new RuntimeException(exception);
 215             } catch (InvocationTargetException exception) {
 216                 throw new RuntimeException(exception);
 217             }
 218         } else {
 219             value = null;
 220         }
 221 
 222         return value;
 223     }
 224 
 225     /**
 226      * Invokes a setter method for the given property. The
 227      * {@link #coerce(Object, Class)} method is used as needed to attempt to
 228      * convert a given value to the property type, as defined by the return
 229      * value of the getter method.
 230      *
 231      * @param key
 232      * The property name.
 233      *
 234      * @param value
 235      * The new property value.
 236      *
 237      * @return
 238      * Returns <tt>null</tt>, since returning the previous value would require
 239      * an unnecessary call to the getter method.
 240      *
 241      * @throws PropertyNotFoundException
 242      * If the given property does not exist or is read-only.
 243      */
 244     @Override
 245     public Object put(String key, Object value) {
 246         if (key == null) {
 247             throw new NullPointerException();
 248         }
 249 
 250         Method setterMethod = getSetterMethod(key);
 251 
 252         if (setterMethod == null) {
 253             throw new PropertyNotFoundException("Property \"" + key + "\" does not exist"
 254                 + " or is read-only.");
 255         }
 256 
 257         try {
 258             ModuleHelper.invoke(setterMethod, bean, new Object[] { coerce(value, getType(key)) });
 259         } catch (IllegalAccessException exception) {
 260             throw new RuntimeException(exception);
 261         } catch (InvocationTargetException exception) {
 262             throw new RuntimeException(exception);
 263         }
 264 
 265         return null;
 266     }
 267 
 268     /**
 269      * Verifies the existence of a property.
 270      *
 271      * @param key
 272      * The property name.
 273      *
 274      * @return
 275      * <tt>true</tt> if the property exists; <tt>false</tt>, otherwise.
 276      */
 277     @Override
 278     public boolean containsKey(Object key) {
 279         if (key == null) {
 280             throw new NullPointerException();
 281         }
 282 
 283         return getType(key.toString()) != null;
 284     }
 285 
 286     @Override
 287     public Set<Entry<String, Object>> entrySet() {
 288         throw new UnsupportedOperationException();
 289     }
 290 
 291     /**
 292      * Tests the mutability of a property.
 293      *
 294      * @param key
 295      * The property name.
 296      *
 297      * @return
 298      * <tt>true</tt> if the property is read-only; <tt>false</tt>, otherwise.
 299      */
 300     public boolean isReadOnly(String key) {
 301         if (key == null) {
 302             throw new NullPointerException();
 303         }
 304 
 305         return getSetterMethod(key) == null;
 306     }
 307 
 308     /**
 309      * Returns the property model for the given property.
 310      *
 311      * @param key
 312      * The property name.
 313      *
 314      * @return
 315      * The named property model, or <tt>null</tt> if no such property exists.
 316      */
 317     @SuppressWarnings("unchecked")
 318     public <T> ObservableValue<T> getPropertyModel(String key) {
 319         if (key == null) {
 320             throw new NullPointerException();
 321         }
 322 
 323         return (ObservableValue<T>)get(key + BeanAdapter.PROPERTY_SUFFIX);
 324     }
 325 
 326     /**
 327      * Returns the type of a property.
 328      *
 329      * @param key
 330      * The property name.
 331      */
 332     public Class<?> getType(String key) {
 333         if (key == null) {
 334             throw new NullPointerException();
 335         }
 336 
 337         Method getterMethod = getGetterMethod(key);
 338 
 339         return (getterMethod == null) ? null : getterMethod.getReturnType();
 340     }
 341 
 342     /**
 343      * Returns the generic type of a property.
 344      *
 345      * @param key
 346      * The property name.
 347      */
 348     public Type getGenericType(String key) {
 349         if (key == null) {
 350             throw new NullPointerException();
 351         }
 352 
 353         Method getterMethod = getGetterMethod(key);
 354 
 355         return (getterMethod == null) ? null : getterMethod.getGenericReturnType();
 356     }
 357 
 358     @Override
 359     public boolean equals(Object object) {
 360         boolean equals = false;
 361 
 362         if (object instanceof BeanAdapter) {
 363             BeanAdapter beanAdapter = (BeanAdapter)object;
 364             equals = (bean == beanAdapter.bean);
 365         }
 366 
 367         return equals;
 368     }
 369 
 370     @Override
 371     public int hashCode() {
 372         return (bean == null) ? -1 : bean.hashCode();
 373     }
 374 
 375     /**
 376      * Coerces a value to a given type.
 377      *
 378      * @param value
 379      * @param type
 380      *
 381      * @return
 382      * The coerced value.
 383      */
 384     @SuppressWarnings("unchecked")
 385     public static <T> T coerce(Object value, Class<? extends T> type) {
 386         if (type == null) {
 387             throw new NullPointerException();
 388         }
 389 
 390         Object coercedValue = null;
 391 
 392         if (value == null) {
 393             // Null values can only be coerced to null
 394             coercedValue = null;
 395         } else if (type.isAssignableFrom(value.getClass())) {
 396             // Value doesn't require coercion
 397             coercedValue = value;
 398         } else if (type == Boolean.class
 399             || type == Boolean.TYPE) {
 400             coercedValue = Boolean.valueOf(value.toString());
 401         } else if (type == Character.class
 402             || type == Character.TYPE) {
 403             coercedValue = value.toString().charAt(0);
 404         } else if (type == Byte.class
 405             || type == Byte.TYPE) {
 406             if (value instanceof Number) {
 407                 coercedValue = ((Number)value).byteValue();
 408             } else {
 409                 coercedValue = Byte.valueOf(value.toString());
 410             }
 411         } else if (type == Short.class
 412             || type == Short.TYPE) {
 413             if (value instanceof Number) {
 414                 coercedValue = ((Number)value).shortValue();
 415             } else {
 416                 coercedValue = Short.valueOf(value.toString());
 417             }
 418         } else if (type == Integer.class
 419             || type == Integer.TYPE) {
 420             if (value instanceof Number) {
 421                 coercedValue = ((Number)value).intValue();
 422             } else {
 423                 coercedValue = Integer.valueOf(value.toString());
 424             }
 425         } else if (type == Long.class
 426             || type == Long.TYPE) {
 427             if (value instanceof Number) {
 428                 coercedValue = ((Number)value).longValue();
 429             } else {
 430                 coercedValue = Long.valueOf(value.toString());
 431             }
 432         } else if (type == BigInteger.class) {
 433             if (value instanceof Number) {
 434                 coercedValue = BigInteger.valueOf(((Number)value).longValue());
 435             } else {
 436                 coercedValue = new BigInteger(value.toString());
 437             }
 438         } else if (type == Float.class
 439             || type == Float.TYPE) {
 440             if (value instanceof Number) {
 441                 coercedValue = ((Number)value).floatValue();
 442             } else {
 443                 coercedValue = Float.valueOf(value.toString());
 444             }
 445         } else if (type == Double.class
 446             || type == Double.TYPE) {
 447             if (value instanceof Number) {
 448                 coercedValue = ((Number)value).doubleValue();
 449             } else {
 450                 coercedValue = Double.valueOf(value.toString());
 451             }
 452         } else if (type == Number.class) {
 453             String number = value.toString();
 454             if (number.contains(".")) {
 455                 coercedValue = Double.valueOf(number);
 456             } else {
 457                 coercedValue = Long.valueOf(number);
 458             }
 459         } else if (type == BigDecimal.class) {
 460             if (value instanceof Number) {
 461                 coercedValue = BigDecimal.valueOf(((Number)value).doubleValue());
 462             } else {
 463                 coercedValue = new BigDecimal(value.toString());
 464             }
 465         } else if (type == Class.class) {
 466             try {
 467                 final String className = value.toString();
 468                 ReflectUtil.checkPackageAccess(className);
 469                 final ClassLoader cl = Thread.currentThread().getContextClassLoader();
 470                 coercedValue = Class.forName(
 471                         className,
 472                         false,
 473                         cl);
 474             } catch (ClassNotFoundException exception) {
 475                 throw new IllegalArgumentException(exception);
 476             }
 477         } else {
 478             Class<?> valueType = value.getClass();
 479             Method valueOfMethod = null;
 480 
 481             while (valueOfMethod == null
 482                 && valueType != null) {
 483                 try {
 484                     ReflectUtil.checkPackageAccess(type);
 485                     valueOfMethod = type.getDeclaredMethod(VALUE_OF_METHOD_NAME, valueType);
 486                 } catch (NoSuchMethodException exception) {
 487                     // No-op
 488                 }
 489 
 490                 if (valueOfMethod == null) {
 491                     valueType = valueType.getSuperclass();
 492                 }
 493             }
 494 
 495             if (valueOfMethod == null) {
 496                 throw new IllegalArgumentException("Unable to coerce " + value + " to " + type + ".");
 497             }
 498 
 499             if (type.isEnum()
 500                 && value instanceof String
 501                 && Character.isLowerCase(((String)value).charAt(0))) {
 502                 value = toAllCaps((String)value);
 503             }
 504 
 505             try {
 506                 coercedValue = ModuleHelper.invoke(valueOfMethod, null, new Object[] { value });
 507             } catch (IllegalAccessException exception) {
 508                 throw new RuntimeException(exception);
 509             } catch (InvocationTargetException exception) {
 510                 throw new RuntimeException(exception);
 511             } catch (SecurityException exception) {
 512                 throw new RuntimeException(exception);
 513             }
 514         }
 515 
 516         return (T)coercedValue;
 517     }
 518 
 519     /**
 520      * Invokes the static getter method for the given property.
 521      *
 522      * @param target
 523      * The object to which the property is attached.
 524      *
 525      * @param sourceType
 526      * The class that defines the property.
 527      *
 528      * @param key
 529      * The property name.
 530      *
 531      * @return
 532      * The value returned by the method, or <tt>null</tt> if no such method
 533      * exists.
 534      */
 535     @SuppressWarnings("unchecked")
 536     public static <T> T get(Object target, Class<?> sourceType, String key) {
 537         T value = null;
 538 
 539         Class<?> targetType = target.getClass();
 540         Method getterMethod = getStaticGetterMethod(sourceType, key, targetType);
 541 
 542         if (getterMethod != null) {
 543             try {
 544                 value = (T) ModuleHelper.invoke(getterMethod, null, new Object[] { target } );
 545             } catch (InvocationTargetException exception) {
 546                 throw new RuntimeException(exception);
 547             } catch (IllegalAccessException exception) {
 548                 throw new RuntimeException(exception);
 549             }
 550         }
 551 
 552         return value;
 553     }
 554 
 555     /**
 556      * Invokes a static setter method for the given property. If the value is
 557      * <tt>null</tt> or there is no explicit setter for a given type, the
 558      * {@link #coerce(Object, Class)} method is used to attempt to convert the
 559      * value to the actual property type (defined by the return value of the
 560      * getter method).
 561      *
 562      * @param target
 563      * The object to which the property is or will be attached.
 564      *
 565      * @param sourceType
 566      * The class that defines the property.
 567      *
 568      * @param key
 569      * The property name.
 570      *
 571      * @param value
 572      * The new property value.
 573      *
 574      * @throws PropertyNotFoundException
 575      * If the given static property does not exist or is read-only.
 576      */
 577     public static void put(Object target, Class<?> sourceType, String key, Object value) {
 578         Class<?> targetType = target.getClass();
 579 
 580         Method setterMethod = null;
 581         if (value != null) {
 582             setterMethod = getStaticSetterMethod(sourceType, key, value.getClass(), targetType);
 583         }
 584 
 585         if (setterMethod == null) {
 586             // Get the property type and attempt to coerce the value to it
 587             Class<?> propertyType = getType(sourceType, key, targetType);
 588 
 589             if (propertyType != null) {
 590                 setterMethod = getStaticSetterMethod(sourceType, key, propertyType, targetType);
 591                 value = coerce(value, propertyType);
 592             }
 593         }
 594 
 595         if (setterMethod == null) {
 596             throw new PropertyNotFoundException("Static property \"" + key + "\" does not exist"
 597                 + " or is read-only.");
 598         }
 599 
 600         // Invoke the setter
 601         try {
 602             ModuleHelper.invoke(setterMethod, null, new Object[] { target, value });
 603         } catch (InvocationTargetException exception) {
 604             throw new RuntimeException(exception);
 605         } catch (IllegalAccessException exception) {
 606             throw new RuntimeException(exception);
 607         }
 608     }
 609 
 610     /**
 611      * Tests the existence of a static property.
 612      *
 613      * @param sourceType
 614      * The class that defines the property.
 615      *
 616      * @param key
 617      * The property name.
 618      *
 619      * @param targetType
 620      * The type of the object to which the property applies.
 621      *
 622      * @return
 623      * <tt>true</tt> if the property exists; <tt>false</tt>, otherwise.
 624      */
 625     public static boolean isDefined(Class<?> sourceType, String key, Class<?> targetType) {
 626         return (getStaticGetterMethod(sourceType, key, targetType) != null);
 627     }
 628 
 629     /**
 630      * Returns the type of a static property.
 631      *
 632      * @param sourceType
 633      * The class that defines the property.
 634      *
 635      * @param key
 636      * The property name.
 637      *
 638      * @param targetType
 639      * The type of the object to which the property applies.
 640      */
 641     public static Class<?> getType(Class<?> sourceType, String key, Class<?> targetType) {
 642         Method getterMethod = getStaticGetterMethod(sourceType, key, targetType);
 643         return (getterMethod == null) ? null : getterMethod.getReturnType();
 644     }
 645 
 646     /**
 647      * Returns the generic type of a static property.
 648      *
 649      * @param sourceType
 650      * The class that defines the property.
 651      *
 652      * @param key
 653      * The property name.
 654      *
 655      * @param targetType
 656      * The type of the object to which the property applies.
 657      */
 658     public static Type getGenericType(Class<?> sourceType, String key, Class<?> targetType) {
 659         Method getterMethod = getStaticGetterMethod(sourceType, key, targetType);
 660         return (getterMethod == null) ? null : getterMethod.getGenericReturnType();
 661     }
 662 
 663     /**
 664      * Determines the type of a list item.
 665      *
 666      * @param listType
 667      */
 668     public static Class<?> getListItemType(Type listType) {
 669         Type itemType = getGenericListItemType(listType);
 670 
 671         if (itemType instanceof ParameterizedType) {
 672             itemType = ((ParameterizedType)itemType).getRawType();
 673         }
 674 
 675         return (Class<?>)itemType;
 676     }
 677 
 678     /**
 679      * Determines the type of a map value.
 680      *
 681      * @param mapType
 682      */
 683     public static Class<?> getMapValueType(Type mapType) {
 684         Type valueType = getGenericMapValueType(mapType);
 685 
 686         if (valueType instanceof ParameterizedType) {
 687             valueType = ((ParameterizedType)valueType).getRawType();
 688         }
 689 
 690         return (Class<?>)valueType;
 691     }
 692 
 693     /**
 694      * Determines the type of a list item.
 695      *
 696      * @param listType
 697      */
 698     public static Type getGenericListItemType(Type listType) {
 699         Type itemType = null;
 700 
 701         Type parentType = listType;
 702         while (parentType != null) {
 703             if (parentType instanceof ParameterizedType) {
 704                 ParameterizedType parameterizedType = (ParameterizedType)parentType;
 705                 Class<?> rawType = (Class<?>)parameterizedType.getRawType();
 706 
 707                 if (List.class.isAssignableFrom(rawType)) {
 708                     itemType = parameterizedType.getActualTypeArguments()[0];
 709                 }
 710 
 711                 break;
 712             }
 713 
 714             Class<?> classType = (Class<?>)parentType;
 715             Type[] genericInterfaces = classType.getGenericInterfaces();
 716 
 717             for (int i = 0; i < genericInterfaces.length; i++) {
 718                 Type genericInterface = genericInterfaces[i];
 719 
 720                 if (genericInterface instanceof ParameterizedType) {
 721                     ParameterizedType parameterizedType = (ParameterizedType)genericInterface;
 722                     Class<?> interfaceType = (Class<?>)parameterizedType.getRawType();
 723 
 724                     if (List.class.isAssignableFrom(interfaceType)) {
 725                         itemType = parameterizedType.getActualTypeArguments()[0];
 726                         break;
 727                     }
 728                 }
 729             }
 730 
 731             if (itemType != null) {
 732                 break;
 733             }
 734 
 735             parentType = classType.getGenericSuperclass();
 736         }
 737 
 738         if (itemType != null && itemType instanceof TypeVariable<?>) {
 739             itemType = Object.class;
 740         }
 741 
 742         return itemType;
 743     }
 744 
 745     /**
 746      * Determines the type of a map value.
 747      *
 748      * @param mapType
 749      */
 750     public static Type getGenericMapValueType(Type mapType) {
 751         Type valueType = null;
 752 
 753         Type parentType = mapType;
 754         while (parentType != null) {
 755             if (parentType instanceof ParameterizedType) {
 756                 ParameterizedType parameterizedType = (ParameterizedType)parentType;
 757                 Class<?> rawType = (Class<?>)parameterizedType.getRawType();
 758 
 759                 if (Map.class.isAssignableFrom(rawType)) {
 760                     valueType = parameterizedType.getActualTypeArguments()[1];
 761                 }
 762 
 763                 break;
 764             }
 765 
 766             Class<?> classType = (Class<?>)parentType;
 767             Type[] genericInterfaces = classType.getGenericInterfaces();
 768 
 769             for (int i = 0; i < genericInterfaces.length; i++) {
 770                 Type genericInterface = genericInterfaces[i];
 771 
 772                 if (genericInterface instanceof ParameterizedType) {
 773                     ParameterizedType parameterizedType = (ParameterizedType)genericInterface;
 774                     Class<?> interfaceType = (Class<?>)parameterizedType.getRawType();
 775 
 776                     if (Map.class.isAssignableFrom(interfaceType)) {
 777                         valueType = parameterizedType.getActualTypeArguments()[1];
 778                         break;
 779                     }
 780                 }
 781             }
 782 
 783             if (valueType != null) {
 784                 break;
 785             }
 786 
 787             parentType = classType.getGenericSuperclass();
 788         }
 789 
 790         if (valueType != null && valueType instanceof TypeVariable<?>) {
 791             valueType = Object.class;
 792         }
 793 
 794         return valueType;
 795     }
 796 
 797     /**
 798      * Returns the value of a named constant.
 799      *
 800      * @param type
 801      * The type that defines the constant.
 802      *
 803      * @param name
 804      * The name of the constant.
 805      */
 806     public static Object getConstantValue(Class<?> type, String name) {
 807         if (type == null) {
 808             throw new IllegalArgumentException();
 809         }
 810 
 811         if (name == null) {
 812             throw new IllegalArgumentException();
 813         }
 814 
 815         Field field;
 816         try {
 817             field = FieldUtil.getField(type, name);
 818         } catch (NoSuchFieldException exception) {
 819             throw new IllegalArgumentException(exception);
 820         }
 821 
 822         int fieldModifiers = field.getModifiers();
 823         if ((fieldModifiers & Modifier.STATIC) == 0
 824             || (fieldModifiers & Modifier.FINAL) == 0) {
 825             throw new IllegalArgumentException("Field is not a constant.");
 826         }
 827 
 828         Object value;
 829         try {
 830             value = field.get(null);
 831         } catch (IllegalAccessException exception) {
 832             throw new IllegalArgumentException(exception);
 833         }
 834 
 835         return value;
 836     }
 837 
 838     private static Method getStaticGetterMethod(Class<?> sourceType, String key,
 839         Class<?> targetType) {
 840         if (sourceType == null) {
 841             throw new NullPointerException();
 842         }
 843 
 844         if (key == null) {
 845             throw new NullPointerException();
 846         }
 847 
 848         Method method = null;
 849 
 850         if (targetType != null) {
 851             key = Character.toUpperCase(key.charAt(0)) + key.substring(1);
 852 
 853             String getMethodName = GET_PREFIX + key;
 854             String isMethodName = IS_PREFIX + key;
 855 
 856             try {
 857                 method = MethodUtil.getMethod(sourceType, getMethodName, new Class[] { targetType });
 858             } catch (NoSuchMethodException exception) {
 859                 // No-op
 860             }
 861 
 862             if (method == null) {
 863                 try {
 864                     method = MethodUtil.getMethod(sourceType, isMethodName, new Class[] { targetType });
 865                 } catch (NoSuchMethodException exception) {
 866                     // No-op
 867                 }
 868             }
 869 
 870             // Check for interfaces
 871             if (method == null) {
 872                 Class<?>[] interfaces = targetType.getInterfaces();
 873                 for (int i = 0; i < interfaces.length; i++) {
 874                     try {
 875                         method = MethodUtil.getMethod(sourceType, getMethodName, new Class[] { interfaces[i] });
 876                     } catch (NoSuchMethodException exception) {
 877                         // No-op
 878                     }
 879 
 880                     if (method == null) {
 881                         try {
 882                             method = MethodUtil.getMethod(sourceType, isMethodName, new Class[] { interfaces[i] });
 883                         } catch (NoSuchMethodException exception) {
 884                             // No-op
 885                         }
 886                     }
 887 
 888                     if (method != null) {
 889                         break;
 890                     }
 891                 }
 892             }
 893 
 894             if (method == null) {
 895                 method = getStaticGetterMethod(sourceType, key, targetType.getSuperclass());
 896             }
 897         }
 898 
 899         return method;
 900     }
 901 
 902     private static Method getStaticSetterMethod(Class<?> sourceType, String key,
 903         Class<?> valueType, Class<?> targetType) {
 904         if (sourceType == null) {
 905             throw new NullPointerException();
 906         }
 907 
 908         if (key == null) {
 909             throw new NullPointerException();
 910         }
 911 
 912         if (valueType == null) {
 913             throw new NullPointerException();
 914         }
 915 
 916         Method method = null;
 917 
 918         if (targetType != null) {
 919             key = Character.toUpperCase(key.charAt(0)) + key.substring(1);
 920 
 921             String setMethodName = SET_PREFIX + key;
 922             try {
 923                 method = MethodUtil.getMethod(sourceType, setMethodName, new Class[] { targetType, valueType });
 924             } catch (NoSuchMethodException exception) {
 925                 // No-op
 926             }
 927 
 928             // Check for interfaces
 929             if (method == null) {
 930                 Class<?>[] interfaces = targetType.getInterfaces();
 931                 for (int i = 0; i < interfaces.length; i++) {
 932                     try {
 933                         method = MethodUtil.getMethod(sourceType, setMethodName, new Class[] { interfaces[i], valueType });
 934                     } catch (NoSuchMethodException exception) {
 935                         // No-op
 936                     }
 937 
 938                     if (method != null) {
 939                         break;
 940                     }
 941                 }
 942             }
 943 
 944             if (method == null) {
 945                 method = getStaticSetterMethod(sourceType, key, valueType, targetType.getSuperclass());
 946             }
 947         }
 948 
 949         return method;
 950     }
 951 
 952     private static String toAllCaps(String value) {
 953         if (value == null) {
 954             throw new NullPointerException();
 955         }
 956 
 957         StringBuilder allCapsBuilder = new StringBuilder();
 958 
 959         for (int i = 0, n = value.length(); i < n; i++) {
 960             char c = value.charAt(i);
 961 
 962             if (Character.isUpperCase(c)) {
 963                 allCapsBuilder.append('_');
 964             }
 965 
 966             allCapsBuilder.append(Character.toUpperCase(c));
 967         }
 968 
 969         return allCapsBuilder.toString();
 970     }
 971 }