001: // Copyright 2006, 2007 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.internal.services;
016:
017: import java.lang.reflect.Modifier;
018: import java.util.List;
019:
020: import org.apache.tapestry.Binding;
021: import org.apache.tapestry.annotations.Parameter;
022: import org.apache.tapestry.internal.InternalComponentResources;
023: import org.apache.tapestry.internal.bindings.LiteralBinding;
024: import org.apache.tapestry.ioc.internal.util.InternalUtils;
025: import org.apache.tapestry.ioc.util.BodyBuilder;
026: import org.apache.tapestry.model.MutableComponentModel;
027: import org.apache.tapestry.services.BindingSource;
028: import org.apache.tapestry.services.ClassTransformation;
029: import org.apache.tapestry.services.ComponentClassTransformWorker;
030: import org.apache.tapestry.services.FieldFilter;
031: import org.apache.tapestry.services.MethodFilter;
032: import org.apache.tapestry.services.MethodSignature;
033: import org.apache.tapestry.services.TransformConstants;
034: import org.apache.tapestry.services.TransformUtils;
035:
036: /**
037: * Responsible for identifying parameters via the {@link org.apache.tapestry.annotations.Parameter}
038: * annotation on component fields. This is one of the most complex of the transformations.
039: */
040: public class ParameterWorker implements ComponentClassTransformWorker {
041: private static final String BIND_METHOD_NAME = ParameterWorker.class
042: .getName()
043: + ".bind";
044:
045: private final BindingSource _bindingSource;
046:
047: public ParameterWorker(BindingSource bindingSource) {
048: _bindingSource = bindingSource;
049: }
050:
051: public void transform(final ClassTransformation transformation,
052: MutableComponentModel model) {
053: FieldFilter filter = new FieldFilter() {
054: public boolean accept(String fieldName, String fieldType) {
055: Parameter annotation = transformation
056: .getFieldAnnotation(fieldName, Parameter.class);
057:
058: return annotation != null && annotation.principal();
059: }
060: };
061:
062: List<String> principleFieldNames = transformation
063: .findFields(filter);
064:
065: convertFieldsIntoParameters(transformation, model,
066: principleFieldNames);
067:
068: // Now convert the rest.
069:
070: List<String> fieldNames = transformation
071: .findFieldsWithAnnotation(Parameter.class);
072:
073: convertFieldsIntoParameters(transformation, model, fieldNames);
074: }
075:
076: private void convertFieldsIntoParameters(
077: ClassTransformation transformation,
078: MutableComponentModel model, List<String> fieldNames) {
079: for (String name : fieldNames)
080: convertFieldIntoParameter(name, transformation, model);
081: }
082:
083: private void convertFieldIntoParameter(String name,
084: ClassTransformation transformation,
085: MutableComponentModel model) {
086: Parameter annotation = transformation.getFieldAnnotation(name,
087: Parameter.class);
088:
089: String parameterName = getParameterName(name, annotation.name());
090:
091: model.addParameter(parameterName, annotation.required(),
092: annotation.defaultPrefix());
093:
094: String type = transformation.getFieldType(name);
095:
096: boolean cache = annotation.cache();
097:
098: String cachedFieldName = transformation.addField(
099: Modifier.PRIVATE, "boolean", name + "_cached");
100:
101: String resourcesFieldName = transformation
102: .getResourcesFieldName();
103:
104: String invariantFieldName = addParameterSetup(name, annotation
105: .defaultPrefix(), annotation.value(), parameterName,
106: cachedFieldName, cache, type, resourcesFieldName,
107: transformation);
108:
109: addReaderMethod(name, cachedFieldName, invariantFieldName,
110: cache, parameterName, type, resourcesFieldName,
111: transformation);
112:
113: addWriterMethod(name, cachedFieldName, cache, parameterName,
114: type, resourcesFieldName, transformation);
115:
116: transformation.claimField(name, annotation);
117: }
118:
119: /**
120: * Returns the name of a field that stores whether the parameter binding is invariant.
121: */
122: private String addParameterSetup(String fieldName,
123: String defaultPrefix, String defaultBinding,
124: String parameterName, String cachedFieldName,
125: boolean cache, String fieldType, String resourcesFieldName,
126: ClassTransformation transformation) {
127: String defaultFieldName = transformation.addField(
128: Modifier.PRIVATE, fieldType, fieldName + "_default");
129:
130: String invariantFieldName = transformation.addField(
131: Modifier.PRIVATE, "boolean", fieldName + "_invariant");
132:
133: BodyBuilder builder = new BodyBuilder();
134: builder.begin();
135:
136: addDefaultBindingSetup(parameterName, defaultPrefix,
137: defaultBinding, resourcesFieldName, transformation,
138: builder);
139:
140: builder.addln("%s = %s.isInvariant(\"%s\");",
141: invariantFieldName, resourcesFieldName, parameterName);
142:
143: // Store the current value of the field into the default field. This value will
144: // be used to reset the field after rendering.
145:
146: builder.addln("%s = %s;", defaultFieldName, fieldName);
147: builder.end();
148:
149: transformation.extendMethod(
150: TransformConstants.CONTAINING_PAGE_DID_LOAD_SIGNATURE,
151: builder.toString());
152:
153: // Now, when the component completes rendering, ensure that any variant parameters are
154: // are returned to default value. This isn't necessary when the parameter is not cached,
155: // because (unless the binding is invariant), there's no value to get rid of (and if it is
156: // invariant, there's no need to get rid of it).
157:
158: if (cache) {
159: builder.clear();
160:
161: builder.addln("if (! %s)", invariantFieldName);
162: builder.begin();
163: builder.addln("%s = %s;", fieldName, defaultFieldName);
164: builder.addln("%s = false;", cachedFieldName);
165: builder.end();
166:
167: transformation.extendMethod(
168: TransformConstants.POST_RENDER_CLEANUP_SIGNATURE,
169: builder.toString());
170: }
171:
172: return invariantFieldName;
173: }
174:
175: private void addDefaultBindingSetup(String parameterName,
176: String defaultPrefix, String defaultBinding,
177: String resourcesFieldName,
178: ClassTransformation transformation, BodyBuilder builder) {
179: if (InternalUtils.isNonBlank(defaultBinding)) {
180: builder.addln("if (! %s.isBound(\"%s\"))",
181: resourcesFieldName, parameterName);
182:
183: String bindingFactoryFieldName = transformation
184: .addInjectedField(BindingSource.class,
185: "bindingSource", _bindingSource);
186:
187: builder
188: .addln(
189: " %s.bindParameter(\"%s\", %s.newBinding(\"default %2$s\", %1$s, \"%s\", \"%s\"));",
190: resourcesFieldName, parameterName,
191: bindingFactoryFieldName, defaultPrefix,
192: defaultBinding);
193:
194: return;
195:
196: }
197:
198: // If no default binding expression provided in the annotation, then look for a default
199: // binding method to provide the binding.
200:
201: final String methodName = "default"
202: + InternalUtils.capitalize(parameterName);
203:
204: MethodFilter filter = new MethodFilter() {
205: public boolean accept(MethodSignature signature) {
206: return signature.getParameterTypes().length == 0
207: && signature.getMethodName().equals(methodName);
208: }
209: };
210:
211: // This will match exactly 0 or 1 methods, and if it matches, we know the name
212: // of the method.
213:
214: List<MethodSignature> signatures = transformation
215: .findMethods(filter);
216:
217: if (signatures.isEmpty())
218: return;
219:
220: builder.addln("if (! %s.isBound(\"%s\"))", resourcesFieldName,
221: parameterName);
222: builder.addln(" %s(\"%s\", %s, %s());", BIND_METHOD_NAME,
223: parameterName, resourcesFieldName, methodName);
224: }
225:
226: private void addWriterMethod(String fieldName,
227: String cachedFieldName, boolean cache,
228: String parameterName, String fieldType,
229: String resourcesFieldName,
230: ClassTransformation transformation) {
231: BodyBuilder builder = new BodyBuilder();
232: builder.begin();
233:
234: // Before the component is loaded, updating the property sets the default value
235: // for the parameter. The value is stored in the field, but will be
236: // rolled into default field inside containingPageDidLoad().
237:
238: builder.addln("if (! %s.isLoaded())", resourcesFieldName);
239: builder.begin();
240: builder.addln("%s = $1;", fieldName);
241: builder.addln("return;");
242: builder.end();
243:
244: // Always start by updating the parameter; this will implicitly check for
245: // read-only or unbound parameters. $1 is the single parameter
246: // to the method.
247:
248: builder.addln("if (%s.isBound(\"%s\"))", resourcesFieldName,
249: parameterName);
250: builder.addln(" %s.writeParameter(\"%s\", ($w)$1);",
251: resourcesFieldName, parameterName);
252:
253: builder.addln("%s = $1;", fieldName);
254:
255: if (cache)
256: builder.addln("%s = %s.isRendering();", cachedFieldName,
257: resourcesFieldName);
258:
259: builder.end();
260:
261: String methodName = transformation.newMemberName(
262: "update_parameter", parameterName);
263:
264: MethodSignature signature = new MethodSignature(
265: Modifier.PRIVATE, "void", methodName,
266: new String[] { fieldType }, null);
267:
268: transformation.addMethod(signature, builder.toString());
269:
270: transformation.replaceWriteAccess(fieldName, methodName);
271: }
272:
273: /** Adds a private method that will be the replacement for read-access to the field. */
274: private void addReaderMethod(String fieldName,
275: String cachedFieldName, String invariantFieldName,
276: boolean cache, String parameterName, String fieldType,
277: String resourcesFieldName,
278: ClassTransformation transformation) {
279: BodyBuilder builder = new BodyBuilder();
280: builder.begin();
281:
282: // While the component is still loading, or when the value for the component is cached,
283: // or if the value is not bound, then return the current value of the field.
284:
285: builder
286: .addln(
287: "if (%s || ! %s.isLoaded() || ! %<s.isBound(\"%s\")) return %s;",
288: cachedFieldName, resourcesFieldName,
289: parameterName, fieldName);
290:
291: String cast = TransformUtils.getWrapperTypeName(fieldType);
292:
293: // The ($r) cast will convert the result to the method return type; generally
294: // this does nothing. but for primitive types, it will unwrap
295: // the wrapper type back to a primitive.
296:
297: builder
298: .addln(
299: "%s result = ($r) ((%s) %s.readParameter(\"%s\", $type));",
300: fieldType, cast, resourcesFieldName,
301: parameterName);
302:
303: // If the binding is invariant, then it's ok to cache. Othewise, its only
304: // ok to cache if a) the @Parameter says to cache and b) the component
305: // is rendering at the point when field is accessed.
306:
307: builder.add("if (%s", invariantFieldName);
308:
309: if (cache)
310: builder.add(" || %s.isRendering()", resourcesFieldName);
311:
312: builder.addln(")");
313: builder.begin();
314: builder.addln("%s = result;", fieldName);
315: builder.addln("%s = true;", cachedFieldName);
316: builder.end();
317:
318: builder.addln("return result;");
319: builder.end();
320:
321: String methodName = transformation.newMemberName(
322: "read_parameter", parameterName);
323:
324: MethodSignature signature = new MethodSignature(
325: Modifier.PRIVATE, fieldType, methodName, null, null);
326:
327: transformation.addMethod(signature, builder.toString());
328:
329: transformation.replaceReadAccess(fieldName, methodName);
330: }
331:
332: private String getParameterName(String fieldName,
333: String annotatedName) {
334: if (InternalUtils.isNonBlank(annotatedName))
335: return annotatedName;
336:
337: return InternalUtils.stripMemberPrefix(fieldName);
338: }
339:
340: public static void bind(String parameterName,
341: InternalComponentResources resources, Object value) {
342: if (value == null)
343: return;
344:
345: if (value instanceof Binding) {
346: Binding binding = (Binding) value;
347:
348: resources.bindParameter(parameterName, binding);
349: return;
350: }
351:
352: resources.bindParameter(parameterName, new LiteralBinding(
353: "default " + parameterName, value, null));
354: }
355: }
|