KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > net > sf > saxon > functions > ExtensionFunctionCall


1 package net.sf.saxon.functions;
2 import net.sf.saxon.Configuration;
3 import net.sf.saxon.expr.*;
4 import net.sf.saxon.om.*;
5 import net.sf.saxon.pattern.AnyNodeTest;
6 import net.sf.saxon.trans.DynamicError;
7 import net.sf.saxon.trans.XPathException;
8 import net.sf.saxon.type.AnyItemType;
9 import net.sf.saxon.type.ExternalObjectType;
10 import net.sf.saxon.type.ItemType;
11 import net.sf.saxon.type.Type;
12 import net.sf.saxon.value.*;
13
14 import javax.xml.transform.Source JavaDoc;
15 import java.io.IOException JavaDoc;
16 import java.io.ObjectInputStream JavaDoc;
17 import java.io.ObjectOutputStream JavaDoc;
18 import java.io.Serializable JavaDoc;
19 import java.lang.reflect.*;
20 import java.util.List JavaDoc;
21 import java.math.BigDecimal JavaDoc;
22
23
24
25 /**
26 * This class acts as a container for an extension function defined to call a method
27 * in a user-defined class.
28  *
29  * <p>Note that the binding of an XPath function call to a Java method is done in
30  * class {@link JavaExtensionLibrary}</p>
31 */

32
33 public class ExtensionFunctionCall extends FunctionCall {
34
35     private transient AccessibleObject theMethod;
36              // declared transient because AccessibleObject is not serializable
37
private MethodRepresentation persistentMethod;
38              // a serializable representation of the method, constructor, or field to be called
39
private Class JavaDoc theClass;
40     private Configuration config;
41
42     /**
43      * Default constructor
44      */

45
46     public ExtensionFunctionCall() {}
47
48     /**
49      * Initialization: creates an ExtensionFunctionCall
50      * @param nameCode the name code of the function, for display purposes
51      * @param theClass the Java class containing the method to be called
52      * @param object the method, field, or constructor of the Java class to be called
53     */

54
55     public void init(int nameCode, Class JavaDoc theClass, AccessibleObject object, Configuration config) {
56         setFunctionNameCode(nameCode);
57         this.theClass = theClass;
58         this.theMethod = object;
59         this.config = config;
60     }
61
62     /**
63     * preEvaluate: this method suppresses compile-time evaluation by doing nothing
64     * (because the external function might have side-effects and might use the context)
65     */

66
67     public Expression preEvaluate(StaticContext env) {
68         return this;
69     }
70
71
72     /**
73     * Method called by the expression parser when all arguments have been supplied
74     */

75
76     public void checkArguments(StaticContext env) throws XPathException {
77     }
78
79
80     /**
81     * Determine which aspects of the context the expression depends on. The result is
82     * a bitwise-or'ed value composed from constants such as XPathContext.VARIABLES and
83     * XPathContext.CURRENT_NODE
84     */

85
86     public int getIntrinsicDependencies() {
87         if (theMethod instanceof Method) {
88             Class JavaDoc[] theParameterTypes = ((Method)theMethod).getParameterTypes();
89             if (theParameterTypes.length > 0 && theParameterTypes[0] == XPathContext.class) {
90                 return StaticProperty.DEPENDS_ON_CONTEXT_ITEM |
91                         StaticProperty.DEPENDS_ON_POSITION |
92                         StaticProperty.DEPENDS_ON_LAST;
93             }
94         }
95         return 0;
96     }
97
98
99     /**
100     * Evaluate the function. <br>
101     * @param context The context in which the function is to be evaluated
102     * @return a Value representing the result of the function.
103     * @throws net.sf.saxon.trans.XPathException if the function cannot be evaluated.
104     */

105
106     public SequenceIterator iterate(XPathContext context) throws XPathException {
107         ValueRepresentation[] argValues = new ValueRepresentation[argument.length];
108         for (int i=0; i<argValues.length; i++) {
109             argValues[i] = ExpressionTool.lazyEvaluate(argument[i], context, 1);
110         }
111         try {
112             return call(argValues, context);
113         } catch (XPathException err) {
114             String JavaDoc msg = err.getMessage();
115             msg = "Error in call to extension function {" + theMethod.toString() + "}: " + msg;
116             DynamicError err2 = new DynamicError(msg, err.getException());
117             err2.setXPathContext(context);
118             err2.setLocator(this);
119             err2.setErrorCode(err.getErrorCodeLocalPart());
120             throw err2;
121         }
122     }
123
124     /**
125      * Get the class containing the method being called
126      */

127
128     public Class JavaDoc getTargetClass() {
129         return theClass;
130     }
131
132     /**
133      * Get the target method (or field, or constructor) being called
134      */

135
136     public AccessibleObject getTargetMethod() {
137         return theMethod;
138     }
139
140
141     /**
142      * Call an extension function previously identified using the bind() method. A subclass
143      * can override this method.
144      * @param argValues The values of the arguments
145      * @return The value returned by the extension function
146      */

147
148     private SequenceIterator call(ValueRepresentation[] argValues, XPathContext context) throws XPathException {
149
150         Class JavaDoc[] theParameterTypes;
151
152         if (theMethod instanceof Constructor) {
153             Constructor constructor = (Constructor) theMethod;
154             theParameterTypes = constructor.getParameterTypes();
155             Object JavaDoc[] params = new Object JavaDoc[theParameterTypes.length];
156
157             setupParams(argValues, params, theParameterTypes, 0, 0, context);
158
159             try {
160                 Object JavaDoc result = invokeConstructor(constructor, params);
161                 return asIterator(result, context);
162             } catch (InstantiationException JavaDoc err0) {
163                 DynamicError e = new DynamicError("Cannot instantiate class", err0);
164                 throw e;
165             } catch (IllegalAccessException JavaDoc err1) {
166                 DynamicError e = new DynamicError("Constructor access is illegal", err1);
167                 throw e;
168             } catch (IllegalArgumentException JavaDoc err2) {
169                 DynamicError e = new DynamicError("Argument is of wrong type", err2);
170                 throw e;
171             } catch (NullPointerException JavaDoc err2) {
172                 DynamicError e = new DynamicError("Object is null");
173                 throw e;
174             } catch (InvocationTargetException err3) {
175                 Throwable JavaDoc ex = err3.getTargetException();
176                 if (ex instanceof XPathException) {
177                     throw (XPathException) ex;
178                 } else {
179                     if (context.getController().isTracing() ||
180                             context.getController().getConfiguration().isTraceExternalFunctions()) {
181                         err3.getTargetException().printStackTrace();
182                     }
183                     DynamicError e = new DynamicError("Exception in extension function: " +
184                             err3.getTargetException().toString(), ex);
185                     throw e;
186                 }
187             }
188         } else if (theMethod instanceof Method) {
189             Method method = (Method) theMethod;
190             boolean isStatic = Modifier.isStatic(method.getModifiers());
191             Object JavaDoc theInstance;
192             theParameterTypes = method.getParameterTypes();
193             boolean usesContext = theParameterTypes.length > 0 &&
194                     (theParameterTypes[0] == XPathContext.class);
195             if (isStatic) {
196                 theInstance = null;
197             } else {
198                 if (argValues.length == 0) {
199                     DynamicError e = new DynamicError("Must supply an argument for an instance-level extension function");
200                     throw e;
201                 }
202                 Value arg0 = Value.asValue(argValues[0]);
203                 theInstance = arg0.convertToJava(theClass, context);
204                 // this fails if the first argument is not of a suitable class
205
}
206
207             Object JavaDoc[] params = new Object JavaDoc[theParameterTypes.length];
208
209             if (usesContext) {
210                 params[0] = context;
211             }
212
213             setupParams(argValues, params, theParameterTypes,
214                     (usesContext ? 1 : 0),
215                     (isStatic ? 0 : 1),
216                     context
217             );
218
219             try {
220                 Object JavaDoc result = invokeMethod(method, theInstance, params);
221                 //Object result = method.invoke(theInstance, params);
222
if (method.getReturnType().toString().equals("void")) {
223                     // method returns void:
224
// tried (method.getReturnType()==Void.class) unsuccessfully
225
return EmptyIterator.getInstance();
226                 }
227                 return asIterator(result, context);
228
229             } catch (IllegalAccessException JavaDoc err1) {
230                 throw new DynamicError("Method access is illegal", err1);
231             } catch (IllegalArgumentException JavaDoc err2) {
232                 throw new DynamicError("Argument is of wrong type", err2);
233             } catch (NullPointerException JavaDoc err2) {
234                 throw new DynamicError("Object is null", err2);
235             } catch (InvocationTargetException err3) {
236                 Throwable JavaDoc ex = err3.getTargetException();
237                 if (ex instanceof XPathException) {
238                     throw (XPathException) ex;
239                 } else {
240                     if (context.getController().isTracing() ||
241                             context.getController().getConfiguration().isTraceExternalFunctions()) {
242                         err3.getTargetException().printStackTrace();
243                     }
244                     throw new DynamicError("Exception in extension function " +
245                             err3.getTargetException().toString(), ex);
246                 }
247             }
248         } else if (theMethod instanceof Field) {
249
250             // Start of code added by GS
251

252             Field field = (Field) theMethod;
253             boolean isStatic = Modifier.isStatic(field.getModifiers());
254             Object JavaDoc theInstance;
255             if (isStatic) {
256                 theInstance = null;
257             } else {
258                 if (argValues.length == 0) {
259                     DynamicError e = new DynamicError("Must supply an argument for an instance-level extension function");
260                     throw e;
261                 }
262                 Value arg0 = Value.asValue(argValues[0]);
263                 theInstance = arg0.convertToJava(theClass, context);
264                 // this fails if the first argument is not of a suitable class
265
}
266
267             try {
268                 Object JavaDoc result = getField(field, theInstance);
269                 return asIterator(result, context);
270
271             } catch (IllegalAccessException JavaDoc err1) {
272                 DynamicError e = new DynamicError("Field access is illegal", err1);
273                 throw e;
274             } catch (IllegalArgumentException JavaDoc err2) {
275                 DynamicError e = new DynamicError("Argument is of wrong type", err2);
276                 throw e;
277             }
278         } else {
279             throw new AssertionError JavaDoc("property " + theMethod + " is neither constructor, method, nor field");
280         }
281
282     }
283
284     /**
285      * Convert the extension function result to an XPath value (a sequence) and return a
286      * SequenceIterator over that sequence
287      * @param result the result returned by the Java extension function
288      * @param context the dynamic context
289      * @return an iterator over the items in the result
290      * @throws net.sf.saxon.trans.XPathException
291      */

292
293     private SequenceIterator asIterator(Object JavaDoc result, XPathContext context) throws XPathException {
294         if (result == null) {
295             return EmptyIterator.getInstance();
296         }
297         if (result instanceof SequenceIterator) {
298             return (SequenceIterator) result;
299         }
300         if (result instanceof Value) {
301             return ((Value) result).iterate(null);
302         }
303         if (result instanceof NodeInfo) {
304             return SingletonIterator.makeIterator(((NodeInfo) result));
305         }
306         Value actual = Value.convertJavaObjectToXPath(
307                 result, SequenceType.ANY_SEQUENCE, context.getController().getConfiguration());
308         return actual.iterate(context);
309     }
310
311     /**
312      * Set up parameters for the Java method call
313      * @param argValues the supplied XPath argument values
314      * @param params the result of converting the XPath argument values to Java objects
315      * @param paramTypes the Java classes defining the types of the arguments in the method signature
316      * @param firstParam normally 0, but 1 if the first parameter to the Java method is an XPathContext object
317      * @param firstArg normally 0, but 1 if the first argument in the XPath call is the instance object whose method
318      * is to be called
319      * @param context The dynamic context, giving access to a NamePool and to schema information
320      * @throws net.sf.saxon.trans.XPathException
321      */

322
323     private void setupParams(ValueRepresentation[] argValues,
324                              Object JavaDoc[] params,
325                              Class JavaDoc[] paramTypes,
326                              int firstParam,
327                              int firstArg,
328                              XPathContext context) throws XPathException {
329         int j = firstParam;
330         for (int i = firstArg; i < argValues.length; i++) {
331             argValues[i] = Value.asValue(argValues[i]);
332             params[j] = ((Value)argValues[i]).convertToJava(paramTypes[j], context);
333             j++;
334         }
335     }
336
337     /**
338      * Determine the data type of the expression, if possible. All expressions return
339      * sequences, in general; this method determines the type of the items within the
340      * sequence, assuming that (a) this is known in advance, and (b) it is the same for
341      * all items in the sequence.
342      *
343      * <p>This method will always return a result, though it may be the best approximation
344      * that is available at the time.</p>
345      *
346      * @return the item type
347      */

348
349     public ItemType getItemType() {
350         return convertClassToType(getReturnClass());
351     }
352
353     private ItemType convertClassToType(Class JavaDoc resultClass) {
354         if (resultClass==null || resultClass==Value.class) {
355             return AnyItemType.getInstance();
356         } else if (resultClass.toString().equals("void")) {
357             return AnyItemType.getInstance();
358         } else if (resultClass==String JavaDoc.class || resultClass==StringValue.class) {
359             return Type.STRING_TYPE;
360         } else if (resultClass==Boolean JavaDoc.class || resultClass==boolean.class || resultClass == BooleanValue.class) {
361             return Type.BOOLEAN_TYPE;
362         } else if (resultClass==Double JavaDoc.class || resultClass==double.class || resultClass==DoubleValue.class) {
363             return Type.DOUBLE_TYPE;
364         } else if (resultClass==Float JavaDoc.class || resultClass==float.class || resultClass==FloatValue.class) {
365             return Type.FLOAT_TYPE;
366         } else if (resultClass==Long JavaDoc.class || resultClass==long.class ||
367                     resultClass==IntegerValue.class || resultClass==BigIntegerValue.class ||
368                     resultClass==Integer JavaDoc.class || resultClass==int.class ||
369                     resultClass==Short JavaDoc.class || resultClass==short.class ||
370                     resultClass==Byte JavaDoc.class || resultClass==byte.class ) {
371             return Type.INTEGER_TYPE;
372         } else if (resultClass == BigDecimal JavaDoc.class) {
373             return Type.DECIMAL_TYPE;
374         } else if (Value.class.isAssignableFrom(resultClass) ||
375                     SequenceIterator.class.isAssignableFrom(resultClass)) {
376             return AnyItemType.getInstance();
377
378         } else {
379             // Offer the object to all the registered external object models
380
List JavaDoc externalObjectModels = config.getExternalObjectModels();
381             for (int m=0; m<externalObjectModels.size(); m++) {
382                 ExternalObjectModel model = (ExternalObjectModel)externalObjectModels.get(m);
383                 if (model.isRecognizedNodeClass(resultClass)) {
384                     return AnyNodeTest.getInstance();
385                 }
386             }
387         }
388
389         if ( NodeInfo.class.isAssignableFrom(resultClass) ||
390                     Source JavaDoc.class.isAssignableFrom(resultClass)) {
391             return AnyNodeTest.getInstance();
392             // we could be more specific regarding the kind of node
393
} else if (List JavaDoc.class.isAssignableFrom(resultClass)) {
394             return AnyItemType.getInstance();
395         } else if (resultClass.isArray()) {
396             Class JavaDoc component = resultClass.getComponentType();
397             return convertClassToType(component);
398         } else {
399             return new ExternalObjectType(resultClass);
400         }
401     }
402
403     public int computeCardinality() {
404         Class JavaDoc resultClass = getReturnClass();
405         if (resultClass==null) {
406             // we don't know yet
407
return StaticProperty.ALLOWS_ZERO_OR_MORE;
408         }
409         if (Value.class.isAssignableFrom(resultClass) ||
410                     SequenceIterator.class.isAssignableFrom(resultClass) ||
411                     List JavaDoc.class.isAssignableFrom(resultClass) ||
412                     Closure.class.isAssignableFrom(resultClass)||
413                     Source JavaDoc.class.isAssignableFrom(resultClass) ||
414                     resultClass.isArray()) {
415             return StaticProperty.ALLOWS_ZERO_OR_MORE;
416         }
417         List JavaDoc models = config.getExternalObjectModels();
418         for (int m=0; m<models.size(); m++) {
419             ExternalObjectModel model = (ExternalObjectModel)models.get(m);
420             if (model.isRecognizedNodeClass(resultClass)) {
421                 return StaticProperty.ALLOWS_ZERO_OR_ONE;
422             } else if (model.isRecognizedNodeListClass(resultClass)) {
423                 return StaticProperty.ALLOWS_ZERO_OR_MORE;
424             }
425         }
426         if (resultClass.isPrimitive()) {
427             if (resultClass.equals(Void.TYPE)) {
428                 // this always returns an empty sequence, but we'll model it as
429
// zero or one
430
return StaticProperty.ALLOWS_ZERO_OR_ONE;
431             } else {
432                 // return type = int, boolean, char etc
433
return StaticProperty.EXACTLY_ONE;
434             }
435         } else {
436             return StaticProperty.ALLOWS_ZERO_OR_ONE;
437         }
438     }
439
440     /**
441      * Get the Java class of the value returned by the method
442      * @return the Java class of the value returned by the method
443      */

444
445     private Class JavaDoc getReturnClass() {
446         if (theMethod instanceof Method) {
447             return ((Method)theMethod).getReturnType();
448         } else if (theMethod instanceof Field) {
449             return ((Field)theMethod).getType();
450         } else if (theMethod instanceof Constructor) {
451             return theClass;
452         } else {
453             // cannot happen
454
return null;
455         }
456     }
457
458     /**
459      * Determine whether this method uses the focus. True if the first argument is of type XPathContext.
460      */

461
462     public boolean usesFocus() { // NOT CURRENTLY USED
463
if (theMethod instanceof Method) {
464             Class JavaDoc[] theParameterTypes = ((Method)theMethod).getParameterTypes();
465             return theParameterTypes.length > 0 && (theParameterTypes[0] == XPathContext.class);
466         } else {
467             return false;
468         }
469     }
470
471     /**
472      * Invoke a constructor. This method is provided separately so that it can be refined in a subclass.
473      * For example, a subclass might perform tracing of calls, or might trap exceptions.
474      * @param constructor The constructor to be invoked
475      * @param params The parameters to be passed to the constructor
476      * @return The object returned by the constructor
477      * @throws InstantiationException if the invocation throws an InstantiationException
478      * @throws IllegalAccessException if the invocation throws an IllegalAccessException
479      * @throws InvocationTargetException if the invocation throws an InvocationTargetException (which happens
480      * when the constructor itself throws an exception)
481      */

482
483     protected Object JavaDoc invokeConstructor(Constructor constructor, Object JavaDoc[] params)
484     throws java.lang.InstantiationException JavaDoc,
485            java.lang.IllegalAccessException JavaDoc,
486            java.lang.reflect.InvocationTargetException JavaDoc {
487         return constructor.newInstance(params);
488     }
489
490     /**
491      * Invoke a method. This method is provided separately so that it can be refined in a subclass.
492      * For example, a subclass might perform tracing of calls, or might trap exceptions.
493      * @param method The method to be invoked
494      * @param instance The object on which the method is to be invoked. This is set to null if the
495      * method is static.
496      * @param params The parameters to be passed to the method
497      * @return The object returned by the method
498      * @throws IllegalAccessException if the invocation throws an IllegalAccessException
499      * @throws InvocationTargetException if the invocation throws an InvocationTargetException (which happens
500      * when the method itself throws an exception)
501      */

502
503     protected Object JavaDoc invokeMethod(Method method, Object JavaDoc instance, Object JavaDoc[] params)
504     throws java.lang.IllegalAccessException JavaDoc,
505            java.lang.reflect.InvocationTargetException JavaDoc {
506         return method.invoke(instance, params);
507     }
508
509     /**
510      * Access a field. This method is provided separately so that it can be refined in a subclass.
511      * For example, a subclass might perform tracing of calls, or might trap exceptions.
512      * @param field The field to be retrieved
513      * @param instance The object whose field is to be retrieved. This is set to null if the
514      * field is static.
515      * @return The value of the field
516      * @throws IllegalAccessException if the invocation throws an IllegalAccessException
517      */

518
519     protected Object JavaDoc getField(Field field, Object JavaDoc instance)
520     throws java.lang.IllegalAccessException JavaDoc {
521         return field.get(instance);
522     }
523
524     /**
525      * Code to handle serialization, used when compiling a stylesheet containing calls to extension functions
526      */

527
528     private void writeObject(ObjectOutputStream JavaDoc s) throws IOException JavaDoc {
529         persistentMethod = new MethodRepresentation(theClass, theMethod);
530         s.defaultWriteObject();
531     }
532
533     /**
534      * Code to handle deserialization, used when reading in a compiled stylesheet
535      */

536
537     private void readObject(ObjectInputStream JavaDoc s) throws IOException JavaDoc {
538         try {
539             s.defaultReadObject();
540             theMethod = persistentMethod.recoverAccessibleObject();
541         } catch (Exception JavaDoc e) {
542             throw new IOException JavaDoc("Failed to read compiled representation of extension function call to " + theClass.getClass());
543         }
544     }
545
546
547     /**
548      * A Java AccessibleObject is not serializable. When compiling a stylesheet that contains extension
549      * functions, we therefore need to create a serializable representation of the method (or constructor
550      * or field) to be called. This is provided by the class MethodRepresentation.
551      */

552
553     private static class MethodRepresentation implements Serializable JavaDoc {
554         private Class JavaDoc theClass;
555         private byte category; // one of Method, Constructor, Field
556
private String JavaDoc name; // the name of the method or field
557
private Class JavaDoc[] params; // the types of the parameters to a method or constructor
558

559         public MethodRepresentation(Class JavaDoc theClass, AccessibleObject obj) {
560             this.theClass = theClass;
561             if (obj instanceof Method) {
562                 category = 0;
563                 name = ((Method)obj).getName();
564                 params = ((Method)obj).getParameterTypes();
565             } else if (obj instanceof Constructor) {
566                 category = 1;
567                 params = ((Constructor)obj).getParameterTypes();
568             } else {
569                 category = 2;
570                 name = ((Field)obj).getName();
571             }
572         }
573
574         public AccessibleObject recoverAccessibleObject() throws NoSuchMethodException JavaDoc, NoSuchFieldException JavaDoc {
575             switch (category) {
576                 case 0:
577                     return theClass.getMethod(name, params);
578                 case 1:
579                     return theClass.getConstructor(params);
580                 case 2:
581                     return theClass.getField(name);
582                 default:
583                     return null;
584             }
585         }
586     }
587
588 }
589
590 //
591
// The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
592
// you may not use this file except in compliance with the License. You may obtain a copy of the
593
// License at http://www.mozilla.org/MPL/
594
//
595
// Software distributed under the License is distributed on an "AS IS" basis,
596
// WITHOUT WARRANTY OF ANY KIND, either express or implied.
597
// See the License for the specific language governing rights and limitations under the License.
598
//
599
// The Original Code is: all this file.
600
//
601
// The Initial Developer of the Original Code is Michael H. Kay.
602
//
603
// Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
604
//
605
// Contributor(s): Gunther Schadow (changes to allow access to public fields; also wrapping
606
// of extensions and mapping of null to empty sequence).
607
//
608
Popular Tags