| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.commons.beanutils2; |
| |
| import java.beans.IntrospectionException; |
| import java.beans.Introspector; |
| import java.beans.PropertyDescriptor; |
| import java.lang.reflect.Method; |
| import java.util.Locale; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| |
| /** |
| * <p> |
| * An implementation of the {@code BeanIntrospector} interface which can |
| * detect write methods for properties used in fluent API scenario. |
| * </p> |
| * <p> |
| * A <em>fluent API</em> allows setting multiple properties using a single |
| * statement by supporting so-called <em>method chaining</em>: Methods for |
| * setting a property value do not return <b>void</b>, but an object which can |
| * be called for setting another property. An example of such a fluent API could |
| * look as follows: |
| * </p> |
| * <pre> |
| * public class FooBuilder { |
| * public FooBuilder setFooProperty1(String value) { |
| * ... |
| * return this; |
| * } |
| * |
| * public FooBuilder setFooProperty2(int value) { |
| * ... |
| * return this; |
| * } |
| * } |
| * </pre> |
| * |
| * <p>Per default, {@code PropertyUtils} does not detect methods like this |
| * because, having a non-<b>void</b> return type, they violate the Java Beans |
| * specification. |
| * </p> |
| * <p> |
| * This class is more tolerant with regards to the return type of a set method. |
| * It basically iterates over all methods of a class and filters them for a |
| * configurable prefix (the default prefix is {@code set}). It then |
| * generates corresponding {@code PropertyDescriptor} objects for the |
| * methods found which use these methods as write methods. |
| * </p> |
| * <p> |
| * An instance of this class is intended to collaborate with a |
| * {@link DefaultBeanIntrospector} object. So best results are achieved by |
| * adding this instance as custom {@code BeanIntrospector} after the |
| * {@code DefaultBeanIntrospector} object. Then default introspection finds |
| * read-only properties because it does not detect the write methods with a |
| * non-<b>void</b> return type. {@code FluentPropertyBeanIntrospector} |
| * completes the descriptors for these properties by setting the correct write |
| * method. |
| * </p> |
| * |
| * @since 1.9 |
| */ |
| public class FluentPropertyBeanIntrospector implements BeanIntrospector { |
| /** The default prefix for write methods. */ |
| public static final String DEFAULT_WRITE_METHOD_PREFIX = "set"; |
| |
| /** The logger. */ |
| private final Log log = LogFactory.getLog(getClass()); |
| |
| /** The prefix of write methods to search for. */ |
| private final String writeMethodPrefix; |
| |
| /** |
| * |
| * Creates a new instance of {@code FluentPropertyBeanIntrospector} and |
| * initializes it with the prefix for write methods used by the classes to |
| * be inspected. |
| * |
| * @param writePrefix the prefix for write methods (must not be <b>null</b>) |
| * @throws IllegalArgumentException if the prefix is <b>null</b> |
| */ |
| public FluentPropertyBeanIntrospector(final String writePrefix) { |
| if (writePrefix == null) { |
| throw new IllegalArgumentException( |
| "Prefix for write methods must not be null!"); |
| } |
| writeMethodPrefix = writePrefix; |
| } |
| |
| /** |
| * |
| * Creates a new instance of {@code FluentPropertyBeanIntrospector} and |
| * sets the default prefix for write methods. |
| */ |
| public FluentPropertyBeanIntrospector() { |
| this(DEFAULT_WRITE_METHOD_PREFIX); |
| } |
| |
| /** |
| * Returns the prefix for write methods this instance scans for. |
| * |
| * @return the prefix for write methods |
| */ |
| public String getWriteMethodPrefix() { |
| return writeMethodPrefix; |
| } |
| |
| /** |
| * Performs introspection. This method scans the current class's methods for |
| * property write methods which have not been discovered by default |
| * introspection. |
| * |
| * @param icontext the introspection context |
| * @throws IntrospectionException if an error occurs |
| */ |
| @Override |
| public void introspect(final IntrospectionContext icontext) |
| throws IntrospectionException { |
| for (final Method m : icontext.getTargetClass().getMethods()) { |
| if (m.getName().startsWith(getWriteMethodPrefix())) { |
| final String propertyName = propertyName(m); |
| final PropertyDescriptor pd = icontext |
| .getPropertyDescriptor(propertyName); |
| try { |
| if (pd == null) { |
| icontext.addPropertyDescriptor(createFluentPropertyDescritor( |
| m, propertyName)); |
| } else if (pd.getWriteMethod() == null) { |
| pd.setWriteMethod(m); |
| } |
| } catch (final IntrospectionException e) { |
| log.debug("Error when creating PropertyDescriptor for " + m |
| + "! Ignoring this property."); |
| log.debug("Exception is:", e); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Derives the name of a property from the given set method. |
| * |
| * @param m the method |
| * @return the corresponding property name |
| */ |
| private String propertyName(final Method m) { |
| final String methodName = m.getName().substring( |
| getWriteMethodPrefix().length()); |
| return methodName.length() > 1 ? Introspector.decapitalize(methodName) : methodName |
| .toLowerCase(Locale.ENGLISH); |
| } |
| |
| /** |
| * Creates a property descriptor for a fluent API property. |
| * |
| * @param m the set method for the fluent API property |
| * @param propertyName the name of the corresponding property |
| * @return the descriptor |
| * @throws IntrospectionException if an error occurs |
| */ |
| private PropertyDescriptor createFluentPropertyDescritor(final Method m, |
| final String propertyName) throws IntrospectionException { |
| return new PropertyDescriptor(propertyName(m), null, m); |
| } |
| } |