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());
+    }
+}