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.enhance;
016
017 import java.lang.reflect.Modifier;
018 import java.util.Iterator;
019
020 import org.apache.hivemind.ApplicationRuntimeException;
021 import org.apache.hivemind.ErrorLog;
022 import org.apache.hivemind.Location;
023 import org.apache.hivemind.service.BodyBuilder;
024 import org.apache.hivemind.service.ClassFabUtils;
025 import org.apache.hivemind.service.MethodSignature;
026 import org.apache.hivemind.util.Defense;
027 import org.apache.tapestry.IBinding;
028 import org.apache.tapestry.IComponent;
029 import org.apache.tapestry.spec.IComponentSpecification;
030 import org.apache.tapestry.spec.IParameterSpecification;
031
032 /**
033 * Responsible for creating properties for connected parameters.
034 *
035 * @author Howard M. Lewis Ship
036 * @since 4.0
037 */
038 public class ParameterPropertyWorker implements EnhancementWorker
039 {
040 private ErrorLog _errorLog;
041
042 public void performEnhancement(EnhancementOperation op, IComponentSpecification spec)
043 {
044 Iterator i = spec.getParameterNames().iterator();
045 while (i.hasNext())
046 {
047 String name = (String) i.next();
048
049 IParameterSpecification ps = spec.getParameter(name);
050
051 try
052 {
053 performEnhancement(op, name, ps);
054 }
055 catch (RuntimeException ex)
056 {
057 _errorLog.error(EnhanceMessages.errorAddingProperty(ps.getPropertyName(), op
058 .getBaseClass(), ex), ps.getLocation(), ex);
059 }
060 }
061 }
062
063 /**
064 * Performs the enhancement for a single parameter; this is about to change radically in release
065 * 4.0 but for the moment we're emulating 3.0 behavior.
066 */
067
068 private void performEnhancement(EnhancementOperation op, String parameterName,
069 IParameterSpecification ps)
070 {
071 // If the parameter name doesn't match, its because this is an alias
072 // for a true parameter; we ignore aliases.
073
074 if (!parameterName.equals(ps.getParameterName()))
075 return;
076
077 String propertyName = ps.getPropertyName();
078 String specifiedType = ps.getType();
079 boolean cache = ps.getCache();
080
081 addParameter(op, parameterName, propertyName, specifiedType, cache, ps.getLocation());
082 }
083
084 /**
085 * Adds a parameter as a (very smart) property.
086 *
087 * @param op
088 * the enhancement operation
089 * @param parameterName
090 * the name of the parameter (used to access the binding)
091 * @param propertyName
092 * the name of the property to create (usually, but not always, matches the
093 * parameterName)
094 * @param specifiedType
095 * the type declared in the DTD (only 3.0 DTD supports this), may be null (always
096 * null for 4.0 DTD)
097 * @param cache
098 * if true, then the value should be cached while the component renders; false (a
099 * much less common case) means that every access will work through binding object.
100 * @param location
101 * TODO
102 */
103
104 public void addParameter(EnhancementOperation op, String parameterName, String propertyName,
105 String specifiedType, boolean cache, Location location)
106 {
107 Defense.notNull(op, "op");
108 Defense.notNull(parameterName, "parameterName");
109 Defense.notNull(propertyName, "propertyName");
110
111 Class propertyType = EnhanceUtils.extractPropertyType(op, propertyName, specifiedType);
112
113 // 3.0 would allow connected parameter properties to be fully implemented
114 // in the component class. This is not supported in 4.0 and an existing
115 // property will be overwritten in the subclass.
116
117 op.claimProperty(propertyName);
118
119 // 3.0 used to support a property for the binding itself. That's
120 // no longer the case.
121
122 String fieldName = "_$" + propertyName;
123 String defaultFieldName = fieldName + "$Default";
124 String cachedFieldName = fieldName + "$Cached";
125
126 op.addField(fieldName, propertyType);
127 op.addField(defaultFieldName, propertyType);
128 op.addField(cachedFieldName, boolean.class);
129
130 buildAccessor(
131 op,
132 parameterName,
133 propertyName,
134 propertyType,
135 fieldName,
136 defaultFieldName,
137 cachedFieldName,
138 cache,
139 location);
140
141 buildMutator(
142 op,
143 parameterName,
144 propertyName,
145 propertyType,
146 fieldName,
147 defaultFieldName,
148 cachedFieldName,
149 location);
150
151 extendCleanupAfterRender(
152 op,
153 parameterName,
154 propertyName,
155 propertyType,
156 fieldName,
157 defaultFieldName,
158 cachedFieldName);
159 }
160
161 private void extendCleanupAfterRender(EnhancementOperation op, String parameterName,
162 String propertyName, Class propertyType, String fieldName, String defaultFieldName,
163 String cachedFieldName)
164 {
165 BodyBuilder cleanupBody = new BodyBuilder();
166
167 // Cached is only set when the field is updated in the accessor or mutator.
168 // After rendering, we want to clear the cached value and cached flag
169 // unless the binding is invariant, in which case it can stick around
170 // for some future render.
171
172 String bindingName = propertyName + "Binding";
173
174 addBindingReference(cleanupBody, bindingName, parameterName);
175
176 cleanupBody.addln("if ({0} && ! {1}.isInvariant())", cachedFieldName, bindingName);
177 cleanupBody.begin();
178 cleanupBody.addln("{0} = false;", cachedFieldName);
179 cleanupBody.addln("{0} = {1};", fieldName, defaultFieldName);
180 cleanupBody.end();
181
182 op.extendMethodImplementation(
183 IComponent.class,
184 EnhanceUtils.CLEANUP_AFTER_RENDER_SIGNATURE,
185 cleanupBody.toString());
186 }
187
188 private void addBindingReference(BodyBuilder builder, String localVariableName,
189 String parameterName)
190 {
191 builder.addln(
192 "{0} {1} = getBinding(\"{2}\");",
193 IBinding.class.getName(),
194 localVariableName,
195 parameterName);
196 }
197
198 private void buildMutator(EnhancementOperation op, String parameterName, String propertyName,
199 Class propertyType, String fieldName, String defaultFieldName, String cachedFieldName,
200 Location location)
201 {
202 BodyBuilder builder = new BodyBuilder();
203 builder.begin();
204
205 // The mutator method may be invoked from finishLoad(), in which
206 // case it changes the default value for the parameter property, if the parameter
207 // is not bound.
208
209 builder.addln("if (! isInActiveState())");
210 builder.begin();
211 builder.addln("{0} = $1;", defaultFieldName);
212 builder.addln("return;");
213 builder.end();
214
215 // In the normal state, we update the binding firstm, and it's an error
216 // if the parameter is not bound.
217
218 addBindingReference(builder, "binding", parameterName);
219
220 builder.addln("if (binding == null)");
221 builder.addln(
222 " throw new {0}(\"Parameter ''{1}'' is not bound and can not be updated.\");",
223 ApplicationRuntimeException.class.getName(),
224 parameterName);
225
226 // Always updated the binding first (which may fail with an exception).
227
228 builder.addln("binding.setObject(($w) $1);");
229
230 // While rendering, we store the updated value for fast
231 // access again (while the component is still rendering).
232 // The property value will be reset to default by cleanupAfterRender().
233
234 builder.addln("if (isRendering())");
235 builder.begin();
236 builder.addln("{0} = $1;", fieldName);
237 builder.addln("{0} = true;", cachedFieldName);
238 builder.end();
239
240 builder.end();
241
242 String mutatorMethodName = EnhanceUtils.createMutatorMethodName(propertyName);
243
244 op.addMethod(Modifier.PUBLIC, new MethodSignature(void.class, mutatorMethodName,
245 new Class[]
246 { propertyType }, null), builder.toString(), location);
247 }
248
249 // Package private for testing
250
251 void buildAccessor(EnhancementOperation op, String parameterName, String propertyName,
252 Class propertyType, String fieldName, String defaultFieldName, String cachedFieldName,
253 boolean cache, Location location)
254 {
255 BodyBuilder builder = new BodyBuilder();
256 builder.begin();
257
258 builder.addln("if ({0}) return {1};", cachedFieldName, fieldName);
259
260 addBindingReference(builder, "binding", parameterName);
261
262 builder.addln("if (binding == null) return {0};", defaultFieldName);
263
264 String javaTypeName = ClassFabUtils.getJavaClassName(propertyType);
265
266 builder.addln("{0} result = {1};", javaTypeName, EnhanceUtils.createUnwrapExpression(
267 op,
268 "binding",
269 propertyType));
270
271 // Values read via the binding are cached during the render of
272 // the component (if the parameter defines cache to be true, which
273 // is the default), or any time the binding is invariant
274 // (such as most bindings besides ExpressionBinding.
275
276 String expression = cache ? "isRendering() || binding.isInvariant()"
277 : "binding.isInvariant()";
278
279 builder.addln("if ({0})", expression);
280 builder.begin();
281 builder.addln("{0} = result;", fieldName);
282 builder.addln("{0} = true;", cachedFieldName);
283 builder.end();
284
285 builder.addln("return result;");
286
287 builder.end();
288
289 String accessorMethodName = op.getAccessorMethodName(propertyName);
290
291 op.addMethod(Modifier.PUBLIC, new MethodSignature(propertyType, accessorMethodName, null,
292 null), builder.toString(), location);
293 }
294
295 public void setErrorLog(ErrorLog errorLog)
296 {
297 _errorLog = errorLog;
298 }
299 }