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

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

Author: hlship
Date: Wed Aug  2 10:55:01 2006
New Revision: 428071

URL: http://svn.apache.org/viewvc?rev=428071&view=rev
Log:
Add PropertyShadowBuilder service (which can create an object that shadows a property of some other object)

Added:
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/PropertyShadowBuilder.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/PropertyShadowBuilderImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/ioc/services/PropertyShadowBuilderImplTest.java
Modified:
    tapestry/tapestry5/tapestry-core/trunk/   (props changed)
    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/TapestryIOCModule.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/ioc/services/IOCServicesStrings.properties

Propchange: tapestry/tapestry5/tapestry-core/trunk/
------------------------------------------------------------------------------
--- svn:ignore (original)
+++ svn:ignore Wed Aug  2 10:55:01 2006
@@ -4,3 +4,4 @@
 tapestry-core_*.xml
 TestNG context suite.launch
 cobertura.ser
+temp-testng-customsuite.xml

Modified: 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=428071&r1=428070&r2=428071&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/IOCServicesMessages.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/IOCServicesMessages.java Wed Aug  2 10:55:01 2006
@@ -47,4 +47,11 @@
     {
         return MESSAGES.format("no-such-property", clazz.getName(), propertyName);
     }
+
+    static String propertyTypeMismatch(String propertyName, Class sourceClass, Class propertyType,
+            Class expectedType)
+    {
+        return MESSAGES.format("property-type-mismatch", new Object[]
+        { propertyName, sourceClass.getName(), propertyType.getName(), expectedType.getName() });
+    }
 }

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/PropertyShadowBuilder.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/PropertyShadowBuilder.java?rev=428071&view=auto
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/PropertyShadowBuilder.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/PropertyShadowBuilder.java Wed Aug  2 10:55:01 2006
@@ -0,0 +1,43 @@
+// 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;
+
+/**
+ * Creates a "shadow" of a property of an object. The shadow has the same type as the property, and
+ * delegates all method invocations to the property. Each method invocation on the shadow
+ * re-acquires the value of the property from the underlying object and delegates to the current
+ * value of the property.
+ * <p>
+ * Typically, the object in question is another service, one with the "perthread" service lifecycle.
+ * This allows a global singleton to shadow a value that is specific to the current thread (and
+ * therefore, the current request).
+ * 
+ * @author Howard M. Lewis Ship
+ */
+public interface PropertyShadowBuilder
+{
+    /**
+     * @param <T>
+     * @param source
+     *            the object from which a property will be extracted
+     * @param propertyName
+     *            the name of a property of the object, which must be readable
+     * @param propertyType
+     *            the expected type of the property, the actual property type must be assignable to
+     *            this type
+     * @return the shadow
+     */
+    <T> T createShadow(Object source, String propertyName, Class<T> propertyType);
+}

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/PropertyShadowBuilderImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/PropertyShadowBuilderImpl.java?rev=428071&view=auto
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/PropertyShadowBuilderImpl.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/PropertyShadowBuilderImpl.java Wed Aug  2 10:55:01 2006
@@ -0,0 +1,93 @@
+// 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.Constructor;
+import java.lang.reflect.Modifier;
+
+import static java.lang.String.format;
+
+/**
+ * @author Howard M. Lewis Ship
+ */
+public class PropertyShadowBuilderImpl implements PropertyShadowBuilder
+{
+    private final ClassFactory _classFactory;
+
+    private final PropertyAccess _propertyAccess;
+
+    public PropertyShadowBuilderImpl(ClassFactory classFactory, PropertyAccess propertyAccess)
+    {
+        _classFactory = classFactory;
+        _propertyAccess = propertyAccess;
+    }
+
+    public <T> T createShadow(Object source, String propertyName, Class<T> propertyType)
+    {
+        Class sourceClass = source.getClass();
+        PropertyAdapter adapter = _propertyAccess.getAdapter(sourceClass).getPropertyAdapter(
+                propertyName);
+
+        // TODO: Perhaps extend ClassPropertyAdapter to do these checks?
+
+        if (adapter == null)
+            throw new RuntimeException(IOCServicesMessages
+                    .noSuchProperty(sourceClass, propertyName));
+
+        if (!adapter.isRead())
+            throw new RuntimeException(IOCServicesMessages.readNotSupported(source, propertyName));
+
+        if (!propertyType.isAssignableFrom(adapter.getType()))
+            throw new RuntimeException(IOCServicesMessages.propertyTypeMismatch(
+                    propertyName,
+                    sourceClass,
+                    adapter.getType(),
+                    propertyType));
+
+        ClassFab cf = _classFactory.newClass(propertyType);
+
+        cf.addField("_source", sourceClass);
+
+        cf.addConstructor(new Class[]
+        { sourceClass }, null, "_source = $1;");
+
+        String body = format("return _source.%s();", adapter.getReadMethod().getName());
+
+        MethodSignature sig = new MethodSignature(propertyType, "_delegate", null, null);
+        cf.addMethod(Modifier.PRIVATE, sig, body);
+
+        String toString = format("<Shadow: property %s of %s>", propertyName, source);
+
+        cf.proxyMethodsToDelegate(propertyType, "_delegate()", toString);
+
+        Class shadowClass = cf.createClass();
+
+        try
+        {
+            Constructor cc = shadowClass.getConstructors()[0];
+
+            Object instance = cc.newInstance(source);
+
+            return propertyType.cast(instance);
+        }
+        catch (Exception ex)
+        {
+            // Should not be reachable
+            throw new RuntimeException(ex);
+        }
+
+    }
+
+}

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=428071&r1=428070&r2=428071&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 10:55:01 2006
@@ -111,4 +111,14 @@
     {
         return new PropertyAccessImpl();
     }
+
+    /**
+     * Builder that creates a shadow, a projection of a property of some other object.
+     */
+    public PropertyShadowBuilder buildPropertyShadowBuilder(@InjectService("ClassFactory")
+    ClassFactory classFactory, @InjectService("PropertyAccess")
+    PropertyAccess propertyAccess)
+    {
+        return new PropertyShadowBuilderImpl(classFactory, propertyAccess);
+    }
 }

Modified: 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=428071&r1=428070&r2=428071&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/ioc/services/IOCServicesStrings.properties (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/ioc/services/IOCServicesStrings.properties Wed Aug  2 10:55:01 2006
@@ -16,4 +16,5 @@
 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
+no-such-property=Class {0} does not contain a property named ''{1}''.
+property-type-mismatch=Property ''{0}'' of class {1} is of type {2}, which is not assignable to type {3}.
\ No newline at end of file

Added: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/ioc/services/PropertyShadowBuilderImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/ioc/services/PropertyShadowBuilderImplTest.java?rev=428071&view=auto
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/ioc/services/PropertyShadowBuilderImplTest.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/ioc/services/PropertyShadowBuilderImplTest.java Wed Aug  2 10:55:01 2006
@@ -0,0 +1,162 @@
+// 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.util.Map;
+
+import org.apache.tapestry.internal.test.InternalBaseTestCase;
+import org.apache.tapestry.ioc.Registry;
+import org.testng.annotations.Configuration;
+import org.testng.annotations.Test;
+
+/**
+ * @author Howard M. Lewis Ship
+ */
+public class PropertyShadowBuilderImplTest extends InternalBaseTestCase
+{
+    private PropertyShadowBuilder _builder;
+
+    private final String CLASS_NAME = getClass().getName();
+
+    @Configuration(beforeTestClass = true)
+    public void setBuilder()
+    {
+        Registry registry = buildRegistry(TapestryIOCModule.class);
+
+        _builder = registry.getService(
+                "tapestry.ioc.PropertyShadowBuilder",
+                PropertyShadowBuilder.class);
+    }
+
+    public class FooHolder
+    {
+        private Foo _foo;
+
+        private int _count = 0;
+
+        public Foo getFoo()
+        {
+            _count++;
+
+            return _foo;
+        }
+
+        public int getCount()
+        {
+            return _count;
+        }
+
+        public void setFoo(Foo foo)
+        {
+            _foo = foo;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "[FooHolder]";
+        }
+
+        public void setWriteOnly(Foo foo)
+        {
+
+        }
+    }
+
+    public interface Foo
+    {
+        void foo();
+    }
+
+    @Test
+    public void basic_delegation()
+    {
+        Foo foo = newMock(Foo.class);
+        FooHolder holder = new FooHolder();
+
+        holder.setFoo(foo);
+
+        Foo shadow = _builder.createShadow(holder, "foo", Foo.class);
+
+        for (int i = 0; i < 3; i++)
+        {
+            foo.foo();
+
+            replay();
+
+            shadow.foo();
+
+            verify();
+
+            assertEquals(holder.getCount(), i + 1);
+        }
+
+        assertEquals(shadow.toString(), "<Shadow: property foo of [FooHolder]>");
+    }
+
+    @Test
+    public void property_does_not_exist()
+    {
+        FooHolder holder = new FooHolder();
+
+        try
+        {
+            _builder.createShadow(holder, "bar", Foo.class);
+            unreachable();
+        }
+        catch (RuntimeException ex)
+        {
+            assertEquals(ex.getMessage(), "Class " + CLASS_NAME
+                    + "$FooHolder does not contain a property named 'bar'.");
+        }
+    }
+
+    @Test
+    public void property_type_mismatch()
+    {
+        FooHolder holder = new FooHolder();
+
+        try
+        {
+            _builder.createShadow(holder, "count", Map.class);
+            unreachable();
+        }
+        catch (RuntimeException ex)
+        {
+            assertEquals(ex.getMessage(), "Property 'count' of class " + CLASS_NAME
+                    + "$FooHolder is of type int, which is not assignable to type java.util.Map.");
+        }
+    }
+
+    @Test
+    public void property_write_only()
+    {
+        FooHolder holder = new FooHolder();
+
+        try
+        {
+            _builder.createShadow(holder, "writeOnly", Foo.class);
+            unreachable();
+        }
+        catch (RuntimeException ex)
+        {
+            assertEquals(
+                    ex.getMessage(),
+                    "Class "
+                            + CLASS_NAME
+                            + "$FooHolder does not provide an accessor (getter) method for property 'writeOnly'.");
+        }
+    }
+}