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