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'.");
+ }
+ }
+}