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.util.List;
018:
019: import org.apache.tapestry.annotations.OnEvent;
020: import org.apache.tapestry.ioc.internal.util.InternalUtils;
021: import org.apache.tapestry.ioc.util.BodyBuilder;
022: import org.apache.tapestry.model.MutableComponentModel;
023: import org.apache.tapestry.runtime.Component;
024: import org.apache.tapestry.services.ClassTransformation;
025: import org.apache.tapestry.services.ComponentClassTransformWorker;
026: import org.apache.tapestry.services.MethodFilter;
027: import org.apache.tapestry.services.MethodSignature;
028: import org.apache.tapestry.services.TransformConstants;
029: import org.apache.tapestry.services.TransformUtils;
030:
031: /**
032: * Provides implementations of the
033: * {@link Component#handleComponentEvent(org.apache.tapestry.runtime.ComponentEvent)} method, based
034: * on {@link OnEvent} annotations.
035: */
036: public class OnEventWorker implements ComponentClassTransformWorker {
037: static final String OBJECT_ARRAY_TYPE = "java.lang.Object[]";
038:
039: private final static int ANY_NUMBER_OF_PARAMETERS = -1;
040:
041: public void transform(final ClassTransformation transformation,
042: MutableComponentModel model) {
043: MethodFilter filter = new MethodFilter() {
044: public boolean accept(MethodSignature signature) {
045: return signature.getMethodName().startsWith("on")
046: || transformation.getMethodAnnotation(
047: signature, OnEvent.class) != null;
048: };
049: };
050:
051: List<MethodSignature> methods = transformation
052: .findMethods(filter);
053:
054: // No methods, no work.
055:
056: if (methods.isEmpty())
057: return;
058:
059: BodyBuilder builder = new BodyBuilder();
060: builder.begin();
061:
062: builder.addln("if ($1.isAborted()) return $_;");
063:
064: for (MethodSignature method : methods)
065: addCodeForMethod(builder, method, transformation);
066:
067: builder.end();
068:
069: transformation.extendMethod(
070: TransformConstants.HANDLE_COMPONENT_EVENT, builder
071: .toString());
072: }
073:
074: private void addCodeForMethod(BodyBuilder builder,
075: MethodSignature method, ClassTransformation transformation) {
076: // $1 is the event
077:
078: int closeCount = 0;
079:
080: int parameterCount = getParameterCount(method);
081:
082: if (parameterCount > 0) {
083: builder.addln("if ($1.matchesByParameterCount(%d))",
084: parameterCount);
085: builder.begin();
086:
087: closeCount++;
088: }
089:
090: OnEvent annotation = transformation.getMethodAnnotation(method,
091: OnEvent.class);
092:
093: String eventType = extractEventType(method, annotation);
094:
095: if (InternalUtils.isNonBlank(eventType)) {
096: builder.addln("if ($1.matchesByEventType(\"%s\"))",
097: eventType);
098: builder.begin();
099:
100: closeCount++;
101: }
102:
103: String componentId = extractComponentId(method, annotation);
104:
105: if (InternalUtils.isNonBlank(componentId)) {
106: builder.addln("if ($1.matchesByComponentId(\"%s\"))",
107: componentId);
108: builder.begin();
109:
110: closeCount++;
111: }
112:
113: // Ensure that we return true, because *some* event handler method was invoked,
114: // even if it chose not to abort the event.
115:
116: builder.addln("$_ = true;");
117:
118: // Several subsequent calls need to know the method name.
119:
120: builder.addln("$1.setSource(this, \"%s\");", transformation
121: .getMethodIdentifier(method));
122:
123: boolean isNonVoid = !method.getReturnType().equals("void");
124:
125: // Store the result, converting primitives to wrappers automatically.
126:
127: if (isNonVoid)
128: builder.add("if ($1.storeResult(($w) ");
129:
130: builder.add("%s(", method.getMethodName());
131:
132: buildMethodParameters(builder, method);
133:
134: if (isNonVoid)
135: builder.addln("))) return true;");
136: else
137: builder.addln(");");
138:
139: for (int i = 0; i < closeCount; i++)
140: builder.end();
141: }
142:
143: private String extractComponentId(MethodSignature method,
144: OnEvent annotation) {
145: if (annotation != null)
146: return annotation.component();
147:
148: // Method name started with "on". Extract the component id, if present.
149:
150: String name = method.getMethodName();
151:
152: int fromx = name.indexOf("From");
153:
154: if (fromx < 0)
155: return "";
156:
157: return name.substring(fromx + 4);
158: }
159:
160: private String extractEventType(MethodSignature method,
161: OnEvent annotation) {
162: if (annotation != null)
163: return annotation.value();
164:
165: // Method name started with "on". Extract the event type.
166:
167: String name = method.getMethodName();
168:
169: int fromx = name.indexOf("From");
170:
171: String eventName = fromx == -1 ? name.substring(2) : name
172: .substring(2, fromx);
173:
174: // This is intended for onAnyFromComponentId, but just onAny works too (and is dangerous).
175:
176: if (eventName.equalsIgnoreCase("AnyEvent"))
177: return "";
178:
179: return eventName;
180: }
181:
182: private int getParameterCount(MethodSignature method) {
183: String[] types = method.getParameterTypes();
184:
185: if (types.length == 0)
186: return 0;
187:
188: if (types[0].equals(OBJECT_ARRAY_TYPE))
189: return ANY_NUMBER_OF_PARAMETERS;
190:
191: // TODO: If the first parameter is Object[], that should be the only parameter.
192: // Otherwise, Object[] should not be allowed.
193:
194: return types.length;
195: }
196:
197: private void buildMethodParameters(BodyBuilder builder,
198: MethodSignature method) {
199: int contextIndex = 0;
200:
201: for (int i = 0; i < method.getParameterTypes().length; i++) {
202: if (i > 0)
203: builder.add(", ");
204:
205: String type = method.getParameterTypes()[i];
206:
207: // Type Object[] is a special case, it gets all of the context parameters in one go.
208:
209: if (type.equals(OBJECT_ARRAY_TYPE)) {
210: builder.add("$1.getContext()");
211: continue;
212: }
213:
214: boolean isPrimitive = TransformUtils.isPrimitive(type);
215: String wrapperType = TransformUtils
216: .getWrapperTypeName(type);
217:
218: // Add a cast to the wrapper type up front
219:
220: if (isPrimitive)
221: builder.add("(");
222:
223: // A cast is always needed (i.e. from java.lang.Object to, say, java.lang.String, etc.).
224: // The wrapper type will be the actual type unless its a primitive, in which case it
225: // really will be the wrapper type.
226:
227: builder.add("(%s)", wrapperType);
228:
229: // The strings for desired type name will likely repeat a bit; it may be
230: // worth it to inject them as final fields. Could increase the number
231: // of constructor parameters pretty dramatically, however, and will reduce
232: // the readability of the output method bodies.
233:
234: builder.add("$1.coerceContext(%d, \"%s\")", contextIndex++,
235: wrapperType);
236:
237: // and invoke a method on the cast value to get back to primitive
238: if (isPrimitive)
239: builder.add(").%s()", TransformUtils
240: .getUnwrapperMethodName(type));
241: }
242: }
243: }
|