| // Copyright 2006, 2007, 2008 The Apache Software Foundation |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package org.apache.tapestry5.internal.transform; |
| |
| import org.apache.tapestry5.EventContext; |
| import org.apache.tapestry5.annotations.OnEvent; |
| import org.apache.tapestry5.ioc.util.BodyBuilder; |
| import org.apache.tapestry5.model.MutableComponentModel; |
| import org.apache.tapestry5.services.*; |
| |
| import java.util.Arrays; |
| import java.util.List; |
| |
| /** |
| * Provides implementations of the {@link org.apache.tapestry5.runtime.Component#dispatchComponentEvent(org.apache.tapestry5.runtime.ComponentEvent)} |
| * method, based on {@link org.apache.tapestry5.annotations.OnEvent} annotations. |
| */ |
| public class OnEventWorker implements ComponentClassTransformWorker |
| { |
| static final String OBJECT_ARRAY_TYPE = "java.lang.Object[]"; |
| |
| static final String EVENT_CONTEXT_TYPE = EventContext.class.getName(); |
| |
| static final String LIST_TYPE = List.class.getName(); |
| |
| private final static int ANY_NUMBER_OF_PARAMETERS = -1; |
| |
| public void transform(final ClassTransformation transformation, MutableComponentModel model) |
| { |
| MethodFilter filter = new MethodFilter() |
| { |
| public boolean accept(TransformMethodSignature signature) |
| { |
| return signature.getMethodName().startsWith("on") || transformation.getMethodAnnotation(signature, |
| OnEvent.class) != null; |
| } |
| }; |
| |
| List<TransformMethodSignature> methods = transformation.findMethods(filter); |
| |
| // No methods, no work. |
| |
| if (methods.isEmpty()) return; |
| |
| BodyBuilder builder = new BodyBuilder(); |
| builder.begin(); |
| |
| builder.addln("if ($1.isAborted()) return $_;"); |
| |
| builder.addln("try"); |
| builder.begin(); |
| |
| for (TransformMethodSignature method : methods) |
| addCodeForMethod(builder, method, transformation); |
| |
| builder.end(); // try |
| |
| // Runtime exceptions pass right through. |
| |
| builder.addln("catch (RuntimeException ex) { throw ex; }"); |
| |
| // Wrap others in a RuntimeException to communicate them up. |
| |
| builder.addln("catch (Exception ex) { throw new RuntimeException(ex); } "); |
| |
| builder.end(); |
| |
| transformation.extendMethod(TransformConstants.DISPATCH_COMPONENT_EVENT, builder.toString()); |
| } |
| |
| private void addCodeForMethod(BodyBuilder builder, TransformMethodSignature method, |
| ClassTransformation transformation) |
| { |
| // $1 is the event |
| |
| int parameterCount = getParameterCount(method); |
| |
| OnEvent annotation = transformation.getMethodAnnotation(method, OnEvent.class); |
| |
| String eventType = extractEventType(method, annotation); |
| |
| String componentId = extractComponentId(method, annotation); |
| |
| |
| builder.addln("if ($1.matches(\"%s\", \"%s\", %d))", eventType, componentId, parameterCount); |
| builder.begin(); |
| |
| // Ensure that we return true, because *some* event handler method was invoked, |
| // even if it chose not to abort the event. |
| |
| builder.addln("$_ = true;"); |
| |
| builder.addln("$1.setMethodDescription(\"%s\");", transformation.getMethodIdentifier(method)); |
| |
| boolean isNonVoid = !method.getReturnType().equals("void"); |
| |
| // Store the result, converting primitives to wrappers automatically. |
| |
| if (isNonVoid) builder.add("if ($1.storeResult(($w) "); |
| |
| builder.add("%s(", method.getMethodName()); |
| |
| buildMethodParameters(builder, method); |
| |
| if (isNonVoid) builder.addln("))) return true;"); |
| else builder.addln(");"); |
| |
| builder.end(); |
| } |
| |
| private String extractComponentId(TransformMethodSignature method, OnEvent annotation) |
| { |
| if (annotation != null) return annotation.component(); |
| |
| // Method name started with "on". Extract the component id, if present. |
| |
| String name = method.getMethodName(); |
| |
| int fromx = name.indexOf("From"); |
| |
| if (fromx < 0) return ""; |
| |
| return name.substring(fromx + 4); |
| } |
| |
| private String extractEventType(TransformMethodSignature method, OnEvent annotation) |
| { |
| if (annotation != null) return annotation.value(); |
| |
| // Method name started with "on". Extract the event type. |
| |
| String name = method.getMethodName(); |
| |
| int fromx = name.indexOf("From"); |
| |
| return fromx == -1 ? name.substring(2) : name.substring(2, fromx); |
| } |
| |
| private int getParameterCount(TransformMethodSignature method) |
| { |
| String[] types = method.getParameterTypes(); |
| |
| if (types.length == 0) return 0; |
| |
| if (types.length == 1) |
| { |
| String soloType = types[0]; |
| |
| if (soloType.equals(OBJECT_ARRAY_TYPE) || soloType.equals(EVENT_CONTEXT_TYPE) || soloType.equals(LIST_TYPE)) |
| return ANY_NUMBER_OF_PARAMETERS; |
| } |
| |
| return types.length; |
| } |
| |
| private void buildMethodParameters(BodyBuilder builder, TransformMethodSignature method) |
| { |
| int contextIndex = 0; |
| |
| for (int i = 0; i < method.getParameterTypes().length; i++) |
| { |
| if (i > 0) builder.add(", "); |
| |
| String type = method.getParameterTypes()[i]; |
| |
| // Type Object[] is a special case, it gets all of the context parameters in one go. |
| |
| if (type.equals(OBJECT_ARRAY_TYPE)) |
| { |
| builder.add("$1.getContext()"); |
| continue; |
| } |
| |
| // Added for TAPESTRY-2177 |
| |
| if (type.equals(EVENT_CONTEXT_TYPE)) |
| { |
| builder.add("$1.getEventContext()"); |
| continue; |
| } |
| |
| // Added for TAPESTRY-1999 |
| |
| if (type.equals(LIST_TYPE)) |
| { |
| builder.add("%s.asList($1.getContext())", Arrays.class.getName()); |
| continue; |
| } |
| |
| boolean isPrimitive = TransformUtils.isPrimitive(type); |
| String wrapperType = TransformUtils.getWrapperTypeName(type); |
| |
| // Add a cast to the wrapper type up front |
| |
| if (isPrimitive) builder.add("("); |
| |
| // A cast is always needed (i.e. from java.lang.Object to, say, java.lang.String, etc.). |
| // The wrapper type will be the actual type unless its a primitive, in which case it |
| // really will be the wrapper type. |
| |
| builder.add("(%s)", wrapperType); |
| |
| // The strings for desired type name will likely repeat a bit; it may be |
| // worth it to inject them as final fields. Could increase the number |
| // of constructor parameters pretty dramatically, however, and will reduce |
| // the readability of the output method bodies. |
| |
| builder.add("$1.coerceContext(%d, \"%s\")", contextIndex++, wrapperType); |
| |
| // and invoke a method on the cast value to get back to primitive |
| if (isPrimitive) builder.add(").%s()", TransformUtils.getUnwrapperMethodName(type)); |
| } |
| } |
| } |