KickJava   Java API By Example, From Geeks To Geeks.

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


1 package net.sf.saxon.functions;
2
3 import net.sf.saxon.Configuration;
4 import net.sf.saxon.expr.Expression;
5 import net.sf.saxon.expr.StaticContext;
6 import net.sf.saxon.expr.XPathContext;
7 import net.sf.saxon.om.*;
8 import net.sf.saxon.trans.StaticError;
9 import net.sf.saxon.trans.XPathException;
10 import net.sf.saxon.type.ExternalObjectType;
11 import net.sf.saxon.type.ItemType;
12 import net.sf.saxon.type.Type;
13 import net.sf.saxon.value.*;
14
15 import java.io.PrintStream JavaDoc;
16 import java.lang.reflect.*;
17 import java.math.BigDecimal JavaDoc;
18 import java.math.BigInteger JavaDoc;
19 import java.net.URI JavaDoc;
20 import java.net.URL JavaDoc;
21 import java.util.ArrayList JavaDoc;
22 import java.util.Date JavaDoc;
23 import java.util.HashMap JavaDoc;
24 import java.util.List JavaDoc;
25
26 /**
27  * The JavaExtensionLibrary is a FunctionLibrary that binds XPath function calls to
28  * calls on Java methods (or constructors, or fields). It performs a mapping from
29  * the namespace URI of the function to the Java class (the mapping is partly table
30  * driven and partly algorithmic), and maps the local name of the function to the
31  * Java method, constructor, or field within the class. If the Java methods are
32  * polymorphic, then it tries to select the appropriate method based on the static types
33  * of the supplied arguments. Binding is done entirely at XPath compilation time.
34  */

35
36 public class JavaExtensionLibrary implements FunctionLibrary {
37
38     private Configuration config;
39
40     // HashMap containing URI->Class mappings. This includes conventional
41
// URIs such as the Saxon and EXSLT namespaces, and mapping defined by
42
// the user using saxon:script
43

44     private HashMap JavaDoc explicitMappings = new HashMap JavaDoc(10);
45
46     // Output destination for debug messages. At present this cannot be configured.
47

48     private transient PrintStream JavaDoc diag = System.err;
49
50     /**
51      * Construct a JavaExtensionLibrary and establish the default uri->class mappings.
52      * @param config The Saxon configuration
53      */

54
55     public JavaExtensionLibrary(Configuration config) {
56         this.config = config;
57         setDefaultURIMappings();
58     }
59
60     /**
61      * Define initial mappings of "well known" namespace URIs to Java classes (this covers
62      * the Saxon and EXSLT extensions). The method is protected so it can be overridden in
63      * a subclass.
64      */

65     protected void setDefaultURIMappings() {
66         declareJavaClass(NamespaceConstant.SAXON, net.sf.saxon.functions.Extensions.class);
67         declareJavaClass(NamespaceConstant.EXSLT_COMMON, net.sf.saxon.exslt.Common.class);
68         declareJavaClass(NamespaceConstant.EXSLT_SETS, net.sf.saxon.exslt.Sets.class);
69         declareJavaClass(NamespaceConstant.EXSLT_MATH, net.sf.saxon.exslt.Math.class);
70         declareJavaClass(NamespaceConstant.EXSLT_DATES_AND_TIMES, net.sf.saxon.exslt.Date.class);
71         declareJavaClass(NamespaceConstant.EXSLT_RANDOM, net.sf.saxon.exslt.Random.class);
72     }
73
74     /**
75      * Declare a mapping from a specific namespace URI to a Java class
76      * @param uri the namespace URI of the function name
77      * @param theClass the Java class that implements the functions in this namespace
78      */

79
80     public void declareJavaClass(String JavaDoc uri, Class JavaDoc theClass) {
81         explicitMappings.put(uri, theClass);
82     }
83
84     /**
85      * Test whether an extension function with a given name and arity is available. This supports
86      * the function-available() function in XSLT. This method may be called either at compile time
87      * or at run time.
88      * @param fingerprint The code that identifies the function name in the NamePool. This must
89      * match the supplied URI and local name.
90      * @param uri The URI of the function name
91      * @param local The local part of the function name
92      * @param arity The number of arguments. This is set to -1 in the case of the single-argument
93      * function-available() function; in this case the method should return true if there is some
94      * matching extension function, regardless of its arity.
95      */

96
97     public boolean isAvailable(int fingerprint, String JavaDoc uri, String JavaDoc local, int arity) {
98         if (!config.isAllowExternalFunctions()) {
99             return false;
100         }
101         Class JavaDoc reqClass;
102         try {
103             reqClass = getExternalJavaClass(uri);
104             if (reqClass == null) {
105                 return false;
106             }
107         } catch (Exception JavaDoc err) {
108             return false;
109         }
110         int significantArgs;
111
112         Class JavaDoc theClass = reqClass;
113
114         // if the method name is "new", look for a matching constructor
115

116         if ("new".equals(local)) {
117
118             int mod = theClass.getModifiers();
119             if (Modifier.isAbstract(mod)) {
120                 return false;
121             } else if (Modifier.isInterface(mod)) {
122                 return false;
123             } else if (Modifier.isPrivate(mod)) {
124                 return false;
125             } else if (Modifier.isProtected(mod)) {
126                 return false;
127             }
128
129             if (arity == -1) return true;
130
131             Constructor[] constructors = theClass.getConstructors();
132             for (int c = 0; c < constructors.length; c++) {
133                 Constructor theConstructor = constructors[c];
134                 if (theConstructor.getParameterTypes().length == arity) {
135                     return true;
136                 }
137             }
138             return false;
139         } else {
140
141             // convert any hyphens in the name, camelCasing the following character
142

143             String JavaDoc name = toCamelCase(local, false, diag);
144
145             // look through the methods of this class to find one that matches the local name
146

147             Method[] methods = theClass.getMethods();
148             for (int m = 0; m < methods.length; m++) {
149
150                 Method theMethod = methods[m];
151                 if (theMethod.getName().equals(name) && Modifier.isPublic(theMethod.getModifiers())) {
152                     if (arity == -1) return true;
153                     Class JavaDoc[] theParameterTypes = theMethod.getParameterTypes();
154                     boolean isStatic = Modifier.isStatic(theMethod.getModifiers());
155
156                     // if the method is not static, the first supplied argument is the instance, so
157
// discount it
158

159                     significantArgs = (isStatic ? arity : arity - 1);
160
161                     if (significantArgs >= 0) {
162
163                         if (theParameterTypes.length == significantArgs &&
164                                 (significantArgs == 0 || theParameterTypes[0] != XPathContext.class)) {
165                             return true;
166                         }
167
168                         // we allow the method to have an extra parameter if the first parameter is XPathContext
169

170                         if (theParameterTypes.length == significantArgs + 1 &&
171                                 theParameterTypes[0] == XPathContext.class) {
172                             return true;
173                         }
174                     }
175                 }
176             }
177
178             // look through the fields of this class to find those that matches the local name
179

180             Field[] fields = theClass.getFields();
181             for (int m = 0; m < fields.length; m++) {
182
183                 Field theField = fields[m];
184                 if (theField.getName().equals(name) && Modifier.isPublic(theField.getModifiers())) {
185                     if (arity == -1) return true;
186                     boolean isStatic = Modifier.isStatic(theField.getModifiers());
187
188                     // if the field is not static, the first supplied argument is the instance, so
189
// discount it
190

191                     significantArgs = (isStatic ? arity : arity - 1);
192
193                     if (significantArgs == 0) {
194                         return true;
195                     }
196                 }
197             }
198
199             return false;
200         }
201
202     }
203
204     /**
205      * Bind an extension function, given the URI and local parts of the function name,
206      * and the list of expressions supplied as arguments. This method is called at compile
207      * time.
208      * @param nameCode The namepool code of the function name. This must match the supplied
209      * URI and local name.
210      * @param uri The URI of the function name
211      * @param local The local part of the function name
212      * @param staticArgs The expressions supplied statically in the function call. The intention is
213      * that the static type of the arguments (obtainable via getItemType() and getCardinality()) may
214      * be used as part of the binding algorithm.
215      * @return An object representing the extension function to be called, if one is found;
216      * null if no extension function was found matching the required name, arity, or signature.
217      */

218
219     public Expression bind(int nameCode, String JavaDoc uri, String JavaDoc local, Expression[] staticArgs)
220             throws XPathException {
221
222         boolean debug = config.isTraceExternalFunctions();
223         if (!config.isAllowExternalFunctions()) {
224             if (debug) {
225                 diag.println("Calls to extension functions have been disabled");
226             }
227             return null;
228         }
229
230         Class JavaDoc reqClass;
231         Exception JavaDoc theException = null;
232         ArrayList JavaDoc candidateMethods = new ArrayList JavaDoc(10);
233         Class JavaDoc resultClass = null;
234
235         try {
236             reqClass = getExternalJavaClass(uri);
237             if (reqClass == null) {
238                 return null;
239             }
240         } catch (Exception JavaDoc err) {
241             throw new StaticError("Cannot load external Java class", err);
242         }
243
244         if (debug) {
245             diag.println("Looking for method " + local + " in Java class " + reqClass);
246             diag.println("Number of actual arguments = " + staticArgs.length);
247         }
248
249         int numArgs = staticArgs.length;
250         int significantArgs;
251
252         Class JavaDoc theClass = reqClass;
253
254         // if the method name is "new", look for a matching constructor
255

256         if ("new".equals(local)) {
257
258             if (debug) {
259                 diag.println("Looking for a constructor");
260             }
261
262             int mod = theClass.getModifiers();
263             if (Modifier.isAbstract(mod)) {
264                 theException = new StaticError("Class " + theClass + " is abstract");
265             } else if (Modifier.isInterface(mod)) {
266                 theException = new StaticError(theClass + " is an interface");
267             } else if (Modifier.isPrivate(mod)) {
268                 theException = new StaticError("Class " + theClass + " is private");
269             } else if (Modifier.isProtected(mod)) {
270                 theException = new StaticError("Class " + theClass + " is protected");
271             }
272
273             if (theException != null) {
274                 if (debug) {
275                     diag.println("Cannot construct an instance: " + theException.getMessage());
276                 }
277                 return null;
278             }
279
280             Constructor[] constructors = theClass.getConstructors();
281             for (int c = 0; c < constructors.length; c++) {
282                 Constructor theConstructor = constructors[c];
283                 if (debug) {
284                     diag.println("Found a constructor with " + theConstructor.getParameterTypes().length + " arguments");
285                 }
286                 if (theConstructor.getParameterTypes().length == numArgs) {
287                     candidateMethods.add(theConstructor);
288                 }
289             }
290             if (candidateMethods.size() == 0) {
291                 theException = new StaticError("No constructor with " + numArgs +
292                         (numArgs == 1 ? " parameter" : " parameters") +
293                         " found in class " + theClass.getName());
294                 if (debug) {
295                     diag.println(theException.getMessage());
296                 }
297                 return null;
298             }
299         } else {
300
301             // convert any hyphens in the name, camelCasing the following character
302

303             String JavaDoc name = toCamelCase(local, debug, diag);
304
305             // look through the methods of this class to find one that matches the local name
306

307             Method[] methods = theClass.getMethods();
308             boolean consistentReturnType = true;
309             for (int m = 0; m < methods.length; m++) {
310
311                 Method theMethod = methods[m];
312
313                 if (debug) {
314                     if (theMethod.getName().equals(name)) {
315                         diag.println("Trying method " + theMethod.getName() + ": name matches");
316                         if (!Modifier.isPublic(theMethod.getModifiers())) {
317                             diag.println(" -- but the method is not public");
318                         }
319                     } else {
320                         diag.println("Trying method " + theMethod.getName() + ": name does not match");
321                     }
322                 }
323
324                 if (theMethod.getName().equals(name) &&
325                         Modifier.isPublic(theMethod.getModifiers())) {
326
327                     if (consistentReturnType) {
328                         if (resultClass == null) {
329                             resultClass = theMethod.getReturnType();
330                         } else {
331                             consistentReturnType =
332                                     (theMethod.getReturnType() == resultClass);
333                         }
334                     }
335                     Class JavaDoc[] theParameterTypes = theMethod.getParameterTypes();
336                     boolean isStatic = Modifier.isStatic(theMethod.getModifiers());
337
338                     // if the method is not static, the first supplied argument is the instance, so
339
// discount it
340

341                     if (debug) {
342                         diag.println("Method is " + (isStatic ? "" : "not ") + "static");
343                     }
344
345                     significantArgs = (isStatic ? numArgs : numArgs - 1);
346
347                     if (significantArgs >= 0) {
348
349                         if (debug) {
350                             diag.println("Method has " + theParameterTypes.length + " argument" +
351                                     (theParameterTypes.length == 1 ? "" : "s") +
352                                     "; expecting " + significantArgs);
353                         }
354
355                         if (theParameterTypes.length == significantArgs &&
356                                 (significantArgs == 0 || theParameterTypes[0] != XPathContext.class)) {
357                             if (debug) {
358                                 diag.println("Found a candidate method:");
359                                 diag.println(" " + theMethod);
360                             }
361                             candidateMethods.add(theMethod);
362                         }
363
364                         // we allow the method to have an extra parameter if the first parameter is XPathContext
365

366                         if (theParameterTypes.length == significantArgs + 1 &&
367                                 theParameterTypes[0] == XPathContext.class) {
368                             if (debug) {
369                                 diag.println("Method is a candidate because first argument is XPathContext");
370                             }
371                             candidateMethods.add(theMethod);
372                         }
373                     }
374                 }
375             }
376
377             // Code added by GS -- start
378

379             // look through the fields of this class to find those that matches the local name
380

381             Field[] fields = theClass.getFields();
382             for (int m = 0; m < fields.length; m++) {
383
384                 Field theField = fields[m];
385
386                 if (debug) {
387                     if (theField.getName().equals(name)) {
388                         diag.println("Trying field " + theField.getName() + ": name matches");
389                         if (!Modifier.isPublic(theField.getModifiers())) {
390                             diag.println(" -- but the field is not public");
391                         }
392                     } else {
393                         diag.println("Trying field " + theField.getName() + ": name does not match");
394                     }
395                 }
396
397                 if (theField.getName().equals(name) &&
398                         Modifier.isPublic(theField.getModifiers())) {
399                     if (consistentReturnType) {
400                         if (resultClass == null) {
401                             resultClass = theField.getType();
402                         } else {
403                             consistentReturnType =
404                                     (theField.getType() == resultClass);
405                         }
406                     }
407                     boolean isStatic = Modifier.isStatic(theField.getModifiers());
408
409                     // if the field is not static, the first supplied argument is the instance, so
410
// discount it
411

412                     if (debug) {
413                         diag.println("Field is " + (isStatic ? "" : "not ") + "static");
414                     }
415
416                     significantArgs = (isStatic ? numArgs : numArgs - 1);
417
418                     if (significantArgs == 0) {
419                         if (debug) {
420                             diag.println("Found a candidate field:");
421                             diag.println(" " + theField);
422                         }
423                         candidateMethods.add(theField);
424                     }
425                 }
426             }
427
428             // End of code added by GS
429

430             // No method found?
431

432             if (candidateMethods.size() == 0) {
433                 theException = new StaticError("No method or field matching " + name +
434                         " with " + numArgs +
435                         (numArgs == 1 ? " parameter" : " parameters") +
436                         " found in class " + theClass.getName());
437                 if (debug) {
438                     diag.println(theException.getMessage());
439                 }
440                 return null;
441             }
442         }
443         if (candidateMethods.size() == 0) {
444             if (debug) {
445                 diag.println("There is no suitable method matching the arguments of function " + local);
446             }
447             return null;
448         }
449         AccessibleObject method = getBestFit(candidateMethods, staticArgs, theClass);
450         if (method == null) {
451             if (candidateMethods.size() > 1) {
452                 // There was more than one candidate method, and we can't decide which to use.
453
// This may be because insufficient type information is available at this stage.
454
// Return an UnresolvedExtensionFunction, and try to resolve it later when more
455
// type information is known.
456
return new UnresolvedExtensionFunction(nameCode, theClass, candidateMethods, staticArgs);
457             }
458             return null;
459         } else {
460             ExtensionFunctionFactory factory = config.getExtensionFunctionFactory();
461             return factory.makeExtensionFunctionCall(nameCode, theClass, method, staticArgs);
462         }
463     }
464
465
466     /**
467      * Get the best fit amongst all the candidate methods, constructors, or fields, based on the static types
468      * of the supplied arguments
469      * @param candidateMethods a list of all the methods, fields, and constructors that match the extension
470      * function call in name and arity (but not necessarily in the types of the arguments)
471      * @param args the expressions supplied as arguments.F
472      * @return the result is either a Method or a Constructor or a Field, or null if no unique best fit
473      * method could be found.
474      */

475
476     private AccessibleObject getBestFit(List JavaDoc candidateMethods, Expression[] args, Class JavaDoc theClass) {
477         boolean debug = config.isTraceExternalFunctions();
478         int candidates = candidateMethods.size();
479
480         if (candidates == 1) {
481             // short cut: there is only one candidate method
482
return (AccessibleObject) candidateMethods.get(0);
483
484         } else {
485             // choose the best fit method or constructor or field
486
// for each pair of candidate methods, eliminate either or both of the pair
487
// if one argument is less-preferred
488

489             if (debug) {
490                 diag.println("Finding best fit method with arguments:");
491                 for (int v = 0; v < args.length; v++) {
492                     args[v].display(10, config.getNamePool(), diag);
493                 }
494             }
495
496             boolean eliminated[] = new boolean[candidates];
497             for (int i = 0; i < candidates; i++) {
498                 eliminated[i] = false;
499             }
500
501             if (debug) {
502                 for (int i = 0; i < candidates; i++) {
503                     int[] pref_i = getConversionPreferences(
504                             args,
505                             (AccessibleObject) candidateMethods.get(i), theClass);
506                     diag.println("Trying option " + i + ": " + candidateMethods.get(i).toString());
507                     if (pref_i == null) {
508                         diag.println("Arguments cannot be converted to required types");
509                     } else {
510                         String JavaDoc prefs = "[";
511                         for (int p = 0; p < pref_i.length; p++) {
512                             if (p != 0) prefs += ", ";
513                             prefs += pref_i[p];
514                         }
515                         prefs += "]";
516                         diag.println("Conversion preferences are " + prefs);
517                     }
518                 }
519             }
520
521             for (int i = 0; i < candidates; i++) {
522                 int[] pref_i = getConversionPreferences(
523                         args,
524                         (AccessibleObject) candidateMethods.get(i), theClass);
525
526                 if (pref_i == null) {
527                     eliminated[i] = true;
528                 }
529                 if (!eliminated[i]) {
530                     for (int j = i + 1; j < candidates; j++) {
531                         if (!eliminated[j]) {
532                             int[] pref_j = getConversionPreferences(
533                                     args,
534                                     (AccessibleObject) candidateMethods.get(j), theClass);
535                             if (pref_j == null) {
536                                 eliminated[j] = true;
537                             } else {
538                                 for (int k = 0; k < pref_j.length; k++) {
539                                     if (pref_i[k] > pref_j[k] && !eliminated[i]) { // high number means less preferred
540
eliminated[i] = true;
541                                         if (debug) {
542                                             diag.println("Eliminating option " + i);
543                                         }
544                                     }
545                                     if (pref_i[k] < pref_j[k] && !eliminated[j]) {
546                                         eliminated[j] = true;
547                                         if (debug) {
548                                             diag.println("Eliminating option " + j);
549                                         }
550                                     }
551                                 }
552                             }
553                         }
554                     }
555                 }
556             }
557
558             int remaining = 0;
559             AccessibleObject theMethod = null;
560             for (int r = 0; r < candidates; r++) {
561                 if (!eliminated[r]) {
562                     theMethod = (AccessibleObject) candidateMethods.get(r);
563                     remaining++;
564                 }
565             }
566
567             if (debug) {
568                 diag.println("Number of candidate methods remaining: " + remaining);
569             }
570
571             if (remaining == 0) {
572                 if (debug) {
573                     diag.println("There are " + candidates +
574                             " candidate Java methods matching the function name, but none is a unique best match");
575                 }
576                 return null;
577             }
578
579             if (remaining > 1) {
580                 if (debug) {
581                     diag.println("There are several Java methods that match the function name equally well");
582                 }
583                 return null;
584             }
585
586             return theMethod;
587         }
588     }
589
590     /**
591      * Convert a name to camelCase (by removing hyphens and changing the following
592      * letter to capitals)
593      * @param name the name to be converted to camelCase
594      * @param debug true if tracing is required
595      * @return the camelCased name
596      */

597
598     private static String JavaDoc toCamelCase(String JavaDoc name, boolean debug, PrintStream JavaDoc diag) {
599         if (name.indexOf('-') >= 0) {
600             FastStringBuffer buff = new FastStringBuffer(name.length());
601             boolean afterHyphen = false;
602             for (int n = 0; n < name.length(); n++) {
603                 char c = name.charAt(n);
604                 if (c == '-') {
605                     afterHyphen = true;
606                 } else {
607                     if (afterHyphen) {
608                         buff.append(Character.toUpperCase(c));
609                     } else {
610                         buff.append(c);
611                     }
612                     afterHyphen = false;
613                 }
614             }
615             name = buff.toString();
616             if (debug) {
617                 diag.println("Seeking a method with adjusted name " + name);
618             }
619         }
620         return name;
621     }
622
623     /**
624      * Get an array of integers representing the conversion distances of each "real" argument
625      * to a given method
626      * @param args: the actual expressions supplied in the function call
627      * @param method: the method or constructor.
628      * @return an array of integers, one for each argument, indicating the conversion
629      * distances. A high number indicates low preference. If any of the arguments cannot
630      * be converted to the corresponding type defined in the method signature, return null.
631      */

632
633     private int[] getConversionPreferences(Expression[] args, AccessibleObject method, Class JavaDoc theClass) {
634
635         Class JavaDoc[] params;
636         int firstArg;
637
638         if (method instanceof Constructor) {
639             firstArg = 0;
640             params = ((Constructor) method).getParameterTypes();
641         } else if (method instanceof Method) {
642             boolean isStatic = Modifier.isStatic(((Method) method).getModifiers());
643             firstArg = (isStatic ? 0 : 1);
644             params = ((Method) method).getParameterTypes();
645         } else if (method instanceof Field) {
646             boolean isStatic = Modifier.isStatic(((Field) method).getModifiers());
647             firstArg = (isStatic ? 0 : 1);
648             params = NO_PARAMS;
649         } else {
650             throw new AssertionError JavaDoc("property " + method + " was neither constructor, method, nor field");
651         }
652
653         int noOfArgs = args.length;
654         int preferences[] = new int[noOfArgs];
655         int firstParam = 0;
656
657         if (params.length > 0 && params[0] == XPathContext.class) {
658             firstParam = 1;
659         }
660         for (int i = firstArg; i < noOfArgs; i++) {
661             preferences[i] = getConversionPreference(args[i], params[i + firstParam - firstArg]);
662             if (preferences[i] == -1) {
663                 return null;
664             }
665         }
666
667         if (firstArg == 1) {
668             preferences[0] = getConversionPreference(args[0], theClass);
669             if (preferences[0] == -1) {
670                 return null;
671             }
672         }
673
674         return preferences;
675     }
676
677     /**
678      * Get the conversion preference from a given XPath type to a given Java class
679      * @param arg the supplied XPath expression (the static type of this expression
680      * is used as input to the algorithm)
681      * @param required the Java class of the relevant argument of the Java method
682      * @return the conversion preference. A high number indicates a low preference;
683      * -1 indicates that conversion is not possible.
684      */

685
686     private int getConversionPreference(Expression arg, Class JavaDoc required) {
687         ItemType itemType = arg.getItemType();
688         int cardinality = arg.getCardinality();
689         if (required == Object JavaDoc.class) {
690             return 100;
691         } else if (Cardinality.allowsMany(cardinality)) {
692             if (required.isAssignableFrom(SequenceIterator.class)) {
693                 return 20;
694             } else if (required.isAssignableFrom(Value.class)) {
695                 return 21;
696             } else if (Collection.class.isAssignableFrom(required)) {
697                 return 22;
698             } else if (required.isArray()) {
699                 return 24;
700                 // sort out at run-time whether the component type of the array is actually suitable
701
} else {
702                 return 80; // conversion possible only if external object model supports it
703
}
704         } else {
705             if (Type.isNodeType(itemType)) {
706                 if (required.isAssignableFrom(NodeInfo.class)) {
707                     return 20;
708                 } else if (required.isAssignableFrom(DocumentInfo.class)) {
709                     return 21;
710                 } else {
711                     return 80;
712                 }
713             } else if (itemType instanceof ExternalObjectType) {
714                 Class JavaDoc ext = ((ExternalObjectType)itemType).getJavaClass();
715                 if (required.isAssignableFrom(ext)) {
716                     return 10;
717                 } else {
718                     return -1;
719                 }
720             } else {
721                 int primitiveType = itemType.getPrimitiveType();
722                 return atomicConversionPreference(primitiveType, required);
723             }
724         }
725     }
726
727     private static final Class JavaDoc[] NO_PARAMS = new Class JavaDoc[0];
728
729
730     /**
731      * Get the conversion preference from an XPath primitive atomic type to a Java class
732      * @param primitiveType integer code identifying the XPath primitive type, for example
733      * {@link net.sf.saxon.type.Type#INTEGER} or {@link net.sf.saxon.type.Type#STRING}
734      * @param required The Java Class named in the method signature
735      * @return an integer indicating the relative preference for converting this primitive type
736      * to this Java class. A high number indicates a low preference. All values are in the range
737      * 50 to 100. For example, the conversion of an XPath String to {@link net.sf.saxon.value.StringValue} is 50, while
738      * XPath String to {@link java.lang.String} is 51. The value -1 indicates that the conversion is not allowed.
739      */

740
741     protected int atomicConversionPreference(int primitiveType, Class JavaDoc required) {
742         if (required == Object JavaDoc.class) return 100;
743         switch (primitiveType) {
744             case Type.STRING:
745                 if (required.isAssignableFrom(StringValue.class)) return 50;
746                 if (required == String JavaDoc.class) return 51;
747                 if (required == CharSequence JavaDoc.class) return 51;
748                 return -1;
749             case Type.DOUBLE:
750                 if (required.isAssignableFrom(DoubleValue.class)) return 50;
751                 if (required == double.class) return 50;
752                 if (required == Double JavaDoc.class) return 51;
753                 return -1;
754             case Type.FLOAT:
755                 if (required.isAssignableFrom(FloatValue.class)) return 50;
756                 if (required == float.class) return 50;
757                 if (required == Float JavaDoc.class) return 51;
758                 if (required == double.class) return 52;
759                 if (required == Double JavaDoc.class) return 53;
760                 return -1;
761             case Type.DECIMAL:
762                 if (required.isAssignableFrom(DecimalValue.class)) return 50;
763                 if (required == BigDecimal JavaDoc.class) return 50;
764                 if (required == double.class) return 51;
765                 if (required == Double JavaDoc.class) return 52;
766                 if (required == float.class) return 53;
767                 if (required == Float JavaDoc.class) return 54;
768                 return -1;
769             case Type.INTEGER:
770                 if (required.isAssignableFrom(IntegerValue.class)) return 50;
771                 if (required == BigInteger JavaDoc.class) return 51;
772                 if (required == BigDecimal JavaDoc.class) return 52;
773                 if (required == long.class) return 53;
774                 if (required == Long JavaDoc.class) return 54;
775                 if (required == int.class) return 55;
776                 if (required == Integer JavaDoc.class) return 56;
777                 if (required == short.class) return 57;
778                 if (required == Short JavaDoc.class) return 58;
779                 if (required == byte.class) return 59;
780                 if (required == Byte JavaDoc.class) return 60;
781                 if (required == double.class) return 61;
782                 if (required == Double JavaDoc.class) return 62;
783                 if (required == float.class) return 63;
784                 if (required == Float JavaDoc.class) return 64;
785                 return -1;
786             case Type.BOOLEAN:
787                 if (required.isAssignableFrom(BooleanValue.class)) return 50;
788                 if (required == boolean.class) return 51;
789                 if (required == Boolean JavaDoc.class) return 52;
790                 return -1;
791             case Type.DATE:
792             case Type.G_DAY:
793             case Type.G_MONTH_DAY:
794             case Type.G_MONTH:
795             case Type.G_YEAR_MONTH:
796             case Type.G_YEAR:
797                 if (required.isAssignableFrom(DateValue.class)) return 50;
798                 if (required.isAssignableFrom(Date JavaDoc.class)) return 51;
799                 return -1;
800             case Type.DATE_TIME:
801                 if (required.isAssignableFrom(DateTimeValue.class)) return 50;
802                 if (required.isAssignableFrom(Date JavaDoc.class)) return 51;
803                 return -1;
804             case Type.TIME:
805                 if (required.isAssignableFrom(TimeValue.class)) return 50;
806                 return -1;
807             case Type.DURATION:
808             case Type.YEAR_MONTH_DURATION:
809             case Type.DAY_TIME_DURATION:
810                 if (required.isAssignableFrom(DurationValue.class)) return 50;
811                 return -1;
812             case Type.ANY_URI:
813                 if (required.isAssignableFrom(AnyURIValue.class)) return 50;
814                 if (required == URI JavaDoc.class) return 51;
815                 if (required == URL JavaDoc.class) return 52;
816                 if (required == String JavaDoc.class) return 53;
817                 if (required == CharSequence JavaDoc.class) return 53;
818                 return -1;
819             case Type.QNAME:
820                 if (required.isAssignableFrom(QNameValue.class)) return 50;
821                 //if (required.isAssignableFrom(QName.class)) return 51;
822
// TODO: reinstate above line under JDK 1.5
823
if (required.getClass().getName().equals("javax.xml.namespace.QName")) return 51;
824                 return -1;
825             case Type.BASE64_BINARY:
826                 if (required.isAssignableFrom(Base64BinaryValue.class)) return 50;
827                 return -1;
828             case Type.HEX_BINARY:
829                 if (required.isAssignableFrom(HexBinaryValue.class)) return 50;
830                 return -1;
831             case Type.UNTYPED_ATOMIC:
832                 return 50;
833             default:
834                 return -1;
835         }
836     }
837
838     /**
839      * Get an external Java class corresponding to a given namespace prefix, if there is
840      * one.
841      * @param uri The namespace URI corresponding to the prefix used in the function call.
842      * @return the Java class name if a suitable class exists, otherwise return null.
843      */

844
845     private Class JavaDoc getExternalJavaClass(String JavaDoc uri) {
846
847         // First see if an explicit mapping has been registered for this URI
848

849         Class JavaDoc c = (Class JavaDoc) explicitMappings.get(uri);
850         if (c != null) {
851             return c;
852         }
853
854         // Failing that, try to identify a class directly from the URI
855

856         try {
857
858             // support the URN format java:full.class.Name
859

860             if (uri.startsWith("java:")) {
861                 return config.getClass(uri.substring(5), config.isTraceExternalFunctions(), null);
862             }
863
864             // extract the class name as anything in the URI after the last "/"
865
// if there is one, or the whole class name otherwise
866

867             int slash = uri.lastIndexOf('/');
868             if (slash < 0) {
869                 return config.getClass(uri, config.isTraceExternalFunctions(), null);
870             } else if (slash == uri.length() - 1) {
871                 return null;
872             } else {
873                 return config.getClass(uri.substring(slash + 1), config.isTraceExternalFunctions(), null);
874             }
875         } catch (XPathException err) {
876             return null;
877         }
878     }
879
880     /**
881      * Inner class representing an unresolved extension function call. This arises when there is insufficient
882      * static type information available at the time the function call is parsed to determine which of several
883      * candidate Java methods to invoke. The function call cannot be executed; it must be resolved to an
884      * actual Java method during the analysis phase.
885      */

886
887     private class UnresolvedExtensionFunction extends CompileTimeFunction {
888
889         List JavaDoc candidateMethods;
890         int nameCode;
891         Class JavaDoc theClass;
892
893
894         public UnresolvedExtensionFunction(int nameCode, Class JavaDoc theClass, List JavaDoc candidateMethods, Expression[] staticArgs) {
895             setArguments(staticArgs);
896             this.nameCode = nameCode;
897             this.theClass = theClass;
898             this.candidateMethods = candidateMethods;
899         }
900
901         /**
902          * Type-check the expression.
903          */

904
905         public Expression typeCheck(StaticContext env, ItemType contextItemType) throws XPathException {
906             for (int i=0; i<argument.length; i++) {
907                 Expression exp = argument[i].typeCheck(env, contextItemType);
908                 if (exp != argument[i]) {
909                     adoptChildExpression(exp);
910                     argument[i] = exp;
911                 }
912             }
913             AccessibleObject method = getBestFit(candidateMethods, argument, theClass);
914             if (method == null) {
915                 StaticError err = new StaticError("There is more than one method matching the function call " +
916                         config.getNamePool().getDisplayName(nameCode) +
917                         ", and there is insufficient type information to determine which one should be used");
918                 err.setLocator(this);
919                 throw err;
920             } else {
921                 ExtensionFunctionFactory factory = config.getExtensionFunctionFactory();
922                 return factory.makeExtensionFunctionCall(nameCode, theClass, method, argument);
923             }
924         }
925     }
926
927     /**
928      * This method creates a copy of a FunctionLibrary: if the original FunctionLibrary allows
929      * new functions to be added, then additions to this copy will not affect the original, or
930      * vice versa.
931      *
932      * @return a copy of this function library. This must be an instance of the original class.
933      */

934
935     public FunctionLibrary copy() {
936         JavaExtensionLibrary jel = new JavaExtensionLibrary(config);
937         jel.explicitMappings = new HashMap JavaDoc(explicitMappings);
938         jel.diag = diag;
939         return jel;
940     }
941
942 }
943
944 //
945
// The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
946
// you may not use this file except in compliance with the License. You may obtain a copy of the
947
// License at http://www.mozilla.org/MPL/
948
//
949
// Software distributed under the License is distributed on an "AS IS" basis,
950
// WITHOUT WARRANTY OF ANY KIND, either express or implied.
951
// See the License for the specific language governing rights and limitations under the License.
952
//
953
// The Original Code is: all this file.
954
//
955
// The Initial Developer of the Original Code is Michael H. Kay.
956
//
957
// Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
958
//
959
// Contributor(s): none.
960
//
Popular Tags