You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by oh...@apache.org on 2013/11/09 19:01:19 UTC
svn commit: r1540353 - in /commons/proper/beanutils/trunk/src:
main/java/org/apache/commons/beanutils/
test/java/org/apache/commons/beanutils/
Author: oheger
Date: Sat Nov 9 18:01:19 2013
New Revision: 1540353
URL: http://svn.apache.org/r1540353
Log:
[BEANUTILS-428] Added FluentPropertyBeanIntrospector class.
This is a specialized BeanIntrospector implementation which also supports
properties whose setter method has a non-void return type. This is useful for
classes using a fluent API.
Added:
commons/proper/beanutils/trunk/src/main/java/org/apache/commons/beanutils/FluentPropertyBeanIntrospector.java
commons/proper/beanutils/trunk/src/test/java/org/apache/commons/beanutils/FluentIntrospectionTestBean.java
commons/proper/beanutils/trunk/src/test/java/org/apache/commons/beanutils/FluentPropertyBeanIntrospectorTestCase.java
Added: commons/proper/beanutils/trunk/src/main/java/org/apache/commons/beanutils/FluentPropertyBeanIntrospector.java
URL: http://svn.apache.org/viewvc/commons/proper/beanutils/trunk/src/main/java/org/apache/commons/beanutils/FluentPropertyBeanIntrospector.java?rev=1540353&view=auto
==============================================================================
--- commons/proper/beanutils/trunk/src/main/java/org/apache/commons/beanutils/FluentPropertyBeanIntrospector.java (added)
+++ commons/proper/beanutils/trunk/src/main/java/org/apache/commons/beanutils/FluentPropertyBeanIntrospector.java Sat Nov 9 18:01:19 2013
@@ -0,0 +1,179 @@
+/*
+ * 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.beanutils;
+
+import java.beans.IntrospectionException;
+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</code> 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:
+ *
+ * <pre>
+ * public class FooBuilder {
+ * public FooBuilder setFooProperty1(String value) {
+ * ...
+ * return this;
+ * }
+ *
+ * public FooBuilder setFooProperty2(int value) {
+ * ...
+ * return this;
+ * }
+ * }
+ * </pre>
+ *
+ * Per default, <code>PropertyUtils</code> 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</code>). It then
+ * generates corresponding <code>PropertyDescriptor</code> 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</code> 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>
+ *
+ * @version $Id: $
+ * @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</code> 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(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</code> 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
+ */
+ public void introspect(IntrospectionContext icontext)
+ throws IntrospectionException {
+ for (Method m : icontext.getTargetClass().getMethods()) {
+ if (m.getName().startsWith(getWriteMethodPrefix())) {
+ String propertyName = propertyName(m);
+ PropertyDescriptor pd = icontext
+ .getPropertyDescriptor(propertyName);
+ try {
+ if (pd == null) {
+ icontext.addPropertyDescriptor(createFluentPropertyDescritor(
+ m, propertyName));
+ } else if (pd.getWriteMethod() == null) {
+ pd.setWriteMethod(m);
+ }
+ } catch (IntrospectionException e) {
+ log.warn("Error when creating PropertyDescriptor for " + m
+ + "! Ignoring this property.", e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Derives the name of a property from the given set method.
+ *
+ * @param m the method
+ * @return the corresponding property name
+ */
+ private String propertyName(Method m) {
+ String methodName = m.getName().substring(
+ getWriteMethodPrefix().length());
+ return (methodName.length() > 1) ? Character.toLowerCase(methodName
+ .charAt(0)) + methodName.substring(1) : 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(Method m,
+ String propertyName) throws IntrospectionException {
+ return new PropertyDescriptor(propertyName(m), null, m);
+ }
+}
\ No newline at end of file
Added: commons/proper/beanutils/trunk/src/test/java/org/apache/commons/beanutils/FluentIntrospectionTestBean.java
URL: http://svn.apache.org/viewvc/commons/proper/beanutils/trunk/src/test/java/org/apache/commons/beanutils/FluentIntrospectionTestBean.java?rev=1540353&view=auto
==============================================================================
--- commons/proper/beanutils/trunk/src/test/java/org/apache/commons/beanutils/FluentIntrospectionTestBean.java (added)
+++ commons/proper/beanutils/trunk/src/test/java/org/apache/commons/beanutils/FluentIntrospectionTestBean.java Sat Nov 9 18:01:19 2013
@@ -0,0 +1,51 @@
+/*
+ * 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.beanutils;
+
+/**
+ * A bean class used for tests of introspection.
+ *
+ * @version $Id: $
+ */
+public class FluentIntrospectionTestBean extends AlphaBean {
+ private String stringProperty;
+
+ private String fluentGetProperty;
+
+ public String getStringProperty() {
+ return stringProperty;
+ }
+
+ public void setStringProperty(String stringProperty) {
+ this.stringProperty = stringProperty;
+ }
+
+ public FluentIntrospectionTestBean setFluentProperty(String value) {
+ setStringProperty(value);
+ return this;
+ }
+
+ public String getFluentGetProperty() {
+ return fluentGetProperty;
+ }
+
+ public FluentIntrospectionTestBean setFluentGetProperty(
+ String fluentGetProperty) {
+ this.fluentGetProperty = fluentGetProperty;
+ return this;
+ }
+}
Added: commons/proper/beanutils/trunk/src/test/java/org/apache/commons/beanutils/FluentPropertyBeanIntrospectorTestCase.java
URL: http://svn.apache.org/viewvc/commons/proper/beanutils/trunk/src/test/java/org/apache/commons/beanutils/FluentPropertyBeanIntrospectorTestCase.java?rev=1540353&view=auto
==============================================================================
--- commons/proper/beanutils/trunk/src/test/java/org/apache/commons/beanutils/FluentPropertyBeanIntrospectorTestCase.java (added)
+++ commons/proper/beanutils/trunk/src/test/java/org/apache/commons/beanutils/FluentPropertyBeanIntrospectorTestCase.java Sat Nov 9 18:01:19 2013
@@ -0,0 +1,96 @@
+/*
+ * 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.beanutils;
+
+import java.beans.IntrospectionException;
+import java.beans.PropertyDescriptor;
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+/**
+ * Test class for {@code FluentPropertyBeanIntrospector}.
+ *
+ * @version $Id: $
+ */
+public class FluentPropertyBeanIntrospectorTestCase extends TestCase {
+ /**
+ * Puts all property descriptors into a map so that they can be accessed by
+ * property name.
+ *
+ * @param descs the array with descriptors
+ * @return a map with property names as keys
+ */
+ private static Map<String, PropertyDescriptor> createDescriptorMap(
+ PropertyDescriptor[] descs) {
+ Map<String, PropertyDescriptor> map = new HashMap<String, PropertyDescriptor>();
+ for (PropertyDescriptor pd : descs) {
+ map.put(pd.getName(), pd);
+ }
+ return map;
+ }
+
+ /**
+ * Convenience method for obtaining a specific property descriptor and
+ * checking whether it exists.
+ *
+ * @param props the map with property descriptors
+ * @param name the name of the desired descriptor
+ * @return the descriptor from the map
+ */
+ private static PropertyDescriptor fetchDescriptor(
+ Map<String, PropertyDescriptor> props, String name) {
+ assertTrue("Property not found: " + name, props.containsKey(name));
+ return props.get(name);
+ }
+
+ /**
+ * Tries to create an instance without a prefix for write methods.
+ */
+ public void testInitNoPrefix() {
+ try {
+ new FluentPropertyBeanIntrospector(null);
+ fail("Missing prefix for write methods not detected!");
+ } catch (IllegalArgumentException iex) {
+ // ok
+ }
+ }
+
+ /**
+ * Tests whether correct property descriptors are detected.
+ */
+ public void testIntrospection() throws IntrospectionException {
+ PropertyUtilsBean pu = new PropertyUtilsBean();
+ FluentPropertyBeanIntrospector introspector = new FluentPropertyBeanIntrospector();
+ pu.addBeanIntrospector(introspector);
+ Map<String, PropertyDescriptor> props = createDescriptorMap(pu
+ .getPropertyDescriptors(FluentIntrospectionTestBean.class));
+ PropertyDescriptor pd = fetchDescriptor(props, "name");
+ assertNotNull("No read method for name", pd.getReadMethod());
+ assertNotNull("No write method for name", pd.getWriteMethod());
+ fetchDescriptor(props, "stringProperty");
+ pd = fetchDescriptor(props, "fluentProperty");
+ assertNull("Read method for fluentProperty", pd.getReadMethod());
+ assertNotNull("No write method for fluentProperty", pd.getWriteMethod());
+ pd = fetchDescriptor(props, "fluentGetProperty");
+ assertNotNull("No read method for fluentGetProperty",
+ pd.getReadMethod());
+ assertNotNull("No write method for fluentGetProperty",
+ pd.getWriteMethod());
+ }
+}