001 // Copyright 2004, 2005 The Apache Software Foundation
002 //
003 // Licensed under the Apache License, Version 2.0 (the "License");
004 // you may not use this file except in compliance with the License.
005 // You may obtain a copy of the License at
006 //
007 // http://www.apache.org/licenses/LICENSE-2.0
008 //
009 // Unless required by applicable law or agreed to in writing, software
010 // distributed under the License is distributed on an "AS IS" BASIS,
011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012 // See the License for the specific language governing permissions and
013 // limitations under the License.
014
015 package org.apache.tapestry.test;
016
017 import java.util.ArrayList;
018 import java.util.HashMap;
019 import java.util.Iterator;
020 import java.util.List;
021 import java.util.Map;
022
023 import org.apache.hivemind.ApplicationRuntimeException;
024 import org.apache.hivemind.ClassResolver;
025 import org.apache.hivemind.Location;
026 import org.apache.hivemind.Resource;
027 import org.apache.hivemind.impl.DefaultClassResolver;
028 import org.apache.hivemind.service.ClassFactory;
029 import org.apache.hivemind.service.impl.ClassFactoryImpl;
030 import org.apache.hivemind.util.ClasspathResource;
031 import org.apache.hivemind.util.PropertyUtils;
032 import org.apache.tapestry.Tapestry;
033 import org.apache.tapestry.enhance.AbstractPropertyWorker;
034 import org.apache.tapestry.enhance.EnhancementOperationImpl;
035 import org.apache.tapestry.enhance.EnhancementWorker;
036 import org.apache.tapestry.services.ComponentConstructor;
037 import org.apache.tapestry.spec.ComponentSpecification;
038 import org.apache.tapestry.spec.IComponentSpecification;
039 import org.apache.tapestry.util.DescribedLocation;
040
041 /**
042 * A utility class that is used to instantiate abstract Tapestry pages and components. It creates,
043 * at runtime, a subclass where all abstract properties are filled in (each property complete with
044 * an instance variable, an accessor method and a mutator method). This isn't quite the same as how
045 * the class is enhanced at runtime (though it does use a subset of the same
046 * {@link org.apache.tapestry.enhance.EnhancementWorker code}), but is sufficient to unit test the
047 * class, especially listener methods.
048 * <p>
049 * One part of the enhancement is that the
050 * {@link org.apache.tapestry.IComponent#getSpecification() specification} and
051 * {@link org.apache.tapestry.IComponent#getMessages() messages} properties of the page or
052 * component class are converted into read/write properties that can be set via reflection
053 * (including {@link #newInstance(Class, Map)}.
054 *
055 * @author Howard Lewis Ship
056 * @since 4.0
057 */
058 public class Creator
059 {
060 /**
061 * Keyed on Class, value is an {@link ComponentConstructor}.
062 */
063 private final Map _constructors = new HashMap();
064
065 private final ClassFactory _classFactory = new ClassFactoryImpl();
066
067 private final ClassResolver _classResolver = new DefaultClassResolver();
068
069 private final List _workers = new ArrayList();
070
071 private final Resource _creatorResource = new ClasspathResource(_classResolver,
072 "/CreatorLocation");
073
074 private final Location _creatorLocation = new DescribedLocation(_creatorResource,
075 "Creator Location");
076
077 {
078 // Overrride AbstractComponent's implementations of
079 // these two properties (making them read/write).
080
081 _workers.add(new CreatePropertyWorker("messages", _creatorLocation));
082 _workers.add(new CreatePropertyWorker("specification", _creatorLocation));
083
084 // Implement any abstract properties.
085 // Note that we don't bother setting the errorLog property
086 // so failures may turn into NPEs.
087
088 _workers.add(new AbstractPropertyWorker());
089 }
090
091 private ComponentConstructor createComponentConstructor(Class inputClass)
092 {
093 if (inputClass.isInterface() || inputClass.isPrimitive() || inputClass.isArray())
094 throw new IllegalArgumentException(ScriptMessages.wrongTypeForEnhancement(inputClass));
095
096 EnhancementOperationImpl op = new EnhancementOperationImpl(_classResolver,
097 new ComponentSpecification(), inputClass, _classFactory, null);
098
099 IComponentSpecification spec = new ComponentSpecification();
100 spec.setLocation(_creatorLocation);
101
102 Iterator i = _workers.iterator();
103 while (i.hasNext())
104 {
105 EnhancementWorker worker = (EnhancementWorker) i.next();
106
107 worker.performEnhancement(op, spec);
108 }
109
110 return op.getConstructor();
111 }
112
113 private ComponentConstructor getComponentConstructor(Class inputClass)
114 {
115 ComponentConstructor result = (ComponentConstructor) _constructors.get(inputClass);
116
117 if (result == null)
118 {
119 result = createComponentConstructor(inputClass);
120
121 _constructors.put(inputClass, result);
122 }
123
124 return result;
125 }
126
127 /**
128 * Given a particular abstract class; will create an instance of that class. A subclass is
129 * created with all abstract properties filled in with ordinary implementations.
130 */
131 public Object newInstance(Class abstractClass)
132 {
133 ComponentConstructor constructor = getComponentConstructor(abstractClass);
134
135 try
136 {
137 return constructor.newInstance();
138 }
139 catch (Exception ex)
140 {
141 throw new ApplicationRuntimeException(ScriptMessages.unableToInstantiate(
142 abstractClass,
143 ex));
144 }
145 }
146
147 /**
148 * Creates a new instance of a given class, and then initializes properties of the instance. The
149 * map contains string keys that are property names, and object values.
150 */
151 public Object newInstance(Class abstractClass, Map properties)
152 {
153 Object result = newInstance(abstractClass);
154
155 if (properties != null)
156 {
157 Iterator i = properties.entrySet().iterator();
158
159 while (i.hasNext())
160 {
161 Map.Entry e = (Map.Entry) i.next();
162
163 String propertyName = (String) e.getKey();
164
165 PropertyUtils.write(result, propertyName, e.getValue());
166 }
167 }
168
169 return result;
170 }
171
172 /**
173 * A convienience (useful in test code) for invoking {@link #newInstance(Class, Map)}. The Map
174 * is constructed from the properties array, which consists of alternating keys and values.
175 */
176
177 public Object newInstance(Class abstractClass, Object[] properties)
178 {
179 Map propertyMap = Tapestry.convertArrayToMap(properties);
180
181 return newInstance(abstractClass, propertyMap);
182 }
183 }