You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by th...@apache.org on 2014/12/21 20:56:57 UTC
[31/35] tapestry-5 git commit: First final version of the BeanModel
and Commons packages.
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 <th...@apache.org>
Authored: Sun Dec 7 00:22:27 2014 -0200
Committer: Thiago H. de Paula Figueiredo <th...@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());
- }
-}