blob: a68e23a049ab3bd46e7c6d46fe90a57958fad189 [file] [log] [blame]
/*
* 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;
}
}