001: package net.sf.saxon.functions;
002:
003: import net.sf.saxon.Configuration;
004: import net.sf.saxon.expr.*;
005: import net.sf.saxon.om.*;
006: import net.sf.saxon.pattern.AnyNodeTest;
007: import net.sf.saxon.trans.DynamicError;
008: import net.sf.saxon.trans.XPathException;
009: import net.sf.saxon.type.*;
010: import net.sf.saxon.type.Type;
011: import net.sf.saxon.value.*;
012:
013: import javax.xml.transform.Source;
014: import java.io.IOException;
015: import java.io.ObjectInputStream;
016: import java.io.ObjectOutputStream;
017: import java.io.Serializable;
018: import java.lang.reflect.*;
019: import java.util.List;
020: import java.math.BigDecimal;
021:
022: /**
023: * This class acts as a container for an extension function defined to call a method
024: * in a user-defined class.
025: *
026: * <p>Note that the binding of an XPath function call to a Java method is done in
027: * class {@link JavaExtensionLibrary}</p>
028: */
029:
030: public class ExtensionFunctionCall extends FunctionCall {
031:
032: private transient AccessibleObject theMethod;
033: // declared transient because AccessibleObject is not serializable
034: private MethodRepresentation persistentMethod;
035: // a serializable representation of the method, constructor, or field to be called
036: private Class theClass;
037: private Configuration config;
038:
039: /**
040: * Default constructor
041: */
042:
043: public ExtensionFunctionCall() {
044: }
045:
046: /**
047: * Initialization: creates an ExtensionFunctionCall
048: * @param nameCode the name code of the function, for display purposes
049: * @param theClass the Java class containing the method to be called
050: * @param object the method, field, or constructor of the Java class to be called
051: */
052:
053: public void init(int nameCode, Class theClass,
054: AccessibleObject object, Configuration config) {
055: setFunctionNameCode(nameCode);
056: this .theClass = theClass;
057: this .theMethod = object;
058: this .config = config;
059: }
060:
061: /**
062: * preEvaluate: this method suppresses compile-time evaluation by doing nothing
063: * (because the external function might have side-effects and might use the context)
064: */
065:
066: public Expression preEvaluate(StaticContext env) {
067: return this ;
068: }
069:
070: /**
071: * Method called by the expression parser when all arguments have been supplied
072: */
073:
074: public void checkArguments(StaticContext env) throws XPathException {
075: }
076:
077: /**
078: * Determine which aspects of the context the expression depends on. The result is
079: * a bitwise-or'ed value composed from constants such as XPathContext.VARIABLES and
080: * XPathContext.CURRENT_NODE
081: */
082:
083: public int getIntrinsicDependencies() {
084: if (theMethod instanceof Method) {
085: Class[] theParameterTypes = ((Method) theMethod)
086: .getParameterTypes();
087: if (theParameterTypes.length > 0
088: && theParameterTypes[0] == XPathContext.class) {
089: return StaticProperty.DEPENDS_ON_CONTEXT_ITEM
090: | StaticProperty.DEPENDS_ON_POSITION
091: | StaticProperty.DEPENDS_ON_LAST;
092: }
093: }
094: return 0;
095: }
096:
097: /**
098: * Evaluate the function. <br>
099: * @param context The context in which the function is to be evaluated
100: * @return a Value representing the result of the function.
101: * @throws net.sf.saxon.trans.XPathException if the function cannot be evaluated.
102: */
103:
104: public SequenceIterator iterate(XPathContext context)
105: throws XPathException {
106: ValueRepresentation[] argValues = new ValueRepresentation[argument.length];
107: for (int i = 0; i < argValues.length; i++) {
108: argValues[i] = ExpressionTool.lazyEvaluate(argument[i],
109: context, 1);
110: }
111: try {
112: return call(argValues, context);
113: } catch (XPathException err) {
114: String msg = err.getMessage();
115: msg = "Error in call to extension function {"
116: + theMethod.toString() + "}: " + msg;
117: DynamicError err2 = new DynamicError(msg, err
118: .getException());
119: err2.setXPathContext(context);
120: err2.setLocator(this );
121: err2.setErrorCode(err.getErrorCodeLocalPart());
122: throw err2;
123: }
124: }
125:
126: /**
127: * Get the class containing the method being called
128: */
129:
130: public Class getTargetClass() {
131: return theClass;
132: }
133:
134: /**
135: * Get the target method (or field, or constructor) being called
136: */
137:
138: public AccessibleObject getTargetMethod() {
139: return theMethod;
140: }
141:
142: /**
143: * Call an extension function previously identified using the bind() method. A subclass
144: * can override this method.
145: * @param argValues The values of the arguments
146: * @return The value returned by the extension function
147: */
148:
149: private SequenceIterator call(ValueRepresentation[] argValues,
150: XPathContext context) throws XPathException {
151:
152: Class[] theParameterTypes;
153:
154: if (theMethod instanceof Constructor) {
155: Constructor constructor = (Constructor) theMethod;
156: theParameterTypes = constructor.getParameterTypes();
157: Object[] params = new Object[theParameterTypes.length];
158:
159: setupParams(argValues, params, theParameterTypes, 0, 0,
160: context);
161:
162: try {
163: Object result = invokeConstructor(constructor, params);
164: return asIterator(result, context);
165: } catch (InstantiationException err0) {
166: DynamicError e = new DynamicError(
167: "Cannot instantiate class", err0);
168: throw e;
169: } catch (IllegalAccessException err1) {
170: DynamicError e = new DynamicError(
171: "Constructor access is illegal", err1);
172: throw e;
173: } catch (IllegalArgumentException err2) {
174: DynamicError e = new DynamicError(
175: "Argument is of wrong type", err2);
176: throw e;
177: } catch (NullPointerException err2) {
178: DynamicError e = new DynamicError("Object is null");
179: throw e;
180: } catch (InvocationTargetException err3) {
181: Throwable ex = err3.getTargetException();
182: if (ex instanceof XPathException) {
183: throw (XPathException) ex;
184: } else {
185: if (context.getController().isTracing()
186: || context.getConfiguration()
187: .isTraceExternalFunctions()) {
188: err3.getTargetException().printStackTrace();
189: }
190: DynamicError e = new DynamicError(
191: "Exception in extension function: "
192: + err3.getTargetException()
193: .toString(), ex);
194: throw e;
195: }
196: }
197: } else if (theMethod instanceof Method) {
198: Method method = (Method) theMethod;
199: boolean isStatic = Modifier.isStatic(method.getModifiers());
200: Object theInstance;
201: theParameterTypes = method.getParameterTypes();
202: boolean usesContext = theParameterTypes.length > 0
203: && (theParameterTypes[0] == XPathContext.class);
204: if (isStatic) {
205: theInstance = null;
206: } else {
207: if (argValues.length == 0) {
208: DynamicError e = new DynamicError(
209: "Must supply an argument for an instance-level extension function");
210: throw e;
211: }
212: Value arg0 = Value.asValue(argValues[0]);
213: theInstance = arg0.convertToJava(theClass, context);
214: // this fails if the first argument is not of a suitable class
215: }
216:
217: Object[] params = new Object[theParameterTypes.length];
218:
219: if (usesContext) {
220: params[0] = context;
221: }
222:
223: setupParams(argValues, params, theParameterTypes,
224: (usesContext ? 1 : 0), (isStatic ? 0 : 1), context);
225:
226: try {
227: Object result = invokeMethod(method, theInstance,
228: params);
229: //Object result = method.invoke(theInstance, params);
230: if (method.getReturnType().toString().equals("void")) {
231: // method returns void:
232: // tried (method.getReturnType()==Void.class) unsuccessfully
233: return EmptyIterator.getInstance();
234: }
235: return asIterator(result, context);
236:
237: } catch (IllegalAccessException err1) {
238: throw new DynamicError("Method access is illegal", err1);
239: } catch (IllegalArgumentException err2) {
240: throw new DynamicError("Argument is of wrong type",
241: err2);
242: } catch (NullPointerException err2) {
243: throw new DynamicError("Object is null", err2);
244: } catch (InvocationTargetException err3) {
245: Throwable ex = err3.getTargetException();
246: if (ex instanceof XPathException) {
247: throw (XPathException) ex;
248: } else {
249: if (context.getController().isTracing()
250: || context.getConfiguration()
251: .isTraceExternalFunctions()) {
252: err3.getTargetException().printStackTrace();
253: }
254: throw new DynamicError(
255: "Exception in extension function "
256: + err3.getTargetException()
257: .toString(), ex);
258: }
259: }
260: } else if (theMethod instanceof Field) {
261:
262: // Start of code added by GS
263:
264: Field field = (Field) theMethod;
265: boolean isStatic = Modifier.isStatic(field.getModifiers());
266: Object theInstance;
267: if (isStatic) {
268: theInstance = null;
269: } else {
270: if (argValues.length == 0) {
271: DynamicError e = new DynamicError(
272: "Must supply an argument for an instance-level extension function");
273: throw e;
274: }
275: Value arg0 = Value.asValue(argValues[0]);
276: theInstance = arg0.convertToJava(theClass, context);
277: // this fails if the first argument is not of a suitable class
278: }
279:
280: try {
281: Object result = getField(field, theInstance);
282: return asIterator(result, context);
283:
284: } catch (IllegalAccessException err1) {
285: DynamicError e = new DynamicError(
286: "Field access is illegal", err1);
287: throw e;
288: } catch (IllegalArgumentException err2) {
289: DynamicError e = new DynamicError(
290: "Argument is of wrong type", err2);
291: throw e;
292: }
293: } else {
294: throw new AssertionError("property " + theMethod
295: + " is neither constructor, method, nor field");
296: }
297:
298: }
299:
300: /**
301: * Convert the extension function result to an XPath value (a sequence) and return a
302: * SequenceIterator over that sequence
303: * @param result the result returned by the Java extension function
304: * @param context the dynamic context
305: * @return an iterator over the items in the result
306: * @throws net.sf.saxon.trans.XPathException
307: */
308:
309: private SequenceIterator asIterator(Object result,
310: XPathContext context) throws XPathException {
311: if (result == null) {
312: return EmptyIterator.getInstance();
313: }
314: if (result instanceof SequenceIterator) {
315: return (SequenceIterator) result;
316: }
317: if (result instanceof Value) {
318: return ((Value) result).iterate(null);
319: }
320: if (result instanceof NodeInfo) {
321: return SingletonIterator.makeIterator(((NodeInfo) result));
322: }
323: Value actual = Value.convertJavaObjectToXPath(result,
324: SequenceType.ANY_SEQUENCE, context.getConfiguration());
325: return actual.iterate(context);
326: }
327:
328: /**
329: * Set up parameters for the Java method call
330: * @param argValues the supplied XPath argument values
331: * @param params the result of converting the XPath argument values to Java objects
332: * @param paramTypes the Java classes defining the types of the arguments in the method signature
333: * @param firstParam normally 0, but 1 if the first parameter to the Java method is an XPathContext object
334: * @param firstArg normally 0, but 1 if the first argument in the XPath call is the instance object whose method
335: * is to be called
336: * @param context The dynamic context, giving access to a NamePool and to schema information
337: * @throws net.sf.saxon.trans.XPathException
338: */
339:
340: private void setupParams(ValueRepresentation[] argValues,
341: Object[] params, Class[] paramTypes, int firstParam,
342: int firstArg, XPathContext context) throws XPathException {
343: int j = firstParam;
344: for (int i = firstArg; i < argValues.length; i++) {
345: argValues[i] = Value.asValue(argValues[i]);
346: params[j] = ((Value) argValues[i]).convertToJava(
347: paramTypes[j], context);
348: j++;
349: }
350: }
351:
352: /**
353: * Determine the data type of the expression, if possible. All expressions return
354: * sequences, in general; this method determines the type of the items within the
355: * sequence, assuming that (a) this is known in advance, and (b) it is the same for
356: * all items in the sequence.
357: *
358: * <p>This method will always return a result, though it may be the best approximation
359: * that is available at the time.</p>
360: *
361: * @return the item type
362: * @param th
363: */
364:
365: public ItemType getItemType(TypeHierarchy th) {
366: return convertClassToType(getReturnClass());
367: }
368:
369: private ItemType convertClassToType(Class resultClass) {
370: if (resultClass == null || resultClass == Value.class) {
371: return AnyItemType.getInstance();
372: } else if (resultClass.toString().equals("void")) {
373: return AnyItemType.getInstance();
374: } else if (resultClass == String.class
375: || resultClass == StringValue.class) {
376: return Type.STRING_TYPE;
377: } else if (resultClass == Boolean.class
378: || resultClass == boolean.class
379: || resultClass == BooleanValue.class) {
380: return Type.BOOLEAN_TYPE;
381: } else if (resultClass == Double.class
382: || resultClass == double.class
383: || resultClass == DoubleValue.class) {
384: return Type.DOUBLE_TYPE;
385: } else if (resultClass == Float.class
386: || resultClass == float.class
387: || resultClass == FloatValue.class) {
388: return Type.FLOAT_TYPE;
389: } else if (resultClass == Long.class
390: || resultClass == long.class
391: || resultClass == IntegerValue.class
392: || resultClass == BigIntegerValue.class
393: || resultClass == Integer.class
394: || resultClass == int.class
395: || resultClass == Short.class
396: || resultClass == short.class
397: || resultClass == Byte.class
398: || resultClass == byte.class) {
399: return Type.INTEGER_TYPE;
400: } else if (resultClass == BigDecimal.class) {
401: return Type.DECIMAL_TYPE;
402: } else if (Value.class.isAssignableFrom(resultClass)
403: || SequenceIterator.class.isAssignableFrom(resultClass)) {
404: return AnyItemType.getInstance();
405:
406: } else {
407: // Offer the object to all the registered external object models
408: List externalObjectModels = config
409: .getExternalObjectModels();
410: for (int m = 0; m < externalObjectModels.size(); m++) {
411: ExternalObjectModel model = (ExternalObjectModel) externalObjectModels
412: .get(m);
413: if (model.isRecognizedNodeClass(resultClass)) {
414: return AnyNodeTest.getInstance();
415: }
416: }
417: }
418:
419: if (NodeInfo.class.isAssignableFrom(resultClass)
420: || Source.class.isAssignableFrom(resultClass)) {
421: return AnyNodeTest.getInstance();
422: // we could be more specific regarding the kind of node
423: } else if (List.class.isAssignableFrom(resultClass)) {
424: return AnyItemType.getInstance();
425: } else if (resultClass.isArray()) {
426: Class component = resultClass.getComponentType();
427: return convertClassToType(component);
428: } else {
429: return new ExternalObjectType(resultClass);
430: }
431: }
432:
433: public int computeCardinality() {
434: Class resultClass = getReturnClass();
435: if (resultClass == null) {
436: // we don't know yet
437: return StaticProperty.ALLOWS_ZERO_OR_MORE;
438: }
439: if (Value.class.isAssignableFrom(resultClass)
440: || SequenceIterator.class.isAssignableFrom(resultClass)
441: || List.class.isAssignableFrom(resultClass)
442: || Closure.class.isAssignableFrom(resultClass)
443: || Source.class.isAssignableFrom(resultClass)
444: || resultClass.isArray()) {
445: return StaticProperty.ALLOWS_ZERO_OR_MORE;
446: }
447: List models = config.getExternalObjectModels();
448: for (int m = 0; m < models.size(); m++) {
449: ExternalObjectModel model = (ExternalObjectModel) models
450: .get(m);
451: if (model.isRecognizedNodeClass(resultClass)) {
452: return StaticProperty.ALLOWS_ZERO_OR_ONE;
453: } else if (model.isRecognizedNodeListClass(resultClass)) {
454: return StaticProperty.ALLOWS_ZERO_OR_MORE;
455: }
456: }
457: if (resultClass.isPrimitive()) {
458: if (resultClass.equals(Void.TYPE)) {
459: // this always returns an empty sequence, but we'll model it as
460: // zero or one
461: return StaticProperty.ALLOWS_ZERO_OR_ONE;
462: } else {
463: // return type = int, boolean, char etc
464: return StaticProperty.EXACTLY_ONE;
465: }
466: } else {
467: return StaticProperty.ALLOWS_ZERO_OR_ONE;
468: }
469: }
470:
471: /**
472: * Get the Java class of the value returned by the method
473: * @return the Java class of the value returned by the method
474: */
475:
476: private Class getReturnClass() {
477: if (theMethod instanceof Method) {
478: return ((Method) theMethod).getReturnType();
479: } else if (theMethod instanceof Field) {
480: return ((Field) theMethod).getType();
481: } else if (theMethod instanceof Constructor) {
482: return theClass;
483: } else {
484: // cannot happen
485: return null;
486: }
487: }
488:
489: /**
490: * Determine whether this method uses the focus. True if the first argument is of type XPathContext.
491: */
492:
493: public boolean usesFocus() { // NOT CURRENTLY USED
494: if (theMethod instanceof Method) {
495: Class[] theParameterTypes = ((Method) theMethod)
496: .getParameterTypes();
497: return theParameterTypes.length > 0
498: && (theParameterTypes[0] == XPathContext.class);
499: } else {
500: return false;
501: }
502: }
503:
504: /**
505: * Invoke a constructor. This method is provided separately so that it can be refined in a subclass.
506: * For example, a subclass might perform tracing of calls, or might trap exceptions.
507: * @param constructor The constructor to be invoked
508: * @param params The parameters to be passed to the constructor
509: * @return The object returned by the constructor
510: * @throws InstantiationException if the invocation throws an InstantiationException
511: * @throws IllegalAccessException if the invocation throws an IllegalAccessException
512: * @throws InvocationTargetException if the invocation throws an InvocationTargetException (which happens
513: * when the constructor itself throws an exception)
514: */
515:
516: protected Object invokeConstructor(Constructor constructor,
517: Object[] params) throws java.lang.InstantiationException,
518: java.lang.IllegalAccessException,
519: java.lang.reflect.InvocationTargetException {
520: return constructor.newInstance(params);
521: }
522:
523: /**
524: * Invoke a method. This method is provided separately so that it can be refined in a subclass.
525: * For example, a subclass might perform tracing of calls, or might trap exceptions.
526: * @param method The method to be invoked
527: * @param instance The object on which the method is to be invoked. This is set to null if the
528: * method is static.
529: * @param params The parameters to be passed to the method
530: * @return The object returned by the method
531: * @throws IllegalAccessException if the invocation throws an IllegalAccessException
532: * @throws InvocationTargetException if the invocation throws an InvocationTargetException (which happens
533: * when the method itself throws an exception)
534: */
535:
536: protected Object invokeMethod(Method method, Object instance,
537: Object[] params) throws java.lang.IllegalAccessException,
538: java.lang.reflect.InvocationTargetException {
539: return method.invoke(instance, params);
540: }
541:
542: /**
543: * Access a field. This method is provided separately so that it can be refined in a subclass.
544: * For example, a subclass might perform tracing of calls, or might trap exceptions.
545: * @param field The field to be retrieved
546: * @param instance The object whose field is to be retrieved. This is set to null if the
547: * field is static.
548: * @return The value of the field
549: * @throws IllegalAccessException if the invocation throws an IllegalAccessException
550: */
551:
552: protected Object getField(Field field, Object instance)
553: throws java.lang.IllegalAccessException {
554: return field.get(instance);
555: }
556:
557: /**
558: * Code to handle serialization, used when compiling a stylesheet containing calls to extension functions
559: */
560:
561: private void writeObject(ObjectOutputStream s) throws IOException {
562: persistentMethod = new MethodRepresentation(theClass, theMethod);
563: s.defaultWriteObject();
564: }
565:
566: /**
567: * Code to handle deserialization, used when reading in a compiled stylesheet
568: */
569:
570: private void readObject(ObjectInputStream s) throws IOException {
571: try {
572: s.defaultReadObject();
573: theMethod = persistentMethod.recoverAccessibleObject();
574: } catch (Exception e) {
575: throw new IOException(
576: "Failed to read compiled representation of extension function call to "
577: + theClass.getClass());
578: }
579: }
580:
581: /**
582: * A Java AccessibleObject is not serializable. When compiling a stylesheet that contains extension
583: * functions, we therefore need to create a serializable representation of the method (or constructor
584: * or field) to be called. This is provided by the class MethodRepresentation.
585: */
586:
587: private static class MethodRepresentation implements Serializable {
588: private Class theClass;
589: private byte category; // one of Method, Constructor, Field
590: private String name; // the name of the method or field
591: private Class[] params; // the types of the parameters to a method or constructor
592:
593: public MethodRepresentation(Class theClass, AccessibleObject obj) {
594: this .theClass = theClass;
595: if (obj instanceof Method) {
596: category = 0;
597: name = ((Method) obj).getName();
598: params = ((Method) obj).getParameterTypes();
599: } else if (obj instanceof Constructor) {
600: category = 1;
601: params = ((Constructor) obj).getParameterTypes();
602: } else {
603: category = 2;
604: name = ((Field) obj).getName();
605: }
606: }
607:
608: public AccessibleObject recoverAccessibleObject()
609: throws NoSuchMethodException, NoSuchFieldException {
610: switch (category) {
611: case 0:
612: return theClass.getMethod(name, params);
613: case 1:
614: return theClass.getConstructor(params);
615: case 2:
616: return theClass.getField(name);
617: default:
618: return null;
619: }
620: }
621: }
622:
623: }
624:
625: //
626: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
627: // you may not use this file except in compliance with the License. You may obtain a copy of the
628: // License at http://www.mozilla.org/MPL/
629: //
630: // Software distributed under the License is distributed on an "AS IS" basis,
631: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
632: // See the License for the specific language governing rights and limitations under the License.
633: //
634: // The Original Code is: all this file.
635: //
636: // The Initial Developer of the Original Code is Michael H. Kay.
637: //
638: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
639: //
640: // Contributor(s): Gunther Schadow (changes to allow access to public fields; also wrapping
641: // of extensions and mapping of null to empty sequence).
642: //
|