001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.beanutils; 018 019import java.beans.IntrospectionException; 020import java.beans.PropertyDescriptor; 021import java.lang.reflect.Method; 022import java.util.Locale; 023 024import org.apache.commons.logging.Log; 025import org.apache.commons.logging.LogFactory; 026 027/** 028 * <p> 029 * An implementation of the <code>BeanIntrospector</code> interface which can 030 * detect write methods for properties used in fluent API scenario. 031 * </p> 032 * <p> 033 * A <em>fluent API</em> allows setting multiple properties using a single 034 * statement by supporting so-called <em>method chaining</em>: Methods for 035 * setting a property value do not return <b>void</b>, but an object which can 036 * be called for setting another property. An example of such a fluent API could 037 * look as follows: 038 * 039 * <pre> 040 * public class FooBuilder { 041 * public FooBuilder setFooProperty1(String value) { 042 * ... 043 * return this; 044 * } 045 * 046 * public FooBuilder setFooProperty2(int value) { 047 * ... 048 * return this; 049 * } 050 * } 051 * </pre> 052 * 053 * Per default, <code>PropertyUtils</code> does not detect methods like this 054 * because, having a non-<b>void</b> return type, they violate the Java Beans 055 * specification. 056 * </p> 057 * <p> 058 * This class is more tolerant with regards to the return type of a set method. 059 * It basically iterates over all methods of a class and filters them for a 060 * configurable prefix (the default prefix is <code>set</code>). It then 061 * generates corresponding <code>PropertyDescriptor</code> objects for the 062 * methods found which use these methods as write methods. 063 * </p> 064 * <p> 065 * An instance of this class is intended to collaborate with a 066 * {@link DefaultBeanIntrospector} object. So best results are achieved by 067 * adding this instance as custom {@code BeanIntrospector} after the 068 * <code>DefaultBeanIntrospector</code> object. Then default introspection finds 069 * read-only properties because it does not detect the write methods with a 070 * non-<b>void</b> return type. {@code FluentPropertyBeanIntrospector} 071 * completes the descriptors for these properties by setting the correct write 072 * method. 073 * </p> 074 * 075 * @version $Id: FluentPropertyBeanIntrospector.html 893732 2014-01-11 19:35:15Z oheger $ 076 * @since 1.9 077 */ 078public class FluentPropertyBeanIntrospector implements BeanIntrospector { 079 /** The default prefix for write methods. */ 080 public static final String DEFAULT_WRITE_METHOD_PREFIX = "set"; 081 082 /** The logger. */ 083 private final Log log = LogFactory.getLog(getClass()); 084 085 /** The prefix of write methods to search for. */ 086 private final String writeMethodPrefix; 087 088 /** 089 * 090 * Creates a new instance of <code>FluentPropertyBeanIntrospector</code> and 091 * initializes it with the prefix for write methods used by the classes to 092 * be inspected. 093 * 094 * @param writePrefix the prefix for write methods (must not be <b>null</b>) 095 * @throws IllegalArgumentException if the prefix is <b>null</b> 096 */ 097 public FluentPropertyBeanIntrospector(String writePrefix) { 098 if (writePrefix == null) { 099 throw new IllegalArgumentException( 100 "Prefix for write methods must not be null!"); 101 } 102 writeMethodPrefix = writePrefix; 103 } 104 105 /** 106 * 107 * Creates a new instance of <code>FluentPropertyBeanIntrospector</code> and 108 * sets the default prefix for write methods. 109 */ 110 public FluentPropertyBeanIntrospector() { 111 this(DEFAULT_WRITE_METHOD_PREFIX); 112 } 113 114 /** 115 * Returns the prefix for write methods this instance scans for. 116 * 117 * @return the prefix for write methods 118 */ 119 public String getWriteMethodPrefix() { 120 return writeMethodPrefix; 121 } 122 123 /** 124 * Performs introspection. This method scans the current class's methods for 125 * property write methods which have not been discovered by default 126 * introspection. 127 * 128 * @param icontext the introspection context 129 * @throws IntrospectionException if an error occurs 130 */ 131 public void introspect(IntrospectionContext icontext) 132 throws IntrospectionException { 133 for (Method m : icontext.getTargetClass().getMethods()) { 134 if (m.getName().startsWith(getWriteMethodPrefix())) { 135 String propertyName = propertyName(m); 136 PropertyDescriptor pd = icontext 137 .getPropertyDescriptor(propertyName); 138 try { 139 if (pd == null) { 140 icontext.addPropertyDescriptor(createFluentPropertyDescritor( 141 m, propertyName)); 142 } else if (pd.getWriteMethod() == null) { 143 pd.setWriteMethod(m); 144 } 145 } catch (IntrospectionException e) { 146 log.warn("Error when creating PropertyDescriptor for " + m 147 + "! Ignoring this property.", e); 148 } 149 } 150 } 151 } 152 153 /** 154 * Derives the name of a property from the given set method. 155 * 156 * @param m the method 157 * @return the corresponding property name 158 */ 159 private String propertyName(Method m) { 160 String methodName = m.getName().substring( 161 getWriteMethodPrefix().length()); 162 return (methodName.length() > 1) ? Character.toLowerCase(methodName 163 .charAt(0)) + methodName.substring(1) : methodName 164 .toLowerCase(Locale.ENGLISH); 165 } 166 167 /** 168 * Creates a property descriptor for a fluent API property. 169 * 170 * @param m the set method for the fluent API property 171 * @param propertyName the name of the corresponding property 172 * @return the descriptor 173 * @throws IntrospectionException if an error occurs 174 */ 175 private PropertyDescriptor createFluentPropertyDescritor(Method m, 176 String propertyName) throws IntrospectionException { 177 return new PropertyDescriptor(propertyName(m), null, m); 178 } 179}