| /* |
| * Copyright 2006 Google Inc. |
| * |
| * 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 com.google.gwt.dev.util.xml; |
| |
| import com.google.gwt.core.ext.UnableToCompleteException; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * Represents metadata about a handler method in a class derived from {@link Schema}. |
| */ |
| public final class HandlerMethod { |
| |
| private static final HandlerParam[] EMPTY_HANDLERPARAMS = new HandlerParam[0]; |
| |
| // A schema level that ignores everything. |
| private static final Schema sArbitraryChildHandler = new Schema() { |
| |
| @Override |
| public void onBadAttributeValue(int lineNumber, String elemName, |
| String attrName, String attrValue, Class<?> paramType) { |
| // Ignore |
| } |
| |
| @Override |
| public void onHandlerException(int lineNumber, String elemLocalName, |
| Method method, Throwable e) { |
| // Ignore |
| } |
| |
| @Override |
| public void onMissingAttribute(int lineNumber, String elemName, |
| String argName) { |
| // Ignore |
| } |
| |
| @Override |
| public void onUnexpectedAttribute(int lineNumber, String elemName, |
| String attrName, String attrValue) { |
| // Ignore |
| } |
| |
| @Override |
| public void onUnexpectedChild(int lineNumber, String elemName) { |
| // Ignore |
| } |
| |
| @Override |
| public void onUnexpectedElement(int lineNumber, String elemName) { |
| // Ignore |
| } |
| }; |
| |
| private static final int TYPE_NONE = 0; |
| private static final int TYPE_BEGIN = 1; |
| private static final int TYPE_END = 2; |
| private static final int TYPE_TEXT = 3; |
| |
| static { |
| ReflectiveParser.registerSchemaLevel(sArbitraryChildHandler.getClass()); |
| } |
| |
| /** |
| * Attempts to create a handler method from any method. You can pass in any |
| * method at all, but an exception will be thrown if the method is clearly a |
| * handler but the containing class does not have the proper parameter |
| * metafields. |
| */ |
| @SuppressWarnings("unchecked") |
| public static HandlerMethod tryCreate(Method method) { |
| String methodName = method.getName(); |
| String normalizedTagName = null; |
| try { |
| int type = TYPE_NONE; |
| |
| if (methodName.startsWith("__")) { |
| if (methodName.endsWith("_begin")) { |
| type = TYPE_BEGIN; |
| normalizedTagName = methodName.substring(0, methodName.length() |
| - "_begin".length()); |
| } else if (methodName.endsWith("_end")) { |
| type = TYPE_END; |
| normalizedTagName = methodName.substring(0, methodName.length() |
| - "_end".length()); |
| } else if (methodName.equals("__text")) { |
| type = TYPE_TEXT; |
| } |
| } |
| |
| if (type == TYPE_NONE) { |
| // This was not a handler method. |
| // Exit early. |
| // |
| return null; |
| } |
| |
| assert (type == TYPE_BEGIN || type == TYPE_END || type == TYPE_TEXT); |
| |
| // Can the corresponding element have arbitrary children? |
| // |
| Class<?> returnType = method.getReturnType(); |
| boolean arbitraryChildren = false; |
| if (type == TYPE_BEGIN) { |
| if (Schema.class.isAssignableFrom(returnType)) { |
| arbitraryChildren = false; |
| |
| // Also, we need to register this schema type. |
| // |
| ReflectiveParser.registerSchemaLevel((Class<? extends Schema>) returnType); |
| |
| } else if (returnType.equals(Void.TYPE)) { |
| arbitraryChildren = true; |
| } else { |
| throw new IllegalArgumentException( |
| "The return type of begin handlers must be 'void' or assignable to 'SchemaLevel'"); |
| } |
| } else if (!Void.TYPE.equals(returnType)) { |
| throw new IllegalArgumentException( |
| "Only 'void' may be specified as a return type for 'end' and 'text' handlers"); |
| } |
| |
| // Create handler args. |
| // |
| if (type == TYPE_TEXT) { |
| Class<?>[] paramTypes = method.getParameterTypes(); |
| if (paramTypes.length != 1 || !String.class.equals(paramTypes[0])) { |
| throw new IllegalArgumentException( |
| "__text handlers must have exactly one String parameter"); |
| } |
| |
| // We pretend it doesn't have any param since they're always |
| // pre-determined. |
| // |
| return new HandlerMethod(method, type, false, EMPTY_HANDLERPARAMS); |
| } else { |
| Class<?>[] paramTypes = method.getParameterTypes(); |
| List<HandlerParam> handlerParams = new ArrayList<HandlerParam>(); |
| for (int i = 0, n = paramTypes.length; i < n; ++i) { |
| HandlerParam handlerParam = HandlerParam.create(method, |
| normalizedTagName, i); |
| if (handlerParam != null) { |
| handlerParams.add(handlerParam); |
| } else { |
| throw new IllegalArgumentException("In method '" + method.getName() |
| + "', parameter " + (i + 1) + " is an unsupported type"); |
| } |
| } |
| |
| HandlerParam[] hpa = handlerParams.toArray(EMPTY_HANDLERPARAMS); |
| return new HandlerMethod(method, type, arbitraryChildren, hpa); |
| } |
| } catch (Exception e) { |
| throw new RuntimeException("Unable to use method '" + methodName |
| + "' as a handler", e); |
| } |
| } |
| |
| private final boolean arbitraryChildren; |
| |
| private final HandlerParam[] handlerParams; |
| |
| private final Method method; |
| |
| private final int methodType; |
| |
| private HandlerMethod(Method method, int type, boolean arbitraryChildren, |
| HandlerParam[] hpa) { |
| this.method = method; |
| this.methodType = type; |
| this.arbitraryChildren = arbitraryChildren; |
| this.handlerParams = hpa.clone(); |
| |
| this.method.setAccessible(true); |
| } |
| |
| public HandlerArgs createArgs(Schema schema, int lineNumber, String elemName) { |
| return new HandlerArgs(schema, lineNumber, elemName, handlerParams); |
| } |
| |
| public String getNormalizedName() { |
| String name = method.getName(); |
| if (isStartMethod()) { |
| return name.substring(2, name.length() - "_begin".length()); |
| } else if (isEndMethod()) { |
| return name.substring(2, name.length() - "_end".length()); |
| } else { |
| throw new IllegalStateException("Unexpected method name"); |
| } |
| } |
| |
| public HandlerParam getParam(int i) { |
| return handlerParams[i]; |
| } |
| |
| public int getParamCount() { |
| return handlerParams.length; |
| } |
| |
| public Schema invokeBegin(int lineNumber, String elemLocalName, |
| Schema target, HandlerArgs args, Object[] outInvokeArgs) |
| throws UnableToCompleteException { |
| assert (outInvokeArgs.length == args.getArgCount()); |
| |
| for (int i = 0, n = args.getArgCount(); i < n; ++i) { |
| Object invokeArg = args.convertToArg(i); |
| outInvokeArgs[i] = invokeArg; |
| } |
| |
| Schema nextSchemaLevel = null; |
| |
| Throwable caught = null; |
| try { |
| target.setLineNumber(lineNumber); |
| nextSchemaLevel = (Schema) method.invoke(target, outInvokeArgs); |
| } catch (IllegalArgumentException e) { |
| caught = e; |
| } catch (IllegalAccessException e) { |
| caught = e; |
| } catch (InvocationTargetException e) { |
| caught = e.getTargetException(); |
| } |
| |
| if (caught != null) { |
| target.onHandlerException(lineNumber, elemLocalName, method, caught); |
| } |
| |
| // Prepare a resulting schema level that allows the reflective parser |
| // to simply perform its normal logic, even while there are some |
| // special cases. |
| // |
| // Four cases: |
| // (1) childSchemaLevel is non-null, in which case it becomes the new |
| // schema used for child elements |
| // (2) the handler method has return type "SchemaLevel" but the result |
| // was null, meaning that it cannot have child elements; |
| // we return null to indicate this |
| // (3) the handler method has return type "void", meaning that child |
| // elements are simply ignored; we push null to detect this |
| // (4) the method failed or could not be called, which is treated the same |
| // as case (3) |
| // |
| if (nextSchemaLevel != null) { |
| return nextSchemaLevel; |
| } else if (arbitraryChildren) { |
| return sArbitraryChildHandler; |
| } else { |
| return null; |
| } |
| } |
| |
| public void invokeEnd(int lineNumber, String elem, Schema target, |
| Object[] args) throws UnableToCompleteException { |
| Throwable caught = null; |
| try { |
| target.setLineNumber(lineNumber); |
| method.invoke(target, args); |
| return; |
| } catch (IllegalArgumentException e) { |
| caught = e; |
| } catch (IllegalAccessException e) { |
| caught = e; |
| } catch (InvocationTargetException e) { |
| caught = e.getTargetException(); |
| } |
| target.onHandlerException(lineNumber, elem, method, caught); |
| } |
| |
| public void invokeText(int lineNumber, String text, Schema target) |
| throws UnableToCompleteException { |
| Throwable caught = null; |
| try { |
| target.setLineNumber(lineNumber); |
| method.invoke(target, new Object[] {text}); |
| return; |
| } catch (IllegalArgumentException e) { |
| caught = e; |
| } catch (IllegalAccessException e) { |
| caught = e; |
| } catch (InvocationTargetException e) { |
| caught = e.getTargetException(); |
| } |
| target.onHandlerException(lineNumber, "#text", method, caught); |
| } |
| |
| public boolean isEndMethod() { |
| return methodType == TYPE_END; |
| } |
| |
| public boolean isStartMethod() { |
| return methodType == TYPE_BEGIN; |
| } |
| } |