001 /* 002 * Created on Jun 28, 2010 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 005 * the License. You may obtain a copy of the License at 006 * 007 * http://www.apache.org/licenses/LICENSE-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 010 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 011 * specific language governing permissions and limitations under the License. 012 * 013 * Copyright @2010-2011 the original author or authors. 014 */ 015 package org.fest.util; 016 017 import static java.lang.String.format; 018 import static java.lang.reflect.Modifier.isPublic; 019 import static java.util.Locale.ENGLISH; 020 import static org.fest.util.Strings.*; 021 022 import java.beans.*; 023 import java.lang.reflect.Method; 024 025 /** 026 * Utility methods related to <a 027 * href="http://java.sun.com/docs/books/tutorial/javabeans/introspection/index.html">JavaBeans Introspection</a>. 028 * 029 * @author Alex Ruiz 030 */ 031 public final class Introspection { 032 033 /** 034 * Returns a <code>{@link PropertyDescriptor}</code> for a property matching the given name in the given object. 035 * @param propertyName the given property name. 036 * @param target the given object. 037 * @return a {@code PropertyDescriptor} for a property matching the given name in the given object. 038 * @throws NullPointerException if the given property name is {@code null}. 039 * @throws IllegalArgumentException if the given property name is empty. 040 * @throws NullPointerException if the given object is {@code null}. 041 * @throws IntrospectionError if a matching property cannot be found or accessed. 042 */ 043 public static PropertyDescriptor descriptorForProperty(String propertyName, Object target) { 044 validate(propertyName, target); 045 BeanInfo beanInfo = null; 046 Class<?> type = target.getClass(); 047 try { 048 beanInfo = Introspector.getBeanInfo(type); 049 } catch (Exception e) { 050 throw new IntrospectionError(format("Unable to get BeanInfo for type %s", type.getName()), e); 051 } 052 for (PropertyDescriptor d : beanInfo.getPropertyDescriptors()) 053 if (propertyName.equals(d.getName())) return d; 054 throw new IntrospectionError(propertyNotFoundErrorMessage(propertyName, target)); 055 } 056 057 private static String propertyNotFoundErrorMessage(String propertyName, Object target) { 058 String targetTypeName = target.getClass().getName(); 059 String property = quote(propertyName); 060 // no PropertyDescriptor found, try to give user a precise error message 061 Method getter = beanGetter(propertyName, target); 062 if (getter == null) 063 return format("No getter for property %s in %s", property, targetTypeName); 064 if (!isPublic(getter.getModifiers())) 065 return format("No public getter for property %s in %s", property, targetTypeName); 066 // generic message 067 return format("Unable to find property %s in %s", property, targetTypeName); 068 } 069 070 private static Method beanGetter(String propertyName, Object target) { 071 validate(propertyName, target); 072 String capitalized = propertyName.substring(0, 1).toUpperCase(ENGLISH) + propertyName.substring(1); 073 // try to find getProperty 074 Method getter = findMethod("get" + capitalized, target); 075 if (getter != null) return getter; 076 // try to find isProperty for boolean properties 077 return findMethod("is" + capitalized, target); 078 } 079 080 private static Method findMethod(String name, Object target) { 081 // TODO walk class hierarchy to check if any superclass declares the method we are looking for. 082 try { 083 return target.getClass().getDeclaredMethod(name); 084 } catch (Throwable t) { 085 return null; 086 } 087 } 088 089 private static void validate(String propertyName, Object target) { 090 if (propertyName == null) throw new NullPointerException("The property name should not be null"); 091 if (isEmpty(propertyName)) throw new IllegalArgumentException("The property name should not be empty"); 092 if (target == null) throw new NullPointerException("The target object should not be null"); 093 } 094 095 private Introspection() {} 096 }