First final version of the BeanModel and Commons packages.
Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/97bd4d5e Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/97bd4d5e Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/97bd4d5e Branch: refs/heads/master Commit: 97bd4d5eff6af1a4f970b81246c8689c3a25687a Parents: 3120870 Author: Thiago H. de Paula Figueiredo <thiag...@apache.org> Authored: Sun Dec 7 00:22:27 2014 -0200 Committer: Thiago H. de Paula Figueiredo <thiag...@apache.org> Committed: Sun Dec 7 00:22:27 2014 -0200 ---------------------------------------------------------------------- beanmodel/build.gradle | 3 + .../beaneditor/BeanModelSourceBuilder.java | 150 +++- .../services/DefaultDataTypeAnalyzer.java | 61 ++ .../ioc/internal/BasicDataTypeAnalyzers.java | 45 +- .../tapestry5/ioc/util/StrategyRegistry.java | 172 +++++ .../services/DefaultDataTypeAnalyzer.java | 61 -- .../AbstractBeanModelSourceImplTest.java | 757 +++++++++++++++++++ .../services/BeanModelSourceBuilderTest.java | 33 + .../services/BeanModelSourceImplTest.java | 736 +----------------- .../tapestry5/ioc/util/StrategyRegistry.java | 172 ----- 10 files changed, 1182 insertions(+), 1008 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/97bd4d5e/beanmodel/build.gradle ---------------------------------------------------------------------- diff --git a/beanmodel/build.gradle b/beanmodel/build.gradle index fba43b7..9ff0fb3 100644 --- a/beanmodel/build.gradle +++ b/beanmodel/build.gradle @@ -35,6 +35,9 @@ dependencies { compile "org.antlr:antlr-runtime:3.5.2", { exclude group: "org.antlr", module: "stringtemplate" } + + testCompile "org.testng:testng:${versions.testng}", { transitive = false } + } // This may spin out as a plugin once we've got the details down pat http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/97bd4d5e/beanmodel/src/main/java/org/apache/tapestry5/beaneditor/BeanModelSourceBuilder.java ---------------------------------------------------------------------- diff --git a/beanmodel/src/main/java/org/apache/tapestry5/beaneditor/BeanModelSourceBuilder.java b/beanmodel/src/main/java/org/apache/tapestry5/beaneditor/BeanModelSourceBuilder.java index 4ac3373..58537b5 100644 --- a/beanmodel/src/main/java/org/apache/tapestry5/beaneditor/BeanModelSourceBuilder.java +++ b/beanmodel/src/main/java/org/apache/tapestry5/beaneditor/BeanModelSourceBuilder.java @@ -13,32 +13,26 @@ // limitations under the License. package org.apache.tapestry5.beaneditor; +import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Set; - -import javax.naming.OperationNotSupportedException; -import javax.swing.JFrame; import org.apache.tapestry5.internal.services.BeanModelSourceImpl; import org.apache.tapestry5.internal.services.PropertyConduitSourceImpl; import org.apache.tapestry5.internal.services.StringInterner; import org.apache.tapestry5.internal.services.StringInternerImpl; +import org.apache.tapestry5.ioc.AnnotationProvider; import org.apache.tapestry5.ioc.Configuration; -import org.apache.tapestry5.ioc.MessageFormatter; -import org.apache.tapestry5.ioc.Messages; import org.apache.tapestry5.ioc.ObjectLocator; import org.apache.tapestry5.ioc.internal.BasicDataTypeAnalyzers; import org.apache.tapestry5.ioc.internal.BasicTypeCoercions; import org.apache.tapestry5.ioc.internal.services.PlasticProxyFactoryImpl; import org.apache.tapestry5.ioc.internal.services.PropertyAccessImpl; import org.apache.tapestry5.ioc.internal.services.TypeCoercerImpl; +import org.apache.tapestry5.ioc.internal.util.TapestryException; import org.apache.tapestry5.ioc.services.CoercionTuple; import org.apache.tapestry5.ioc.services.PlasticProxyFactory; import org.apache.tapestry5.ioc.services.PropertyAccess; -import org.apache.tapestry5.ioc.services.PropertyAdapter; import org.apache.tapestry5.ioc.services.TypeCoercer; import org.apache.tapestry5.services.BeanModelSource; import org.apache.tapestry5.services.DataTypeAnalyzer; @@ -46,8 +40,12 @@ import org.apache.tapestry5.services.PropertyConduitSource; import org.slf4j.LoggerFactory; /** - * Utility class for creating {@link BeanModelSource} instances without + * <p>Utility class for creating {@link BeanModelSource} instances without * Tapestry-IoC. Usage of Tapestry-IoC is still recommended. + * </p> + * <p>The setter methods can be used to customize the BeanModelSource to be created and can be + * (and usually are skipped), so <code>BeanModelSource beanModelSource = new BeanModelSourceBuilder().build()</code> + * is all you need to do. */ public class BeanModelSourceBuilder { @@ -60,14 +58,8 @@ public class BeanModelSourceBuilder { private StringInterner stringInterner; /** - * Sets the {@link TypeCoercer} to be used. + * Creates and returns a {@link BeanModelSource} instance. */ - public BeanModelSourceBuilder setTypeCoercer(TypeCoercer typeCoercer) - { - this.typeCoercer = typeCoercer; - return this; - } - public BeanModelSource build() { @@ -101,9 +93,80 @@ public class BeanModelSourceBuilder { propertyConduitSource = new PropertyConduitSourceImpl(propertyAccess, plasticProxyFactory, typeCoercer, stringInterner); } + if (objectLocator == null) + { + objectLocator = new AutobuildOnlyObjectLocator(); + } + return new BeanModelSourceImpl(typeCoercer, propertyAccess, propertyConduitSource, plasticProxyFactory, dataTypeAnalyzer, objectLocator); } + + /** + * Sets the {@link TypeCoercer} to be used. + */ + public BeanModelSourceBuilder setTypeCoercer(TypeCoercer typeCoercer) + { + this.typeCoercer = typeCoercer; + return this; + } + + /** + * Sets the {@link PropertyAccess} to be used. + */ + public BeanModelSourceBuilder setPropertyAccess(PropertyAccess propertyAccess) + { + this.propertyAccess = propertyAccess; + return this; + } + + /** + * Sets the {@link PropertyConduitSource} to be used. + */ + public BeanModelSourceBuilder setPropertyConduitSource(PropertyConduitSource propertyConduitSource) + { + this.propertyConduitSource = propertyConduitSource; + return this; + } + + /** + * Sets the {@link PlasticProxyFactory} to be used. + */ + public BeanModelSourceBuilder setPlasticProxyFactory(PlasticProxyFactory plasticProxyFactory) + { + this.plasticProxyFactory = plasticProxyFactory; + return this; + } + + /** + * Sets the {@link DataTypeAnalyzer} to be used. + */ + public BeanModelSourceBuilder setDataTypeAnalyzer(DataTypeAnalyzer dataTypeAnalyzer) + { + this.dataTypeAnalyzer = dataTypeAnalyzer; + return this; + } + + /** + * Sets the {@link ObjectLocator} to be used. Actually, the only method of it actually used is + * {@link ObjectLocator#autobuild(Class)}, for creating objects of the class described by the + * {@link BeanModel}. + */ + public BeanModelSourceBuilder setObjectLocator(ObjectLocator objectLocator) + { + this.objectLocator = objectLocator; + return this; + } + + /** + * Sets the {@link StringInterner} to be used. + */ + public BeanModelSourceBuilder setStringInterner(StringInterner stringInterner) + { + this.stringInterner = stringInterner; + return this; + } + private void createTypeCoercer() { CoercionTupleConfiguration configuration = new CoercionTupleConfiguration(); @@ -134,5 +197,58 @@ public class BeanModelSourceBuilder { } } + + final private static class AutobuildOnlyObjectLocator implements ObjectLocator { + + @Override + public <T> T getService(String serviceId, Class<T> serviceInterface) + { + throw new RuntimeException("Not implemented"); + } + + @Override + public <T> T getService(Class<T> serviceInterface) + { + throw new RuntimeException("Not implemented"); + } + + @Override + public <T> T getService(Class<T> serviceInterface, + Class<? extends Annotation>... markerTypes) + { + throw new RuntimeException("Not implemented"); + } + + @Override + public <T> T getObject(Class<T> objectType, AnnotationProvider annotationProvider) + { + throw new RuntimeException("Not implemented"); + } + + @Override + public <T> T autobuild(Class<T> clazz) + { + try + { + return clazz.newInstance(); + } + catch (Exception e) + { + throw new TapestryException("Couldn't instantiate class " + clazz.getName(), e); + } + } + + @Override + public <T> T autobuild(String description, Class<T> clazz) + { + return autobuild(clazz); + } + + public <T> T proxy(Class<T> interfaceClass, Class<? extends T> implementationClass) + { + throw new RuntimeException("Not implemented"); + } + + } } http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/97bd4d5e/commons/src/main/java/org/apache/tapestry5/internal/services/DefaultDataTypeAnalyzer.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/tapestry5/internal/services/DefaultDataTypeAnalyzer.java b/commons/src/main/java/org/apache/tapestry5/internal/services/DefaultDataTypeAnalyzer.java new file mode 100644 index 0000000..cdf98e5 --- /dev/null +++ b/commons/src/main/java/org/apache/tapestry5/internal/services/DefaultDataTypeAnalyzer.java @@ -0,0 +1,61 @@ +// Copyright 2007, 2008, 2012 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.tapestry5.internal.services; + +import org.apache.tapestry5.ioc.services.PropertyAdapter; +import org.apache.tapestry5.ioc.util.StrategyRegistry; +import org.apache.tapestry5.services.DataTypeAnalyzer; +import org.apache.tapestry5.services.InvalidationListener; + +import java.util.Map; + +/** + * The default data type analyzer, which is based entirely on the type of the property (and not on annotations or naming + * conventions). This is based on a configuration of property type class to string provided as an IoC service + * configuration. + */ +public class DefaultDataTypeAnalyzer implements DataTypeAnalyzer, Runnable +{ + private final StrategyRegistry<String> registry; + + public DefaultDataTypeAnalyzer(Map<Class, String> configuration) + { + registry = StrategyRegistry.newInstance(String.class, configuration); + } + + /** + * Clears the registry on an invalidation event (this is because the registry caches results, and the keys are + * classes that may be component classes from the invalidated component class loader). + */ + public void run() + { + registry.clearCache(); + } + + public String identifyDataType(PropertyAdapter adapter) + { + Class propertyType = adapter.getType(); + + String dataType = registry.get(propertyType); + + // To avoid "no strategy" exceptions, we expect a contribution of Object.class to the empty + // string. We convert that back to a null. + + if (dataType.equals("")) + return null; + + return dataType; + } +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/97bd4d5e/commons/src/main/java/org/apache/tapestry5/ioc/internal/BasicDataTypeAnalyzers.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/internal/BasicDataTypeAnalyzers.java b/commons/src/main/java/org/apache/tapestry5/ioc/internal/BasicDataTypeAnalyzers.java index df7564f..4d603f5 100644 --- a/commons/src/main/java/org/apache/tapestry5/ioc/internal/BasicDataTypeAnalyzers.java +++ b/commons/src/main/java/org/apache/tapestry5/ioc/internal/BasicDataTypeAnalyzers.java @@ -16,10 +16,12 @@ package org.apache.tapestry5.ioc.internal; import java.util.Calendar; import java.util.Date; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.apache.tapestry5.beaneditor.DataTypeConstants; import org.apache.tapestry5.internal.services.AnnotationDataTypeAnalyzer; +import org.apache.tapestry5.internal.services.DefaultDataTypeAnalyzer; import org.apache.tapestry5.ioc.MappedConfiguration; import org.apache.tapestry5.ioc.OrderedConfiguration; import org.apache.tapestry5.ioc.services.PropertyAdapter; @@ -46,7 +48,8 @@ public class BasicDataTypeAnalyzers { DefaultDataTypeAnalyzerMappedConfiguration mappedConfiguration = new DefaultDataTypeAnalyzerMappedConfiguration(); provideDefaultDataTypeAnalyzers(mappedConfiguration); - return new CombinedDataTypeAnalyzer(new AnnotationDataTypeAnalyzer(), new MapDataTypeAnalyzer(mappedConfiguration.getMap())); + + return new CombinedDataTypeAnalyzer(new AnnotationDataTypeAnalyzer(), new DefaultDataTypeAnalyzer(mappedConfiguration.getMap())); } /** @@ -106,43 +109,29 @@ public class BasicDataTypeAnalyzers } - final private static class MapDataTypeAnalyzer implements DataTypeAnalyzer - { - - final Map<Class, String> map; - - public MapDataTypeAnalyzer(Map<Class, String> map) { - this.map = map; - } - - @Override - public String identifyDataType(PropertyAdapter adapter) { - return map.get(adapter.getType()); - } - - } - final private static class CombinedDataTypeAnalyzer implements DataTypeAnalyzer { - final private DataTypeAnalyzer first, second; + final private DataTypeAnalyzer[] analyzers; - public CombinedDataTypeAnalyzer(DataTypeAnalyzer first, DataTypeAnalyzer second) + public CombinedDataTypeAnalyzer(DataTypeAnalyzer... analyzers) { - super(); - this.first = first; - this.second = second; + this.analyzers = analyzers; } @Override public String identifyDataType(PropertyAdapter adapter) { - String type = first.identifyDataType(adapter); - if (type == null) - { - type = second.identifyDataType(adapter); - } - return type; + String type = null; + for (DataTypeAnalyzer analyzer : analyzers) + { + type = analyzer.identifyDataType(adapter); + if (type != null) + { + break; + } + } + return type; } } http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/97bd4d5e/commons/src/main/java/org/apache/tapestry5/ioc/util/StrategyRegistry.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/util/StrategyRegistry.java b/commons/src/main/java/org/apache/tapestry5/ioc/util/StrategyRegistry.java new file mode 100644 index 0000000..fbdfc6a --- /dev/null +++ b/commons/src/main/java/org/apache/tapestry5/ioc/util/StrategyRegistry.java @@ -0,0 +1,172 @@ +// Copyright 2006, 2007, 2008, 2011, 2012 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.tapestry5.ioc.util; + +import org.apache.tapestry5.ioc.internal.util.CollectionFactory; +import org.apache.tapestry5.ioc.internal.util.InheritanceSearch; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * A key component in implementing the "Gang of Four" Strategy pattern. A StrategyRegistry will match up a given input + * type with a registered strategy for that type. + * + * @param <A> the type of the strategy adapter + */ +public final class StrategyRegistry<A> +{ + private final Class<A> adapterType; + + private final boolean allowNonMatch; + + private final Map<Class, A> registrations = CollectionFactory.newMap(); + + private final Map<Class, A> cache = CollectionFactory.newConcurrentMap(); + + /** + * Used to identify types for which there is no matching adapter; we're using it as if it were a ConcurrentSet. + */ + private final Map<Class, Boolean> unmatched = CollectionFactory.newConcurrentMap(); + + private StrategyRegistry(Class<A> adapterType, Map<Class, A> registrations, boolean allowNonMatch) + { + this.adapterType = adapterType; + this.allowNonMatch = allowNonMatch; + + this.registrations.putAll(registrations); + } + + /** + * Creates a strategy registry for the given adapter type. The registry will be configured to require matches. + * + * @param adapterType the type of adapter retrieved from the registry + * @param registrations map of registrations (the contents of the map are copied) + */ + public static <A> StrategyRegistry<A> newInstance(Class<A> adapterType, + Map<Class, A> registrations) + { + return newInstance(adapterType, registrations, false); + } + + /** + * Creates a strategy registry for the given adapter type. + * + * @param adapterType the type of adapter retrieved from the registry + * @param registrations map of registrations (the contents of the map are copied) + * @param allowNonMatch if true, then the registry supports non-matches when retrieving an adapter + */ + public static <A> StrategyRegistry<A> newInstance( + Class<A> adapterType, + Map<Class, A> registrations, boolean allowNonMatch) + { + return new StrategyRegistry<A>(adapterType, registrations, allowNonMatch); + } + + public void clearCache() + { + cache.clear(); + unmatched.clear(); + } + + public Class<A> getAdapterType() + { + return adapterType; + } + + /** + * Gets an adapter for an object. Searches based on the value's class, unless the value is null, in which case, a + * search on class void is used. + * + * @param value for which an adapter is needed + * @return the adapter for the value or null if not found (and allowNonMatch is true) + * @throws IllegalArgumentException if no matching adapter may be found and allowNonMatch is false + */ + + public A getByInstance(Object value) + { + return get(value == null ? void.class : value.getClass()); + } + + /** + * Searches for an adapter corresponding to the given input type. + * + * @param type the type to search + * @return the adapter for the type or null if not found (and allowNonMatch is true) + * @throws IllegalArgumentException if no matching adapter may be found and allowNonMatch is false + */ + public A get(Class type) + { + + A result = cache.get(type); + + if (result != null) return result; + + if (unmatched.containsKey(type)) return null; + + + result = findMatch(type); + + // This may be null in the case that there is no match and we're allowing that to not + // be an error. That's why we check via containsKey. + + if (result != null) + { + cache.put(type, result); + } else + { + unmatched.put(type, true); + } + + return result; + } + + private A findMatch(Class type) + { + for (Class t : new InheritanceSearch(type)) + { + A result = registrations.get(t); + + if (result != null) return result; + } + + if (allowNonMatch) return null; + + // Report the error. These things really confused the hell out of people in Tap4, so we're + // going the extra mile on the exception message. + + List<String> names = CollectionFactory.newList(); + for (Class t : registrations.keySet()) + names.add(t.getName()); + + throw new UnknownValueException(String.format("No adapter from type %s to type %s is available.", type.getName(), adapterType.getName()), null, null, + new AvailableValues("registered types", registrations)); + } + + /** + * Returns the registered types for which adapters are available. + */ + public Collection<Class> getTypes() + { + return CollectionFactory.newList(registrations.keySet()); + } + + @Override + public String toString() + { + return String.format("StrategyRegistry[%s]", adapterType.getName()); + } +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/97bd4d5e/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultDataTypeAnalyzer.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultDataTypeAnalyzer.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultDataTypeAnalyzer.java deleted file mode 100644 index cdf98e5..0000000 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultDataTypeAnalyzer.java +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2007, 2008, 2012 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.tapestry5.internal.services; - -import org.apache.tapestry5.ioc.services.PropertyAdapter; -import org.apache.tapestry5.ioc.util.StrategyRegistry; -import org.apache.tapestry5.services.DataTypeAnalyzer; -import org.apache.tapestry5.services.InvalidationListener; - -import java.util.Map; - -/** - * The default data type analyzer, which is based entirely on the type of the property (and not on annotations or naming - * conventions). This is based on a configuration of property type class to string provided as an IoC service - * configuration. - */ -public class DefaultDataTypeAnalyzer implements DataTypeAnalyzer, Runnable -{ - private final StrategyRegistry<String> registry; - - public DefaultDataTypeAnalyzer(Map<Class, String> configuration) - { - registry = StrategyRegistry.newInstance(String.class, configuration); - } - - /** - * Clears the registry on an invalidation event (this is because the registry caches results, and the keys are - * classes that may be component classes from the invalidated component class loader). - */ - public void run() - { - registry.clearCache(); - } - - public String identifyDataType(PropertyAdapter adapter) - { - Class propertyType = adapter.getType(); - - String dataType = registry.get(propertyType); - - // To avoid "no strategy" exceptions, we expect a contribution of Object.class to the empty - // string. We convert that back to a null. - - if (dataType.equals("")) - return null; - - return dataType; - } -} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/97bd4d5e/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/AbstractBeanModelSourceImplTest.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/AbstractBeanModelSourceImplTest.java b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/AbstractBeanModelSourceImplTest.java new file mode 100644 index 0000000..1294934 --- /dev/null +++ b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/AbstractBeanModelSourceImplTest.java @@ -0,0 +1,757 @@ +// Copyright 2007, 2008, 2009, 2010, 2011 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.tapestry5.internal.services; + +import org.apache.tapestry5.PropertyConduit; +import org.apache.tapestry5.beaneditor.BeanModel; +import org.apache.tapestry5.beaneditor.BeanModelSourceBuilder; +import org.apache.tapestry5.beaneditor.PropertyModel; +import org.apache.tapestry5.beaneditor.RelativePosition; +import org.apache.tapestry5.beaneditor.Sortable; +import org.apache.tapestry5.internal.PropertyOrderBean; +import org.apache.tapestry5.internal.test.InternalBaseTestCase; +import org.apache.tapestry5.internal.transform.pages.ReadOnlyBean; +import org.apache.tapestry5.ioc.Messages; +import org.apache.tapestry5.ioc.util.UnknownValueException; +import org.apache.tapestry5.services.BeanModelSource; +import org.easymock.EasyMock; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import java.util.Arrays; +import java.util.Collections; + +/** + * Tests for the bean editor model source itself, as well as the model classes. + */ +public abstract class AbstractBeanModelSourceImplTest extends InternalBaseTestCase +{ + private BeanModelSource source; + + protected abstract BeanModelSource create(); + + @BeforeClass + public void setup() + { + source = create(); + } + + /** + * Tests defaults for property names, labels and conduits. + */ + @Test + public void default_model_for_bean() + { + Messages messages = mockMessages(); + + stub_contains(messages, false); + + replay(); + + BeanModel model = source.create(SimpleBean.class, true, messages); + + assertSame(model.getBeanType(), SimpleBean.class); + + // Based on order of the getter methods (no longer alphabetical) + + assertEquals(model.getPropertyNames(), Arrays.asList("firstName", "lastName", "age")); + + assertEquals(model.toString(), + "BeanModel[org.apache.tapestry5.internal.services.SimpleBean properties:firstName, lastName, age]"); + + PropertyModel age = model.get("age"); + + assertEquals(age.getLabel(), "Age"); + assertSame(age.getPropertyType(), int.class); + assertEquals(age.getDataType(), "number"); + + PropertyModel firstName = model.get("firstName"); + + assertEquals(firstName.getLabel(), "First Name"); + assertEquals(firstName.getPropertyType(), String.class); + assertEquals(firstName.getDataType(), "text"); + + assertEquals(model.get("lastName").getLabel(), "Last Name"); + + PropertyConduit conduit = model.get("lastName").getConduit(); + + SimpleBean instance = new SimpleBean(); + + instance.setLastName("Lewis Ship"); + + assertEquals(conduit.get(instance), "Lewis Ship"); + + conduit.set(instance, "TapestryDude"); + + assertEquals(instance.getLastName(), "TapestryDude"); + + // Now, one with some type coercion. + + age.getConduit().set(instance, "40"); + + assertEquals(instance.getAge(), 40); + + verify(); + } + + @Test + public void include_properties() + { + Messages messages = mockMessages(); + + stub_contains(messages, false); + + replay(); + + BeanModel model = source.create(SimpleBean.class, true, messages); + + assertSame(model.getBeanType(), SimpleBean.class); + + model.include("lastname", "firstname"); + + // Based on order of the getter methods (no longer alphabetical) + + assertEquals(model.getPropertyNames(), Arrays.asList("lastName", "firstName")); + + verify(); + } + + @Test + public void add_before() + { + Messages messages = mockMessages(); + PropertyConduit conduit = mockPropertyConduit(); + + Class propertyType = String.class; + + stub_contains(messages, false); + + expect(conduit.getPropertyType()).andReturn(propertyType).atLeastOnce(); + expect(conduit.getAnnotation(EasyMock.isA(Class.class))).andStubReturn(null); + + replay(); + + BeanModel model = source.create(SimpleBean.class, true, messages); + + assertEquals(model.getPropertyNames(), Arrays.asList("firstName", "lastName", "age")); + + // Note the use of case insensitivity here. + + PropertyModel property = model.add(RelativePosition.BEFORE, "lastname", "middleInitial", conduit); + + assertEquals(model.getPropertyNames(), Arrays.asList("firstName", "middleInitial", "lastName", "age")); + + assertEquals(property.getPropertyName(), "middleInitial"); + assertSame(property.getConduit(), conduit); + assertSame(property.getPropertyType(), propertyType); + + verify(); + } + + /** + * TAPESTRY-2202 + */ + @Test + public void new_instance() + { + Messages messages = mockMessages(); + + stub_contains(messages, false); + + replay(); + + BeanModel<SimpleBean> model = source.create(SimpleBean.class, true, messages); + + SimpleBean s1 = model.newInstance(); + + assertNotNull(s1); + + SimpleBean s2 = model.newInstance(); + + assertNotNull(s2); + assertNotSame(s1, s2); + + verify(); + } + + @Test + public void add_before_using_default_conduit() + { + Messages messages = mockMessages(); + + stub_contains(messages, false); + + replay(); + + BeanModel model = source.create(SimpleBean.class, true, messages); + + model.exclude("firstname"); + + assertEquals(model.getPropertyNames(), Arrays.asList("lastName", "age")); + + // Note the use of case insensitivity here. + + PropertyModel property = model.add(RelativePosition.BEFORE, "lastname", "firstName"); + + assertEquals(model.getPropertyNames(), Arrays.asList("firstName", "lastName", "age")); + + assertEquals(property.getPropertyName(), "firstName"); + assertSame(property.getPropertyType(), String.class); + + verify(); + } + + @Test + public void add_after() + { + Messages messages = mockMessages(); + PropertyConduit conduit = mockPropertyConduit(); + + Class propertyType = String.class; + + stub_contains(messages, false); + + expect(conduit.getPropertyType()).andReturn(propertyType).atLeastOnce(); + + expect(conduit.getAnnotation(EasyMock.isA(Class.class))).andStubReturn(null); + + replay(); + + BeanModel model = source.create(SimpleBean.class, true, messages); + + assertEquals(model.getPropertyNames(), Arrays.asList("firstName", "lastName", "age")); + + PropertyModel property = model.add(RelativePosition.AFTER, "firstname", "middleInitial", conduit); + + assertEquals(model.getPropertyNames(), Arrays.asList("firstName", "middleInitial", "lastName", "age")); + + assertEquals(property.getPropertyName(), "middleInitial"); + assertSame(property.getConduit(), conduit); + assertSame(property.getPropertyType(), propertyType); + + verify(); + } + + @Test + public void filtering_out_read_only_properties() + { + Messages messages = mockMessages(); + + stub_contains(messages, false); + + replay(); + + BeanModel model = source.create(ReadOnlyBean.class, true, messages); + + assertEquals(model.getPropertyNames(), Arrays.asList("value")); + + model = source.create(ReadOnlyBean.class, false, messages); + + assertEquals(model.getPropertyNames(), Arrays.asList("value", "readOnly")); + + verify(); + } + + @Test + public void non_text_property() + { + Messages messages = mockMessages(); + + stub_contains(messages, false); + + replay(); + + BeanModel model = source.create(EnumBean.class, true, messages); + + assertEquals(model.getPropertyNames(), Arrays.asList("token")); + + assertEquals(model.get("token").getDataType(), "enum"); + + verify(); + } + + @Test + public void add_duplicate_property_name_is_failure() + { + Messages messages = mockMessages(); + + stub_contains(messages, false); + + replay(); + + BeanModel model = source.create(SimpleBean.class, true, messages); + + try + { + model.add("age"); + unreachable(); + } catch (RuntimeException ex) + { + assertEquals( + ex.getMessage(), + "Bean editor model for org.apache.tapestry5.internal.services.SimpleBean already contains a property model for property \'age\'."); + } + + verify(); + } + + @Test + public void unknown_property_name() + { + Messages messages = mockMessages(); + + stub_contains(messages, false); + + replay(); + + BeanModel model = source.create(SimpleBean.class, true, messages); + + try + { + model.get("frobozz"); + unreachable(); + } catch (UnknownValueException ex) + { + assertEquals( + ex.getMessage(), + "Bean editor model for org.apache.tapestry5.internal.services.SimpleBean does not contain a property named \'frobozz\'."); + + assertListsEquals(ex.getAvailableValues().getValues(), "age", "firstName", "lastName"); + } + + verify(); + } + + @Test + public void unknown_property_id() + { + Messages messages = mockMessages(); + + stub_contains(messages, false); + + replay(); + + BeanModel model = source.create(SimpleBean.class, true, messages); + + model.addEmpty("shrub.foo()"); + + try + { + model.getById("frobozz"); + unreachable(); + } catch (UnknownValueException ex) + { + assertEquals( + ex.getMessage(), + "Bean editor model for org.apache.tapestry5.internal.services.SimpleBean does not contain a property with id \'frobozz\'."); + + assertListsEquals(ex.getAvailableValues().getValues(), "age", "firstName", "lastName", "shrubfoo"); + } + + verify(); + } + + @Test + public void get_added_property_by_name() + { + Messages messages = mockMessages(); + + stub_contains(messages, false); + + replay(); + + BeanModel model = source.create(SimpleBean.class, true, messages); + + PropertyModel pm = model.addEmpty("shrub.foo()"); + + assertSame(model.get("Shrub.Foo()"), pm); + + verify(); + } + + @Test + public void get_added_property_by_id() + { + Messages messages = mockMessages(); + + stub_contains(messages, false); + + replay(); + + BeanModel model = source.create(SimpleBean.class, true, messages); + + PropertyModel pm = model.addEmpty("shrub.foo()"); + + assertSame(model.getById("ShrubFoo"), pm); + + verify(); + + } + + @Test + public void order_via_annotation() + { + Messages messages = mockMessages(); + + stub_contains(messages, false); + + replay(); + + BeanModel model = source.create(StoogeBean.class, true, messages); + + assertEquals(model.getPropertyNames(), Arrays.asList("larry", "moe", "shemp", "curly")); + + verify(); + } + + @Test + public void edit_property_label() + { + Messages messages = mockMessages(); + + stub_contains(messages, false); + + replay(); + + BeanModel model = source.create(SimpleBean.class, true, messages).get("age").label("Decrepitude").model(); + + assertEquals(model.get("age").getLabel(), "Decrepitude"); + + verify(); + } + + @Test + public void label_from_component_messages() + { + Messages messages = mockMessages(); + + stub_contains(messages, false); + + train_contains(messages, "age-label", true); + train_get(messages, "age-label", "Decrepitude"); + + replay(); + + BeanModel model = source.create(SimpleBean.class, true, messages); + + assertEquals(model.get("age").getLabel(), "Decrepitude"); + + verify(); + } + + @Test + public void array_type_bean() + { + Messages messages = mockMessages(); + + stub_contains(messages, false); + + replay(); + + BeanModel model = source.create(StringArrayBean.class, true, messages); + + // There's not editor for string arrays yet, so it won't show up normally. + + PropertyModel propertyModel = model.add("array"); + + assertSame(propertyModel.getPropertyType(), String[].class); + + String[] value = + {"foo", "bar"}; + + StringArrayBean bean = new StringArrayBean(); + + PropertyConduit conduit = propertyModel.getConduit(); + + conduit.set(bean, value); + + assertSame(bean.getArray(), value); + + assertSame(conduit.get(bean), value); + + verify(); + } + + @Test + public void composite_bean() + { + Messages messages = mockMessages(); + + stub_contains(messages, false); + + train_contains(messages, "simpleage-label", true); + train_get(messages, "simpleage-label", "Years of Age"); + + replay(); + + BeanModel model = source.create(CompositeBean.class, true, messages); + + // No editor for CompositeBean, so this will be empty. + + assertEquals(model.getPropertyNames(), Collections.emptyList()); + + // There's not editor for string arrays yet, so it won't show up normally. + + PropertyModel firstName = model.add("simple.firstName"); + + assertEquals(firstName.getLabel(), "First Name"); + + PropertyModel age = model.add("simple.age"); + assertEquals(age.getLabel(), "Years of Age"); + + CompositeBean bean = new CompositeBean(); + + firstName.getConduit().set(bean, "Fred"); + age.getConduit().set(bean, "97"); + + assertEquals(bean.getSimple().getFirstName(), "Fred"); + assertEquals(bean.getSimple().getAge(), 97); + + bean.getSimple().setAge(24); + + assertEquals(age.getConduit().get(bean), new Integer(24)); + + verify(); + } + + @Test + public void default_properties_exclude_write_only() + { + Messages messages = mockMessages(); + + stub_contains(messages, false); + + replay(); + + BeanModel model = source.create(WriteOnlyBean.class, false, messages); + + assertEquals(model.getPropertyNames(), Arrays.asList("readOnly", "readWrite")); + + verify(); + } + + @Test + public void add_synthetic_property() + { + Messages messages = mockMessages(); + + stub_contains(messages, false); + + replay(); + + BeanModel model = source.create(SimpleBean.class, true, messages); + + PropertyModel property = model.addEmpty("placeholder"); + + assertFalse(property.isSortable()); + assertSame(property.getPropertyType(), Object.class); + assertEquals(property.getLabel(), "Placeholder"); + + verify(); + } + + @Test + public void add_missing_property_is_failure() + { + Messages messages = mockMessages(); + + stub_contains(messages, false); + + replay(); + + BeanModel model = source.create(SimpleBean.class, true, messages); + + try + { + model.add("doesNotExist"); + unreachable(); + } catch (PropertyExpressionException ex) + { + assertMessageContains(ex, "does not contain", "doesNotExist"); + } + + verify(); + } + + @Test + public void exclude_property() + { + Messages messages = mockMessages(); + + stub_contains(messages, false); + + replay(); + + BeanModel model = source.create(SimpleBean.class, true, messages); + + assertSame(model.exclude("age"), model); + + assertEquals(model.getPropertyNames(), Arrays.asList("firstName", "lastName")); + + verify(); + } + + @Test + public void exclude_unknown_property_is_noop() + { + Messages messages = mockMessages(); + + stub_contains(messages, false); + + replay(); + + BeanModel model = source.create(SimpleBean.class, true, messages); + + assertSame(model.exclude("frobozz"), model); + + assertEquals(model.getPropertyNames(), Arrays.asList("firstName", "lastName", "age")); + + verify(); + } + + @Test + public void nonvisual_properties_are_excluded() + { + Messages messages = mockMessages(); + + stub_contains(messages, false); + + replay(); + + BeanModel model = source.create(NonVisualBean.class, true, messages); + + assertEquals(model.getPropertyNames(), Arrays.asList("name")); + + verify(); + } + + @Test + public void reorder() + { + Messages messages = mockMessages(); + + stub_contains(messages, false); + + replay(); + + BeanModel model = source.create(SimpleBean.class, true, messages); + + assertSame(model.getBeanType(), SimpleBean.class); + + // Based on order of the getter methods (no longer alphabetical) + + assertEquals(model.getPropertyNames(), Arrays.asList("firstName", "lastName", "age")); + + // Testing a couple of things here: + // 1) case insensitive + // 2) unreferenced property names added to the end. + + model.reorder("lastname", "AGE"); + + assertEquals(model.getPropertyNames(), Arrays.asList("lastName", "age", "firstName")); + + verify(); + } + + @Test + public void reoder_from_annotation() + { + Messages messages = mockMessages(); + + stub_contains(messages, false); + + replay(); + + BeanModel model = source.create(PropertyOrderBean.class, true, messages); + + assertEquals(model.getPropertyNames(), Arrays.asList("third", "first", "second")); + + verify(); + } + + // https://issues.apache.org/jira/browse/TAP5-1798 + @Test + public void static_fields_are_ignored() + { + Messages messages = mockMessages(); + + stub_contains(messages, false); + + replay(); + + BeanModel<BeanWithStaticField> model = source.createDisplayModel(BeanWithStaticField.class, messages); + + assertListsEquals(model.getPropertyNames(), "name"); + + verify(); + } + + // https://issues.apache.org/jira/browse/TAP5-2305 + @Test + public void sortable_annotation() + { + Messages messages = mockMessages(); + + stub_contains(messages, false); + + replay(); + + BeanModel<SortableBean> model = source.createDisplayModel(SortableBean.class, messages); + model.add("nonSortableByDefault"); + model.add("sortable"); + + // checking whether non-@Sortable annotated properties still behave in the old ways + assertTrue(model.get("sortableByDefault").isSortable()); + assertFalse(model.get("nonSortableByDefault").isSortable()); + + // checking @Sortable itself + assertFalse(model.get("nonSortable").isSortable()); + assertTrue(model.get("sortable").isSortable()); + + verify(); + } + + final private static class SortableBean + { + private int sortableByDefault; + private int nonSortable; + private SimpleBean sortable; + private SimpleBean nonSortableByDefault; + + public int getSortableByDefault() + { + return sortableByDefault; + } + + @Sortable(false) + public int getNonSortable() + { + return nonSortable; + } + + @Sortable(true) + public SimpleBean getSortable() + { + return sortable; + } + + public SimpleBean getNonSortableByDefault() + { + return nonSortableByDefault; + } + + } + +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/97bd4d5e/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/BeanModelSourceBuilderTest.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/BeanModelSourceBuilderTest.java b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/BeanModelSourceBuilderTest.java new file mode 100644 index 0000000..78502e6 --- /dev/null +++ b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/BeanModelSourceBuilderTest.java @@ -0,0 +1,33 @@ +// Copyright 2014 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.tapestry5.internal.services; + +import org.apache.tapestry5.beaneditor.BeanModelSourceBuilder; +import org.apache.tapestry5.services.BeanModelSource; +import org.testng.annotations.Test; + +/** + * Tests a BeanModelSource created using {@link BeanModelSourceBuilder}. + */ +@Test +public class BeanModelSourceBuilderTest extends AbstractBeanModelSourceImplTest +{ + + @Override + protected BeanModelSource create() + { + return new BeanModelSourceBuilder().build(); + } + +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/97bd4d5e/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/BeanModelSourceImplTest.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/BeanModelSourceImplTest.java b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/BeanModelSourceImplTest.java index 17c9480..a23a3a5 100644 --- a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/BeanModelSourceImplTest.java +++ b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/BeanModelSourceImplTest.java @@ -1,4 +1,4 @@ -// Copyright 2007, 2008, 2009, 2010, 2011 The Apache Software Foundation +// Copyright 2014 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. @@ -11,744 +11,20 @@ // 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.tapestry5.internal.services; -import org.apache.tapestry5.PropertyConduit; -import org.apache.tapestry5.beaneditor.BeanModel; -import org.apache.tapestry5.beaneditor.PropertyModel; -import org.apache.tapestry5.beaneditor.RelativePosition; -import org.apache.tapestry5.beaneditor.Sortable; -import org.apache.tapestry5.internal.PropertyOrderBean; -import org.apache.tapestry5.internal.test.InternalBaseTestCase; -import org.apache.tapestry5.internal.transform.pages.ReadOnlyBean; -import org.apache.tapestry5.ioc.Messages; -import org.apache.tapestry5.ioc.util.UnknownValueException; import org.apache.tapestry5.services.BeanModelSource; -import org.easymock.EasyMock; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - -import java.util.Arrays; -import java.util.Collections; /** - * Tests for the bean editor model source itself, as well as the model classes. + * Tests a BeanModelSource created using Tapestry-IoC. */ -public class BeanModelSourceImplTest extends InternalBaseTestCase +public class BeanModelSourceImplTest extends AbstractBeanModelSourceImplTest { - private BeanModelSource source; - - @BeforeClass - public void setup() - { - source = getObject(BeanModelSource.class, null); - } - - /** - * Tests defaults for property names, labels and conduits. - */ - @Test - public void default_model_for_bean() - { - Messages messages = mockMessages(); - - stub_contains(messages, false); - - replay(); - - BeanModel model = source.create(SimpleBean.class, true, messages); - - assertSame(model.getBeanType(), SimpleBean.class); - - // Based on order of the getter methods (no longer alphabetical) - - assertEquals(model.getPropertyNames(), Arrays.asList("firstName", "lastName", "age")); - - assertEquals(model.toString(), - "BeanModel[org.apache.tapestry5.internal.services.SimpleBean properties:firstName, lastName, age]"); - - PropertyModel age = model.get("age"); - - assertEquals(age.getLabel(), "Age"); - assertSame(age.getPropertyType(), int.class); - assertEquals(age.getDataType(), "number"); - - PropertyModel firstName = model.get("firstName"); - - assertEquals(firstName.getLabel(), "First Name"); - assertEquals(firstName.getPropertyType(), String.class); - assertEquals(firstName.getDataType(), "text"); - - assertEquals(model.get("lastName").getLabel(), "Last Name"); - - PropertyConduit conduit = model.get("lastName").getConduit(); - - SimpleBean instance = new SimpleBean(); - - instance.setLastName("Lewis Ship"); - - assertEquals(conduit.get(instance), "Lewis Ship"); - - conduit.set(instance, "TapestryDude"); - - assertEquals(instance.getLastName(), "TapestryDude"); - - // Now, one with some type coercion. - - age.getConduit().set(instance, "40"); - - assertEquals(instance.getAge(), 40); - - verify(); - } - - @Test - public void include_properties() - { - Messages messages = mockMessages(); - - stub_contains(messages, false); - - replay(); - - BeanModel model = source.create(SimpleBean.class, true, messages); - - assertSame(model.getBeanType(), SimpleBean.class); - - model.include("lastname", "firstname"); - - // Based on order of the getter methods (no longer alphabetical) - - assertEquals(model.getPropertyNames(), Arrays.asList("lastName", "firstName")); - - verify(); - } - - @Test - public void add_before() - { - Messages messages = mockMessages(); - PropertyConduit conduit = mockPropertyConduit(); - - Class propertyType = String.class; - - stub_contains(messages, false); - - expect(conduit.getPropertyType()).andReturn(propertyType).atLeastOnce(); - expect(conduit.getAnnotation(EasyMock.isA(Class.class))).andStubReturn(null); - - replay(); - - BeanModel model = source.create(SimpleBean.class, true, messages); - - assertEquals(model.getPropertyNames(), Arrays.asList("firstName", "lastName", "age")); - - // Note the use of case insensitivity here. - - PropertyModel property = model.add(RelativePosition.BEFORE, "lastname", "middleInitial", conduit); - - assertEquals(model.getPropertyNames(), Arrays.asList("firstName", "middleInitial", "lastName", "age")); - - assertEquals(property.getPropertyName(), "middleInitial"); - assertSame(property.getConduit(), conduit); - assertSame(property.getPropertyType(), propertyType); - - verify(); - } - - /** - * TAPESTRY-2202 - */ - @Test - public void new_instance() - { - Messages messages = mockMessages(); - - stub_contains(messages, false); - - replay(); - - BeanModel<SimpleBean> model = source.create(SimpleBean.class, true, messages); - - SimpleBean s1 = model.newInstance(); - - assertNotNull(s1); - - SimpleBean s2 = model.newInstance(); - - assertNotNull(s2); - assertNotSame(s1, s2); - - verify(); - } - - @Test - public void add_before_using_default_conduit() - { - Messages messages = mockMessages(); - - stub_contains(messages, false); - - replay(); - - BeanModel model = source.create(SimpleBean.class, true, messages); - - model.exclude("firstname"); - - assertEquals(model.getPropertyNames(), Arrays.asList("lastName", "age")); - - // Note the use of case insensitivity here. - - PropertyModel property = model.add(RelativePosition.BEFORE, "lastname", "firstName"); - - assertEquals(model.getPropertyNames(), Arrays.asList("firstName", "lastName", "age")); - - assertEquals(property.getPropertyName(), "firstName"); - assertSame(property.getPropertyType(), String.class); - - verify(); - } - - @Test - public void add_after() - { - Messages messages = mockMessages(); - PropertyConduit conduit = mockPropertyConduit(); - - Class propertyType = String.class; - - stub_contains(messages, false); - - expect(conduit.getPropertyType()).andReturn(propertyType).atLeastOnce(); - - expect(conduit.getAnnotation(EasyMock.isA(Class.class))).andStubReturn(null); - - replay(); - - BeanModel model = source.create(SimpleBean.class, true, messages); - - assertEquals(model.getPropertyNames(), Arrays.asList("firstName", "lastName", "age")); - - PropertyModel property = model.add(RelativePosition.AFTER, "firstname", "middleInitial", conduit); - - assertEquals(model.getPropertyNames(), Arrays.asList("firstName", "middleInitial", "lastName", "age")); - - assertEquals(property.getPropertyName(), "middleInitial"); - assertSame(property.getConduit(), conduit); - assertSame(property.getPropertyType(), propertyType); - - verify(); - } - - @Test - public void filtering_out_read_only_properties() - { - Messages messages = mockMessages(); - - stub_contains(messages, false); - - replay(); - - BeanModel model = source.create(ReadOnlyBean.class, true, messages); - - assertEquals(model.getPropertyNames(), Arrays.asList("value")); - - model = source.create(ReadOnlyBean.class, false, messages); - - assertEquals(model.getPropertyNames(), Arrays.asList("value", "readOnly")); - - verify(); - } - - @Test - public void non_text_property() - { - Messages messages = mockMessages(); - - stub_contains(messages, false); - - replay(); - - BeanModel model = source.create(EnumBean.class, true, messages); - - assertEquals(model.getPropertyNames(), Arrays.asList("token")); - - assertEquals(model.get("token").getDataType(), "enum"); - - verify(); - } - - @Test - public void add_duplicate_property_name_is_failure() - { - Messages messages = mockMessages(); - - stub_contains(messages, false); - - replay(); - - BeanModel model = source.create(SimpleBean.class, true, messages); - - try - { - model.add("age"); - unreachable(); - } catch (RuntimeException ex) - { - assertEquals( - ex.getMessage(), - "Bean editor model for org.apache.tapestry5.internal.services.SimpleBean already contains a property model for property \'age\'."); - } - - verify(); - } - - @Test - public void unknown_property_name() - { - Messages messages = mockMessages(); - - stub_contains(messages, false); - - replay(); - - BeanModel model = source.create(SimpleBean.class, true, messages); - - try - { - model.get("frobozz"); - unreachable(); - } catch (UnknownValueException ex) - { - assertEquals( - ex.getMessage(), - "Bean editor model for org.apache.tapestry5.internal.services.SimpleBean does not contain a property named \'frobozz\'."); - - assertListsEquals(ex.getAvailableValues().getValues(), "age", "firstName", "lastName"); - } - - verify(); - } - - @Test - public void unknown_property_id() - { - Messages messages = mockMessages(); - - stub_contains(messages, false); - - replay(); - - BeanModel model = source.create(SimpleBean.class, true, messages); - - model.addEmpty("shrub.foo()"); - - try - { - model.getById("frobozz"); - unreachable(); - } catch (UnknownValueException ex) - { - assertEquals( - ex.getMessage(), - "Bean editor model for org.apache.tapestry5.internal.services.SimpleBean does not contain a property with id \'frobozz\'."); - - assertListsEquals(ex.getAvailableValues().getValues(), "age", "firstName", "lastName", "shrubfoo"); - } - - verify(); - } - - @Test - public void get_added_property_by_name() - { - Messages messages = mockMessages(); - - stub_contains(messages, false); - - replay(); - - BeanModel model = source.create(SimpleBean.class, true, messages); - - PropertyModel pm = model.addEmpty("shrub.foo()"); - - assertSame(model.get("Shrub.Foo()"), pm); - - verify(); - } - - @Test - public void get_added_property_by_id() - { - Messages messages = mockMessages(); - - stub_contains(messages, false); - replay(); - - BeanModel model = source.create(SimpleBean.class, true, messages); - - PropertyModel pm = model.addEmpty("shrub.foo()"); - - assertSame(model.getById("ShrubFoo"), pm); - - verify(); - - } - - @Test - public void order_via_annotation() - { - Messages messages = mockMessages(); - - stub_contains(messages, false); - - replay(); - - BeanModel model = source.create(StoogeBean.class, true, messages); - - assertEquals(model.getPropertyNames(), Arrays.asList("larry", "moe", "shemp", "curly")); - - verify(); - } - - @Test - public void edit_property_label() - { - Messages messages = mockMessages(); - - stub_contains(messages, false); - - replay(); - - BeanModel model = source.create(SimpleBean.class, true, messages).get("age").label("Decrepitude").model(); - - assertEquals(model.get("age").getLabel(), "Decrepitude"); - - verify(); - } - - @Test - public void label_from_component_messages() - { - Messages messages = mockMessages(); - - stub_contains(messages, false); - - train_contains(messages, "age-label", true); - train_get(messages, "age-label", "Decrepitude"); - - replay(); - - BeanModel model = source.create(SimpleBean.class, true, messages); - - assertEquals(model.get("age").getLabel(), "Decrepitude"); - - verify(); - } - - @Test - public void array_type_bean() - { - Messages messages = mockMessages(); - - stub_contains(messages, false); - - replay(); - - BeanModel model = source.create(StringArrayBean.class, true, messages); - - // There's not editor for string arrays yet, so it won't show up normally. - - PropertyModel propertyModel = model.add("array"); - - assertSame(propertyModel.getPropertyType(), String[].class); - - String[] value = - {"foo", "bar"}; - - StringArrayBean bean = new StringArrayBean(); - - PropertyConduit conduit = propertyModel.getConduit(); - - conduit.set(bean, value); - - assertSame(bean.getArray(), value); - - assertSame(conduit.get(bean), value); - - verify(); - } - - @Test - public void composite_bean() - { - Messages messages = mockMessages(); - - stub_contains(messages, false); - - train_contains(messages, "simpleage-label", true); - train_get(messages, "simpleage-label", "Years of Age"); - - replay(); - - BeanModel model = source.create(CompositeBean.class, true, messages); - - // No editor for CompositeBean, so this will be empty. - - assertEquals(model.getPropertyNames(), Collections.emptyList()); - - // There's not editor for string arrays yet, so it won't show up normally. - - PropertyModel firstName = model.add("simple.firstName"); - - assertEquals(firstName.getLabel(), "First Name"); - - PropertyModel age = model.add("simple.age"); - assertEquals(age.getLabel(), "Years of Age"); - - CompositeBean bean = new CompositeBean(); - - firstName.getConduit().set(bean, "Fred"); - age.getConduit().set(bean, "97"); - - assertEquals(bean.getSimple().getFirstName(), "Fred"); - assertEquals(bean.getSimple().getAge(), 97); - - bean.getSimple().setAge(24); - - assertEquals(age.getConduit().get(bean), new Integer(24)); - - verify(); - } - - @Test - public void default_properties_exclude_write_only() + @Override + protected BeanModelSource create() { - Messages messages = mockMessages(); - - stub_contains(messages, false); - - replay(); - - BeanModel model = source.create(WriteOnlyBean.class, false, messages); - - assertEquals(model.getPropertyNames(), Arrays.asList("readOnly", "readWrite")); - - verify(); + return getObject(BeanModelSource.class, null); } - @Test - public void add_synthetic_property() - { - Messages messages = mockMessages(); - - stub_contains(messages, false); - - replay(); - - BeanModel model = source.create(SimpleBean.class, true, messages); - - PropertyModel property = model.addEmpty("placeholder"); - - assertFalse(property.isSortable()); - assertSame(property.getPropertyType(), Object.class); - assertEquals(property.getLabel(), "Placeholder"); - - verify(); - } - - @Test - public void add_missing_property_is_failure() - { - Messages messages = mockMessages(); - - stub_contains(messages, false); - - replay(); - - BeanModel model = source.create(SimpleBean.class, true, messages); - - try - { - model.add("doesNotExist"); - unreachable(); - } catch (PropertyExpressionException ex) - { - assertMessageContains(ex, "does not contain", "doesNotExist"); - } - - verify(); - } - - @Test - public void exclude_property() - { - Messages messages = mockMessages(); - - stub_contains(messages, false); - - replay(); - - BeanModel model = source.create(SimpleBean.class, true, messages); - - assertSame(model.exclude("age"), model); - - assertEquals(model.getPropertyNames(), Arrays.asList("firstName", "lastName")); - - verify(); - } - - @Test - public void exclude_unknown_property_is_noop() - { - Messages messages = mockMessages(); - - stub_contains(messages, false); - - replay(); - - BeanModel model = source.create(SimpleBean.class, true, messages); - - assertSame(model.exclude("frobozz"), model); - - assertEquals(model.getPropertyNames(), Arrays.asList("firstName", "lastName", "age")); - - verify(); - } - - @Test - public void nonvisual_properties_are_excluded() - { - Messages messages = mockMessages(); - - stub_contains(messages, false); - - replay(); - - BeanModel model = source.create(NonVisualBean.class, true, messages); - - assertEquals(model.getPropertyNames(), Arrays.asList("name")); - - verify(); - } - - @Test - public void reorder() - { - Messages messages = mockMessages(); - - stub_contains(messages, false); - - replay(); - - BeanModel model = source.create(SimpleBean.class, true, messages); - - assertSame(model.getBeanType(), SimpleBean.class); - - // Based on order of the getter methods (no longer alphabetical) - - assertEquals(model.getPropertyNames(), Arrays.asList("firstName", "lastName", "age")); - - // Testing a couple of things here: - // 1) case insensitive - // 2) unreferenced property names added to the end. - - model.reorder("lastname", "AGE"); - - assertEquals(model.getPropertyNames(), Arrays.asList("lastName", "age", "firstName")); - - verify(); - } - - @Test - public void reoder_from_annotation() - { - Messages messages = mockMessages(); - - stub_contains(messages, false); - - replay(); - - BeanModel model = source.create(PropertyOrderBean.class, true, messages); - - assertEquals(model.getPropertyNames(), Arrays.asList("third", "first", "second")); - - verify(); - } - - // https://issues.apache.org/jira/browse/TAP5-1798 - @Test - public void static_fields_are_ignored() - { - Messages messages = mockMessages(); - - stub_contains(messages, false); - - replay(); - - BeanModel<BeanWithStaticField> model = source.createDisplayModel(BeanWithStaticField.class, messages); - - assertListsEquals(model.getPropertyNames(), "name"); - - verify(); - } - - // https://issues.apache.org/jira/browse/TAP5-2305 - @Test - public void sortable_annotation() - { - Messages messages = mockMessages(); - - stub_contains(messages, false); - - replay(); - - BeanModel<SortableBean> model = source.createDisplayModel(SortableBean.class, messages); - model.add("nonSortableByDefault"); - model.add("sortable"); - - // checking whether non-@Sortable annotated properties still behave in the old ways - assertTrue(model.get("sortableByDefault").isSortable()); - assertFalse(model.get("nonSortableByDefault").isSortable()); - - // checking @Sortable itself - assertFalse(model.get("nonSortable").isSortable()); - assertTrue(model.get("sortable").isSortable()); - - verify(); - } - - final private static class SortableBean - { - private int sortableByDefault; - private int nonSortable; - private SimpleBean sortable; - private SimpleBean nonSortableByDefault; - - public int getSortableByDefault() - { - return sortableByDefault; - } - - @Sortable(false) - public int getNonSortable() - { - return nonSortable; - } - - @Sortable(true) - public SimpleBean getSortable() - { - return sortable; - } - - public SimpleBean getNonSortableByDefault() - { - return nonSortableByDefault; - } - - } - } http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/97bd4d5e/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/util/StrategyRegistry.java ---------------------------------------------------------------------- diff --git a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/util/StrategyRegistry.java b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/util/StrategyRegistry.java deleted file mode 100644 index fbdfc6a..0000000 --- a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/util/StrategyRegistry.java +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright 2006, 2007, 2008, 2011, 2012 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.tapestry5.ioc.util; - -import org.apache.tapestry5.ioc.internal.util.CollectionFactory; -import org.apache.tapestry5.ioc.internal.util.InheritanceSearch; - -import java.util.Collection; -import java.util.List; -import java.util.Map; - -/** - * A key component in implementing the "Gang of Four" Strategy pattern. A StrategyRegistry will match up a given input - * type with a registered strategy for that type. - * - * @param <A> the type of the strategy adapter - */ -public final class StrategyRegistry<A> -{ - private final Class<A> adapterType; - - private final boolean allowNonMatch; - - private final Map<Class, A> registrations = CollectionFactory.newMap(); - - private final Map<Class, A> cache = CollectionFactory.newConcurrentMap(); - - /** - * Used to identify types for which there is no matching adapter; we're using it as if it were a ConcurrentSet. - */ - private final Map<Class, Boolean> unmatched = CollectionFactory.newConcurrentMap(); - - private StrategyRegistry(Class<A> adapterType, Map<Class, A> registrations, boolean allowNonMatch) - { - this.adapterType = adapterType; - this.allowNonMatch = allowNonMatch; - - this.registrations.putAll(registrations); - } - - /** - * Creates a strategy registry for the given adapter type. The registry will be configured to require matches. - * - * @param adapterType the type of adapter retrieved from the registry - * @param registrations map of registrations (the contents of the map are copied) - */ - public static <A> StrategyRegistry<A> newInstance(Class<A> adapterType, - Map<Class, A> registrations) - { - return newInstance(adapterType, registrations, false); - } - - /** - * Creates a strategy registry for the given adapter type. - * - * @param adapterType the type of adapter retrieved from the registry - * @param registrations map of registrations (the contents of the map are copied) - * @param allowNonMatch if true, then the registry supports non-matches when retrieving an adapter - */ - public static <A> StrategyRegistry<A> newInstance( - Class<A> adapterType, - Map<Class, A> registrations, boolean allowNonMatch) - { - return new StrategyRegistry<A>(adapterType, registrations, allowNonMatch); - } - - public void clearCache() - { - cache.clear(); - unmatched.clear(); - } - - public Class<A> getAdapterType() - { - return adapterType; - } - - /** - * Gets an adapter for an object. Searches based on the value's class, unless the value is null, in which case, a - * search on class void is used. - * - * @param value for which an adapter is needed - * @return the adapter for the value or null if not found (and allowNonMatch is true) - * @throws IllegalArgumentException if no matching adapter may be found and allowNonMatch is false - */ - - public A getByInstance(Object value) - { - return get(value == null ? void.class : value.getClass()); - } - - /** - * Searches for an adapter corresponding to the given input type. - * - * @param type the type to search - * @return the adapter for the type or null if not found (and allowNonMatch is true) - * @throws IllegalArgumentException if no matching adapter may be found and allowNonMatch is false - */ - public A get(Class type) - { - - A result = cache.get(type); - - if (result != null) return result; - - if (unmatched.containsKey(type)) return null; - - - result = findMatch(type); - - // This may be null in the case that there is no match and we're allowing that to not - // be an error. That's why we check via containsKey. - - if (result != null) - { - cache.put(type, result); - } else - { - unmatched.put(type, true); - } - - return result; - } - - private A findMatch(Class type) - { - for (Class t : new InheritanceSearch(type)) - { - A result = registrations.get(t); - - if (result != null) return result; - } - - if (allowNonMatch) return null; - - // Report the error. These things really confused the hell out of people in Tap4, so we're - // going the extra mile on the exception message. - - List<String> names = CollectionFactory.newList(); - for (Class t : registrations.keySet()) - names.add(t.getName()); - - throw new UnknownValueException(String.format("No adapter from type %s to type %s is available.", type.getName(), adapterType.getName()), null, null, - new AvailableValues("registered types", registrations)); - } - - /** - * Returns the registered types for which adapters are available. - */ - public Collection<Class> getTypes() - { - return CollectionFactory.newList(registrations.keySet()); - } - - @Override - public String toString() - { - return String.format("StrategyRegistry[%s]", adapterType.getName()); - } -}