You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2006/08/02 17:21:49 UTC

svn commit: r428021 - in /tapestry/tapestry5/tapestry-core/trunk/src: main/java/org/apache/tapestry/internal/ioc/ main/java/org/apache/tapestry/internal/test/ main/java/org/apache/tapestry/ioc/services/ main/resources/org/apache/tapestry/ioc/ main/reso...

Author: hlship
Date: Wed Aug  2 08:21:47 2006
New Revision: 428021

URL: http://svn.apache.org/viewvc?rev=428021&view=rev
Log:
Create a service to encapsulate JavaBean property access.

Added:
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/ClassPropertyAdapter.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/ClassPropertyAdapterImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/IOCServicesMessages.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/PropertyAccess.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/PropertyAccessImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/PropertyAdapter.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/PropertyAdapterImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/ioc/
    tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/ioc/services/
    tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/ioc/services/IOCServicesStrings.properties
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/IOCUtilitiesTest.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/ioc/services/PropertyAccessImplTest.java
Modified:
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/IOCUtilities.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/test/InternalBaseTestCase.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/TapestryIOCModule.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/ioc/IntegrationTest.java

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/IOCUtilities.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/IOCUtilities.java?rev=428021&r1=428020&r2=428021&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/IOCUtilities.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/IOCUtilities.java Wed Aug  2 08:21:47 2006
@@ -16,11 +16,15 @@
 
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 
 import org.apache.tapestry.internal.annotations.Utility;
 import org.apache.tapestry.ioc.ServiceLocator;
 import org.apache.tapestry.ioc.annotations.InjectService;
+import org.apache.tapestry.util.CollectionFactory;
 
 /**
  * Contains static methods used within this package.
@@ -99,5 +103,34 @@
         }
 
         return parameters;
+    }
+
+    /** Joins together some number of elements to form a comma separated list. */
+    public static String join(Collection<String> elements)
+    {
+        StringBuffer buffer = new StringBuffer();
+        boolean first = true;
+
+        for (String s : elements)
+        {
+            if (!first)
+                buffer.append(", ");
+
+            buffer.append(s);
+
+            first = false;
+        }
+
+        return buffer.toString();
+    }
+
+    /** Creates a sorted copy of the provided elements, then turns that into a comma separated list. */
+    public static String joinSorted(Collection<String> elements)
+    {
+        List<String> list = CollectionFactory.newList(elements);
+
+        Collections.sort(list);
+
+        return join(list);
     }
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/test/InternalBaseTestCase.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/test/InternalBaseTestCase.java?rev=428021&r1=428020&r2=428021&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/test/InternalBaseTestCase.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/test/InternalBaseTestCase.java Wed Aug  2 08:21:47 2006
@@ -21,6 +21,8 @@
 import org.apache.tapestry.internal.ioc.InternalRegistry;
 import org.apache.tapestry.internal.ioc.Module;
 import org.apache.tapestry.internal.parser.ComponentTemplate;
+import org.apache.tapestry.ioc.Registry;
+import org.apache.tapestry.ioc.RegistryBuilder;
 import org.apache.tapestry.ioc.ServiceDecorator;
 import org.apache.tapestry.ioc.ServiceLifecycle;
 import org.apache.tapestry.ioc.def.ServiceDef;
@@ -78,5 +80,13 @@
     protected final InternalRegistry newInternalRegistry()
     {
         return newMock(InternalRegistry.class);
+    }
+
+    protected final Registry buildRegistry(Class... moduleClasses)
+    {
+        RegistryBuilder builder = new RegistryBuilder();
+        builder.add(moduleClasses);
+
+        return builder.build();
     }
 }

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/ClassPropertyAdapter.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/ClassPropertyAdapter.java?rev=428021&view=auto
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/ClassPropertyAdapter.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/ClassPropertyAdapter.java Wed Aug  2 08:21:47 2006
@@ -0,0 +1,48 @@
+// Copyright 2006 The Apache Software Foundation
+//
+// Licensed 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.tapestry.ioc.services;
+
+/**
+ * Organizes all {@link org.apache.tapestry.ioc.services.PropertyAdapter}s for a particular class.
+ * 
+ * @author Howard M. Lewis Ship
+ */
+public interface ClassPropertyAdapter
+{
+    /**
+     * Returns the property adapter with the given name, or null if no such adapter exists.
+     */
+    PropertyAdapter getPropertyAdapter(String name);
+
+    /**
+     * Reads the value of a property.
+     * 
+     * @throws UnsupportedOperationException
+     *             if the property is write only
+     * @throws IllegalArgumentException
+     *             if property does not exist
+     */
+    Object get(Object instance, String propertyName);
+
+    /**
+     * Updates the value of a property. *
+     * 
+     * @throws UnsupportedOperationException
+     *             if the property is read only
+     * @throws IllegalArgumentException
+     *             if property does not exist
+     */
+    void set(Object instance, String propertyName, Object value);
+}

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/ClassPropertyAdapterImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/ClassPropertyAdapterImpl.java?rev=428021&view=auto
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/ClassPropertyAdapterImpl.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/ClassPropertyAdapterImpl.java Wed Aug  2 08:21:47 2006
@@ -0,0 +1,83 @@
+// Copyright 2006 The Apache Software Foundation
+//
+// Licensed 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.tapestry.ioc.services;
+
+import java.beans.PropertyDescriptor;
+import java.util.Map;
+
+import org.apache.tapestry.internal.annotations.SuppressNullCheck;
+import org.apache.tapestry.internal.ioc.IOCUtilities;
+
+import static org.apache.tapestry.util.CollectionFactory.newMap;
+
+/**
+ * @author Howard M. Lewis Ship
+ */
+public class ClassPropertyAdapterImpl implements ClassPropertyAdapter
+{
+    private final Map<String, PropertyAdapter> _adapters = newMap();
+
+    private final Class _targetClass;
+
+    public ClassPropertyAdapterImpl(Class targetClass, PropertyDescriptor[] descriptors)
+    {
+        _targetClass = targetClass;
+
+        for (PropertyDescriptor pd : descriptors)
+        {
+            PropertyAdapter pa = new PropertyAdapterImpl(pd);
+
+            _adapters.put(pa.getName(), pa);
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        String names = IOCUtilities.joinSorted(_adapters.keySet());
+
+        return String.format("<ClassPropertyAdaptor %s : %s>", _targetClass.getName(), names);
+    }
+
+    public PropertyAdapter getPropertyAdapter(String name)
+    {
+        return _adapters.get(name);
+    }
+
+    @SuppressNullCheck
+    public Object get(Object instance, String propertyName)
+    {
+        return adaptorFor(propertyName).get(instance);
+    }
+
+    @SuppressNullCheck
+    public void set(Object instance, String propertyName, Object value)
+    {
+        adaptorFor(propertyName).set(instance, value);
+    }
+
+    private PropertyAdapter adaptorFor(String name)
+    {
+        PropertyAdapter pa = _adapters.get(name);
+
+        if (pa == null)
+            throw new IllegalArgumentException(IOCServicesMessages.noSuchProperty(
+                    _targetClass,
+                    name));
+
+        return pa;
+    }
+
+}

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/IOCServicesMessages.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/IOCServicesMessages.java?rev=428021&view=auto
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/IOCServicesMessages.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/IOCServicesMessages.java Wed Aug  2 08:21:47 2006
@@ -0,0 +1,50 @@
+// Copyright 2006 The Apache Software Foundation
+//
+// Licensed 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.tapestry.ioc.services;
+
+import org.apache.hivemind.Messages;
+import org.apache.hivemind.impl.MessageFormatter;
+import org.apache.tapestry.internal.annotations.Utility;
+
+@Utility
+public class IOCServicesMessages
+{
+    private static final Messages MESSAGES = new MessageFormatter(IOCServicesMessages.class);
+
+    static String readNotSupported(Object instance, String propertyName)
+    {
+        return MESSAGES.format("read-not-supported", instance.getClass().getName(), propertyName);
+    }
+
+    static String writeNotSupported(Object instance, String propertyName)
+    {
+        return MESSAGES.format("write-not-supported", instance.getClass().getName(), propertyName);
+    }
+
+    static String readFailure(String propertyName, Object instance, Throwable cause)
+    {
+        return MESSAGES.format("read-failure", propertyName, instance, cause);
+    }
+
+    static String writeFailure(String propertyName, Object instance, Throwable cause)
+    {
+        return MESSAGES.format("write-failure", propertyName, instance, cause);
+    }
+
+    static String noSuchProperty(Class clazz, String propertyName)
+    {
+        return MESSAGES.format("no-such-property", clazz.getName(), propertyName);
+    }
+}

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/PropertyAccess.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/PropertyAccess.java?rev=428021&view=auto
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/PropertyAccess.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/PropertyAccess.java Wed Aug  2 08:21:47 2006
@@ -0,0 +1,58 @@
+// Copyright 2006 The Apache Software Foundation
+//
+// Licensed 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.tapestry.ioc.services;
+
+/**
+ * A wrapper around the JavaBean Introspector that allows more manageable access to JavaBean
+ * properties of objects.
+ * 
+ * @author Howard M. Lewis Ship
+ */
+public interface PropertyAccess
+{
+    /**
+     * Reads the value of a property.
+     * 
+     * @throws UnsupportedOperationException
+     *             if the property is write only
+     * @throws IllegalArgumentException
+     *             if property does not exist
+     */
+    Object get(Object instance, String propertyName);
+
+    /**
+     * Updates the value of a property.
+     * 
+     * @throws UnsupportedOperationException
+     *             if the property is read only
+     * @throws IllegalArgumentException
+     *             if property does not exist
+     */
+    void set(Object instance, String propertyName, Object value);
+
+    /**
+     * Returns the adapter for a particular object instance. A convienience over invoking
+     * {@link #getAdapter(Class)}.
+     */
+    ClassPropertyAdapter getAdapter(Object instance);
+
+    /**
+     * Returns the adapter used to access properties within the indicated class.
+     */
+    ClassPropertyAdapter getAdapter(Class forClass);
+
+    /** Discards all stored property access information, discarding all created class adapters. */
+    void clear();
+}

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/PropertyAccessImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/PropertyAccessImpl.java?rev=428021&view=auto
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/PropertyAccessImpl.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/PropertyAccessImpl.java Wed Aug  2 08:21:47 2006
@@ -0,0 +1,92 @@
+// Copyright 2006 The Apache Software Foundation
+//
+// Licensed 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.tapestry.ioc.services;
+
+import java.beans.BeanInfo;
+import java.beans.Introspector;
+import java.util.Map;
+
+import org.apache.tapestry.internal.annotations.Concurrent;
+import org.apache.tapestry.internal.annotations.SuppressNullCheck;
+
+import static org.apache.tapestry.util.CollectionFactory.newMap;
+
+/**
+ * @author Howard M. Lewis Ship
+ */
+@Concurrent
+public class PropertyAccessImpl implements PropertyAccess
+{
+    private final Map<Class, ClassPropertyAdapter> _adapters = newMap();
+
+    @SuppressNullCheck
+    public Object get(Object instance, String propertyName)
+    {
+        return getAdapter(instance).get(instance, propertyName);
+    }
+
+    @SuppressNullCheck
+    public void set(Object instance, String propertyName, Object value)
+    {
+        getAdapter(instance).set(instance, propertyName, value);
+    }
+
+    @Concurrent.Write
+    public void clear()
+    {
+        _adapters.clear();
+    }
+
+    public ClassPropertyAdapter getAdapter(Object instance)
+    {
+        return getAdapter(instance.getClass());
+    }
+
+    @Concurrent.Read
+    public ClassPropertyAdapter getAdapter(Class forClass)
+    {
+        ClassPropertyAdapter result = _adapters.get(forClass);
+
+        if (result == null)
+            result = buildAdapter(forClass);
+
+        return result;
+    }
+
+    /**
+     * Builds a new adapter and updates the _adapters cache. This not only guards access to the
+     * adapter cache, but also serializes access to the Java Beans Introspector, which is not thread
+     * safe.
+     */
+    @Concurrent.Write
+    private ClassPropertyAdapter buildAdapter(Class forClass)
+    {
+        try
+        {
+            BeanInfo info = Introspector.getBeanInfo(forClass);
+
+            ClassPropertyAdapter adapter = new ClassPropertyAdapterImpl(forClass, info
+                    .getPropertyDescriptors());
+
+            _adapters.put(forClass, adapter);
+
+            return adapter;
+        }
+        catch (Exception ex)
+        {
+            throw new RuntimeException(ex);
+        }
+    }
+}

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/PropertyAdapter.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/PropertyAdapter.java?rev=428021&view=auto
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/PropertyAdapter.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/PropertyAdapter.java Wed Aug  2 08:21:47 2006
@@ -0,0 +1,79 @@
+// Copyright 2006 The Apache Software Foundation
+//
+// Licensed 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.tapestry.ioc.services;
+
+import java.lang.reflect.Method;
+
+/**
+ * Provides access to a single property within a class.
+ * 
+ * @author Howard M. Lewis Ship
+ * @see org.apache.tapestry.ioc.services.ClassPropertyAdapter
+ */
+public interface PropertyAdapter
+{
+    /**
+     * Returns the name of the property.
+     */
+    String getName();
+
+    /**
+     * Returns true if the property is readable (i.e., has a getter method).
+     */
+    boolean isRead();
+
+    /**
+     * Returns the method used to read the property, or null if the property is not readable.
+     */
+    public Method getReadMethod();
+
+    /**
+     * Returns true if the property is writeable (i.e., has a setter method).
+     */
+    boolean isUpdate();
+
+    /**
+     * Returns the method used to update the property, or null if the property is not writeable.
+     */
+    public Method getWriteMethod();
+
+    /**
+     * Reads the property value.
+     * 
+     * @param instance
+     *            to read from
+     * @throws UnsupportedOperationException
+     *             if the property is write only
+     */
+    Object get(Object instance);
+
+    /**
+     * Updates the property value. The provided value must not be null if the property type is
+     * primitive, and must otherwise be of the proper type.
+     * 
+     * @param instance
+     *            to update
+     * @param value
+     *            new value for the property
+     * @throws UnsupportedOperationException
+     *             if the property is read only
+     */
+    void set(Object instance, Object value);
+
+    /**
+     * Returns the type of the property.
+     */
+    Class getType();
+}

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/PropertyAdapterImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/PropertyAdapterImpl.java?rev=428021&view=auto
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/PropertyAdapterImpl.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/PropertyAdapterImpl.java Wed Aug  2 08:21:47 2006
@@ -0,0 +1,131 @@
+// Copyright 2006 The Apache Software Foundation
+//
+// Licensed 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.tapestry.ioc.services;
+
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import org.apache.tapestry.internal.annotations.SuppressNullCheck;
+import org.apache.tapestry.util.Defense;
+
+@SuppressNullCheck
+public class PropertyAdapterImpl implements PropertyAdapter
+{
+    private final String _name;
+
+    private final Method _readMethod;
+
+    private final Method _writeMethod;
+
+    private final Class _type;
+
+    public PropertyAdapterImpl(String name, Class type, Method readMethod, Method writeMethod)
+    {
+        _name = Defense.notBlank(name, "name");
+        _type = Defense.notNull(type, "type");
+        _readMethod = readMethod;
+        _writeMethod = writeMethod;
+    }
+
+    public PropertyAdapterImpl(PropertyDescriptor descriptor)
+    {
+        this(descriptor.getName(), descriptor.getPropertyType(), descriptor.getReadMethod(),
+                descriptor.getWriteMethod());
+    }
+
+    public String getName()
+    {
+        return _name;
+    }
+
+    public Method getReadMethod()
+    {
+        return _readMethod;
+    }
+
+    public Class getType()
+    {
+        return _type;
+    }
+
+    public Method getWriteMethod()
+    {
+        return _writeMethod;
+    }
+
+    public boolean isRead()
+    {
+        return _readMethod != null;
+    }
+
+    public boolean isUpdate()
+    {
+        return _writeMethod != null;
+    }
+
+    public Object get(Object instance)
+    {
+        if (_readMethod == null)
+            throw new UnsupportedOperationException(IOCServicesMessages.readNotSupported(
+                    instance,
+                    _name));
+
+        Throwable fail = null;
+
+        try
+        {
+            return _readMethod.invoke(instance);
+        }
+        catch (InvocationTargetException ex)
+        {
+            fail = ex.getTargetException();
+        }
+        catch (Exception ex)
+        {
+            fail = ex;
+        }
+
+        throw new RuntimeException(IOCServicesMessages.readFailure(_name, instance, fail));
+    }
+
+    public void set(Object instance, Object value)
+    {
+        if (_writeMethod == null)
+            throw new UnsupportedOperationException(IOCServicesMessages.writeNotSupported(
+                    instance,
+                    _name));
+
+        Throwable fail = null;
+
+        try
+        {
+            _writeMethod.invoke(instance, value);
+
+            return;
+        }
+        catch (InvocationTargetException ex)
+        {
+            fail = ex.getTargetException();
+        }
+        catch (Exception ex)
+        {
+            fail = ex;
+        }
+
+        throw new RuntimeException(IOCServicesMessages.writeFailure(_name, instance, fail));
+    }
+
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/TapestryIOCModule.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/TapestryIOCModule.java?rev=428021&r1=428020&r2=428021&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/TapestryIOCModule.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/TapestryIOCModule.java Wed Aug  2 08:21:47 2006
@@ -102,4 +102,13 @@
     {
         return new ChainBuilderImpl(classFactory);
     }
+
+    /**
+     * Services that provides read/write access to JavaBean properties. Encapsulates JavaBean
+     * introspection, including serializing access to the non-thread-safe Introspector object.
+     */
+    public PropertyAccess buildPropertyAccess()
+    {
+        return new PropertyAccessImpl();
+    }
 }

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/ioc/services/IOCServicesStrings.properties
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/ioc/services/IOCServicesStrings.properties?rev=428021&view=auto
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/ioc/services/IOCServicesStrings.properties (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/ioc/services/IOCServicesStrings.properties Wed Aug  2 08:21:47 2006
@@ -0,0 +1,19 @@
+# Copyright 2006 The Apache Software Foundation
+#
+# Licensed 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.
+
+read-not-supported=Class {0} does not provide an accessor ('getter') method for property ''{1}''.
+write-not-supported=Class {0} does not provide an mutator ('setter') method for property ''{1}''.
+read-failure=Error reading property ''{0}'' of {1}: {2}
+write-failure=Error updating property ''{0}'' of {1}: {2}
+no-such-property=Class {0} does not contain a property named ''{1}''.
\ No newline at end of file

Added: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/IOCUtilitiesTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/IOCUtilitiesTest.java?rev=428021&view=auto
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/IOCUtilitiesTest.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/IOCUtilitiesTest.java Wed Aug  2 08:21:47 2006
@@ -0,0 +1,64 @@
+// Copyright 2006 The Apache Software Foundation
+//
+// Licensed 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.tapestry.internal.ioc;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.tapestry.test.TestBase;
+import org.apache.tapestry.util.CollectionFactory;
+import org.testng.annotations.Test;
+
+/**
+ * @author Howard M. Lewis Ship
+ */
+public class IOCUtilitiesTest extends TestBase
+{
+    @Test
+    public void join_empty_list()
+    {
+        List<String> empty = CollectionFactory.newList();
+
+        assertEquals(IOCUtilities.join(empty), "");
+    }
+
+    @Test
+    public void join_single()
+    {
+        List<String> single = Arrays.asList("barney");
+
+        assertEquals(IOCUtilities.join(single), "barney");
+    }
+
+    @Test
+    public void join_multiple()
+    {
+        List<String> many = Arrays.asList("fred", "barney", "wilma");
+        assertEquals(IOCUtilities.join(many), "fred, barney, wilma");
+    }
+
+    @Test
+    public void join_sorted()
+    {
+        List<String> unsorted = Arrays.asList("betty", "fred", "barney", "wilma");
+        List<String> copy = CollectionFactory.newList(unsorted);
+
+        assertEquals(IOCUtilities.joinSorted(copy), "barney, betty, fred, wilma");
+
+        // Make sure that joinSorted() doesn't change the input list
+
+        assertEquals(copy, unsorted);
+    }
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/ioc/IntegrationTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/ioc/IntegrationTest.java?rev=428021&r1=428020&r2=428021&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/ioc/IntegrationTest.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/ioc/IntegrationTest.java Wed Aug  2 08:21:47 2006
@@ -38,14 +38,6 @@
         return buildRegistry(TapestryIOCModule.class, FredModule.class, BarneyModule.class);
     }
 
-    private Registry buildRegistry(Class... moduleClasses)
-    {
-        RegistryBuilder builder = new RegistryBuilder();
-        builder.add(moduleClasses);
-
-        return builder.build();
-    }
-
     /**
      * Along the way, we also test a few other things, such as decorator matching and automatic
      * dependency resolution.

Added: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/ioc/services/PropertyAccessImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/ioc/services/PropertyAccessImplTest.java?rev=428021&view=auto
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/ioc/services/PropertyAccessImplTest.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/ioc/services/PropertyAccessImplTest.java Wed Aug  2 08:21:47 2006
@@ -0,0 +1,368 @@
+// Copyright 2006 The Apache Software Foundation
+//
+// Licensed 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.tapestry.ioc.services;
+
+import java.awt.Image;
+import java.beans.BeanDescriptor;
+import java.beans.BeanInfo;
+import java.beans.EventSetDescriptor;
+import java.beans.MethodDescriptor;
+import java.beans.PropertyDescriptor;
+import java.util.Random;
+
+import org.apache.tapestry.internal.test.InternalBaseTestCase;
+import org.apache.tapestry.ioc.Registry;
+import org.testng.annotations.Test;
+
+/**
+ * @author Howard M. Lewis Ship
+ */
+public class PropertyAccessImplTest extends InternalBaseTestCase
+{
+    private static final String CLASS_NAME = PropertyAccessImplTest.class.getName();
+
+    private PropertyAccess _access = new PropertyAccessImpl();
+
+    private Random _random = new Random();
+
+    public static class Bean
+    {
+        private int _value;
+
+        public int getValue()
+        {
+            return _value;
+        }
+
+        public void setValue(int value)
+        {
+            _value = value;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "PropertyUtilsTestBean";
+        }
+
+        public void setWriteOnly(boolean b)
+        {
+        }
+
+        public String getReadOnly()
+        {
+            return null;
+        }
+    }
+
+    public static class ExceptionBean
+    {
+        public boolean getFailure()
+        {
+            throw new RuntimeException("getFailure");
+        }
+
+        public void setFailure(boolean b)
+        {
+            throw new RuntimeException("setFailure");
+        }
+
+        @Override
+        public String toString()
+        {
+            return "PropertyUtilsExceptionBean";
+        }
+    }
+
+    public static class UglyBean
+    {
+    }
+
+    public static class UglyBeanBeanInfo implements BeanInfo
+    {
+
+        public BeanInfo[] getAdditionalBeanInfo()
+        {
+            return new BeanInfo[0];
+        }
+
+        public BeanDescriptor getBeanDescriptor()
+        {
+            return null;
+        }
+
+        public int getDefaultEventIndex()
+        {
+            return 0;
+        }
+
+        public int getDefaultPropertyIndex()
+        {
+            return 0;
+        }
+
+        public EventSetDescriptor[] getEventSetDescriptors()
+        {
+            return new EventSetDescriptor[0];
+        }
+
+        public Image getIcon(int iconKind)
+        {
+            return null;
+        }
+
+        public MethodDescriptor[] getMethodDescriptors()
+        {
+            return new MethodDescriptor[0];
+        }
+
+        public PropertyDescriptor[] getPropertyDescriptors()
+        {
+            throw new RuntimeException("This is the UglyBean.");
+        }
+
+    }
+
+    public static class BooleanHolder
+    {
+        private boolean _flag;
+
+        public boolean isFlag()
+        {
+            return _flag;
+        }
+
+        public void setFlag(boolean flag)
+        {
+            _flag = flag;
+        }
+    }
+
+    @Test
+    public void simple_read_access()
+    {
+        Bean b = new Bean();
+
+        int value = _random.nextInt();
+
+        b.setValue(value);
+
+        assertEquals(_access.get(b, "value"), value);
+    }
+
+    @Test
+    public void simple_write_access()
+    {
+        Bean b = new Bean();
+
+        int value = _random.nextInt();
+
+        _access.set(b, "value", value);
+
+        assertEquals(b.getValue(), value);
+    }
+
+    @Test
+    public void missing_property()
+    {
+        Bean b = new Bean();
+
+        try
+        {
+            _access.get(b, "zaphod");
+
+            unreachable();
+        }
+        catch (IllegalArgumentException ex)
+        {
+            assertEquals(ex.getMessage(), "Class " + CLASS_NAME + "$Bean does not "
+                    + "contain a property named 'zaphod'.");
+        }
+    }
+
+    @Test
+    public void attempt_to_update_read_only_property()
+    {
+        Bean b = new Bean();
+
+        try
+        {
+            _access.set(b, "class", null);
+            unreachable();
+        }
+        catch (UnsupportedOperationException ex)
+        {
+            assertEquals(ex.getMessage(), "Class " + CLASS_NAME
+                    + "$Bean does not provide an mutator (setter) method for property 'class'.");
+        }
+    }
+
+    @Test
+    public void attempt_to_read_from_write_only_property()
+    {
+        Bean b = new Bean();
+
+        try
+        {
+            _access.get(b, "writeOnly");
+            unreachable();
+        }
+        catch (UnsupportedOperationException ex)
+        {
+            assertEquals(
+                    ex.getMessage(),
+                    "Class "
+                            + CLASS_NAME
+                            + "$Bean does not provide an accessor (getter) method for property 'writeOnly'.");
+        }
+    }
+
+    @Test
+    public void exception_thrown_inside_getter()
+    {
+        ExceptionBean b = new ExceptionBean();
+
+        try
+        {
+            _access.get(b, "failure");
+            unreachable();
+        }
+        catch (RuntimeException ex)
+        {
+            assertEquals(
+                    ex.getMessage(),
+                    "Error reading property 'failure' of PropertyUtilsExceptionBean: getFailure");
+        }
+    }
+
+    @Test
+    public void exception_thrown_inside_setter()
+    {
+        ExceptionBean b = new ExceptionBean();
+
+        try
+        {
+            _access.set(b, "failure", false);
+            unreachable();
+        }
+        catch (RuntimeException ex)
+        {
+            assertEquals(
+                    ex.getMessage(),
+                    "Error updating property 'failure' of PropertyUtilsExceptionBean: setFailure");
+        }
+    }
+
+    @Test
+    public void failure_when_introspecting_class()
+    {
+        UglyBean b = new UglyBean();
+
+        try
+        {
+            _access.get(b, "google");
+            unreachable();
+        }
+        catch (RuntimeException ex)
+        {
+            assertEquals(ex.getMessage(), "java.lang.RuntimeException: This is the UglyBean.");
+        }
+    }
+
+    @Test
+    public void clear_wipes_internal_cache()
+    {
+        ClassPropertyAdapter cpa1 = _access.getAdapter(Bean.class);
+        ClassPropertyAdapter cpa2 = _access.getAdapter(Bean.class);
+
+        assertSame(cpa2, cpa1);
+
+        _access.clear();
+
+        ClassPropertyAdapter cpa3 = _access.getAdapter(Bean.class);
+
+        assertNotSame(cpa3, cpa1);
+    }
+
+    @Test
+    public void class_property_adapter_toString()
+    {
+        ClassPropertyAdapter cpa = _access.getAdapter(Bean.class);
+
+        assertEquals(cpa.toString(), "<ClassPropertyAdaptor " + CLASS_NAME
+                + "$Bean : class, readOnly, value, writeOnly>");
+    }
+
+    @Test
+    public void property_adapter_read_only_property()
+    {
+        ClassPropertyAdapter cpa = _access.getAdapter(Bean.class);
+        PropertyAdapter pa = cpa.getPropertyAdapter("readOnly");
+
+        assertTrue(pa.isRead());
+        assertFalse(pa.isUpdate());
+
+        assertNull(pa.getWriteMethod());
+        assertEquals(pa.getReadMethod(), findMethod(Bean.class, "getReadOnly"));
+    }
+
+    @Test
+    public void property_adapter_write_only_property()
+    {
+        ClassPropertyAdapter cpa = _access.getAdapter(Bean.class);
+        PropertyAdapter pa = cpa.getPropertyAdapter("writeOnly");
+
+        assertFalse(pa.isRead());
+        assertTrue(pa.isUpdate());
+
+        assertEquals(pa.getWriteMethod(), findMethod(Bean.class, "setWriteOnly"));
+        assertNull(pa.getReadMethod());
+    }
+
+    @Test
+    public void class_property_adapter_returns_null_for_unknown_property()
+    {
+        ClassPropertyAdapter cpa = _access.getAdapter(Bean.class);
+
+        assertNull(cpa.getPropertyAdapter("google"));
+    }
+
+    @Test
+    public void access_to_property_type()
+    {
+        ClassPropertyAdapter cpa = _access.getAdapter(Bean.class);
+
+        assertEquals(cpa.getPropertyAdapter("value").getType(), int.class);
+        assertEquals(cpa.getPropertyAdapter("readOnly").getType(), String.class);
+        assertEquals(cpa.getPropertyAdapter("writeOnly").getType(), boolean.class);
+    }
+
+    @Test
+    public void integration()
+    {
+        Registry registry = buildRegistry(TapestryIOCModule.class);
+
+        PropertyAccess pa = registry
+                .getService("tapestry.ioc.PropertyAccess", PropertyAccess.class);
+
+        Bean b = new Bean();
+
+        int value = _random.nextInt();
+
+        pa.set(b, "value", value);
+
+        assertEquals(b.getValue(), value);
+    }
+}