001: /*
002: * Copyright 2006 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.dev.util.xml;
017:
018: import com.google.gwt.core.ext.UnableToCompleteException;
019:
020: import java.lang.reflect.InvocationTargetException;
021: import java.lang.reflect.Method;
022: import java.util.ArrayList;
023: import java.util.List;
024:
025: /**
026: * Represents metadata about a handler method in a class derived from {@link Schema}.
027: */
028: public final class HandlerMethod {
029:
030: private static final HandlerParam[] EMPTY_HANDLERPARAMS = new HandlerParam[0];
031:
032: // A schema level that ignores everything.
033: private static final Schema sArbitraryChildHandler = new Schema() {
034:
035: public void onBadAttributeValue(int lineNumber,
036: String elemName, String attrName, String attrValue,
037: Class paramType) {
038: // Ignore
039: }
040:
041: public void onHandlerException(int lineNumber,
042: String elemLocalName, Method method, Throwable e) {
043: // Ignore
044: }
045:
046: public void onMissingAttribute(int lineNumber, String elemName,
047: String argName) {
048: // Ignore
049: }
050:
051: public void onUnexpectedAttribute(int lineNumber,
052: String elemName, String attrName, String attrValue) {
053: // Ignore
054: }
055:
056: public void onUnexpectedChild(int lineNumber, String elemName) {
057: // Ignore
058: }
059:
060: public void onUnexpectedElement(int lineNumber, String elemName) {
061: // Ignore
062: }
063: };
064:
065: private static final int TYPE_NONE = 0;
066: private static final int TYPE_BEGIN = 1;
067: private static final int TYPE_END = 2;
068: private static final int TYPE_TEXT = 3;
069:
070: static {
071: ReflectiveParser.registerSchemaLevel(sArbitraryChildHandler
072: .getClass());
073: }
074:
075: /**
076: * Attempts to create a handler method from any method. You can pass in any
077: * method at all, but an exception will be thrown if the method is clearly a
078: * handler but the containing class does not have the proper parameter
079: * metafields.
080: */
081: public static HandlerMethod tryCreate(Method method) {
082: String methodName = method.getName();
083: String normalizedTagName = null;
084: try {
085: int type = TYPE_NONE;
086:
087: if (methodName.startsWith("__")) {
088: if (methodName.endsWith("_begin")) {
089: type = TYPE_BEGIN;
090: normalizedTagName = methodName.substring(0,
091: methodName.length() - "_begin".length());
092: } else if (methodName.endsWith("_end")) {
093: type = TYPE_END;
094: normalizedTagName = methodName.substring(0,
095: methodName.length() - "_end".length());
096: } else if (methodName.equals("__text")) {
097: type = TYPE_TEXT;
098: }
099: }
100:
101: if (type == TYPE_NONE) {
102: // This was not a handler method.
103: // Exit early.
104: //
105: return null;
106: }
107:
108: assert (type == TYPE_BEGIN || type == TYPE_END || type == TYPE_TEXT);
109:
110: // Can the corresponding element have arbitrary children?
111: //
112: Class returnType = method.getReturnType();
113: boolean arbitraryChildren = false;
114: if (type == TYPE_BEGIN) {
115: if (Schema.class.isAssignableFrom(returnType)) {
116: arbitraryChildren = false;
117:
118: // Also, we need to register this schema type.
119: //
120: ReflectiveParser.registerSchemaLevel(returnType);
121:
122: } else if (returnType.equals(Void.TYPE)) {
123: arbitraryChildren = true;
124: } else {
125: throw new IllegalArgumentException(
126: "The return type of begin handlers must be 'void' or assignable to 'SchemaLevel'");
127: }
128: } else if (!Void.TYPE.equals(returnType)) {
129: throw new IllegalArgumentException(
130: "Only 'void' may be specified as a return type for 'end' and 'text' handlers");
131: }
132:
133: // Create handler args.
134: //
135: if (type == TYPE_TEXT) {
136: Class[] paramTypes = method.getParameterTypes();
137: if (paramTypes.length != 1
138: || !String.class.equals(paramTypes[0])) {
139: throw new IllegalArgumentException(
140: "__text handlers must have exactly one String parameter");
141: }
142:
143: // We pretend it doesn't have any param since they're always
144: // pre-determined.
145: //
146: return new HandlerMethod(method, type, false,
147: EMPTY_HANDLERPARAMS);
148: } else {
149: Class[] paramTypes = method.getParameterTypes();
150: List handlerParams = new ArrayList();
151: for (int i = 0, n = paramTypes.length; i < n; ++i) {
152: HandlerParam handlerParam = HandlerParam.create(
153: method, normalizedTagName, i);
154: if (handlerParam != null) {
155: handlerParams.add(handlerParam);
156: } else {
157: throw new IllegalArgumentException(
158: "In method '" + method.getName()
159: + "', parameter " + (i + 1)
160: + " is an unsupported type");
161: }
162: }
163:
164: HandlerParam[] hpa = (HandlerParam[]) handlerParams
165: .toArray(EMPTY_HANDLERPARAMS);
166: return new HandlerMethod(method, type,
167: arbitraryChildren, hpa);
168: }
169: } catch (Exception e) {
170: throw new RuntimeException("Unable to use method '"
171: + methodName + "' as a handler", e);
172: }
173: }
174:
175: private final boolean arbitraryChildren;
176:
177: private final HandlerParam[] handlerParams;
178:
179: private final Method method;
180:
181: private final int methodType;
182:
183: private HandlerMethod(Method method, int type,
184: boolean arbitraryChildren, HandlerParam[] hpa) {
185: this .method = method;
186: this .methodType = type;
187: this .arbitraryChildren = arbitraryChildren;
188: this .handlerParams = (HandlerParam[]) hpa.clone();
189:
190: this .method.setAccessible(true);
191: }
192:
193: public HandlerArgs createArgs(Schema schema, int lineNumber,
194: String elemName) {
195: return new HandlerArgs(schema, lineNumber, elemName,
196: handlerParams);
197: }
198:
199: public String getNormalizedName() {
200: String name = method.getName();
201: if (isStartMethod()) {
202: return name.substring(2, name.length() - "_begin".length());
203: } else if (isEndMethod()) {
204: return name.substring(2, name.length() - "_end".length());
205: } else {
206: throw new IllegalStateException("Unexpected method name");
207: }
208: }
209:
210: public HandlerParam getParam(int i) {
211: return handlerParams[i];
212: }
213:
214: public int getParamCount() {
215: return handlerParams.length;
216: }
217:
218: public Schema invokeBegin(int lineNumber, String elemLocalName,
219: Schema target, HandlerArgs args, Object[] outInvokeArgs)
220: throws UnableToCompleteException {
221: assert (outInvokeArgs.length == args.getArgCount());
222:
223: for (int i = 0, n = args.getArgCount(); i < n; ++i) {
224: Object invokeArg = args.convertToArg(i);
225: outInvokeArgs[i] = invokeArg;
226: }
227:
228: Schema nextSchemaLevel = null;
229:
230: Throwable caught = null;
231: try {
232: target.setLineNumber(lineNumber);
233: nextSchemaLevel = (Schema) method.invoke(target,
234: outInvokeArgs);
235: } catch (IllegalArgumentException e) {
236: caught = e;
237: } catch (IllegalAccessException e) {
238: caught = e;
239: } catch (InvocationTargetException e) {
240: caught = e.getTargetException();
241: }
242:
243: if (caught != null) {
244: target.onHandlerException(lineNumber, elemLocalName,
245: method, caught);
246: }
247:
248: // Prepare a resulting schema level that allows the reflective parser
249: // to simply perform its normal logic, even while there are some
250: // special cases.
251: //
252: // Four cases:
253: // (1) childSchemaLevel is non-null, in which case it becomes the new
254: // schema used for child elements
255: // (2) the handler method has return type "SchemaLevel" but the result
256: // was null, meaning that it cannot have child elements;
257: // we return null to indicate this
258: // (3) the handler method has return type "void", meaning that child
259: // elements are simply ignored; we push null to detect this
260: // (4) the method failed or could not be called, which is treated the same
261: // as case (3)
262: //
263: if (nextSchemaLevel != null) {
264: return nextSchemaLevel;
265: } else if (arbitraryChildren) {
266: return sArbitraryChildHandler;
267: } else {
268: return null;
269: }
270: }
271:
272: public void invokeEnd(int lineNumber, String elem, Schema target,
273: Object[] args) throws UnableToCompleteException {
274: Throwable caught = null;
275: try {
276: target.setLineNumber(lineNumber);
277: method.invoke(target, args);
278: return;
279: } catch (IllegalArgumentException e) {
280: caught = e;
281: } catch (IllegalAccessException e) {
282: caught = e;
283: } catch (InvocationTargetException e) {
284: caught = e.getTargetException();
285: }
286: target.onHandlerException(lineNumber, elem, method, caught);
287: }
288:
289: public void invokeText(int lineNumber, String text, Schema target)
290: throws UnableToCompleteException {
291: Throwable caught = null;
292: try {
293: target.setLineNumber(lineNumber);
294: method.invoke(target, new Object[] { text });
295: return;
296: } catch (IllegalArgumentException e) {
297: caught = e;
298: } catch (IllegalAccessException e) {
299: caught = e;
300: } catch (InvocationTargetException e) {
301: caught = e.getTargetException();
302: }
303: target.onHandlerException(lineNumber, "#text", method, caught);
304: }
305:
306: public boolean isEndMethod() {
307: return methodType == TYPE_END;
308: }
309:
310: public boolean isStartMethod() {
311: return methodType == TYPE_BEGIN;
312: }
313: }
|