0001: package net.sf.saxon.functions;
0002:
0003: import net.sf.saxon.Configuration;
0004: import net.sf.saxon.expr.Expression;
0005: import net.sf.saxon.expr.StaticContext;
0006: import net.sf.saxon.expr.XPathContext;
0007: import net.sf.saxon.om.*;
0008: import net.sf.saxon.trans.StaticError;
0009: import net.sf.saxon.trans.XPathException;
0010: import net.sf.saxon.type.ExternalObjectType;
0011: import net.sf.saxon.type.ItemType;
0012: import net.sf.saxon.type.Type;
0013: import net.sf.saxon.type.TypeHierarchy;
0014: import net.sf.saxon.value.*;
0015:
0016: import java.io.PrintStream;
0017: import java.lang.reflect.*;
0018: import java.math.BigDecimal;
0019: import java.math.BigInteger;
0020: import java.net.URI;
0021: import java.net.URL;
0022: import java.util.ArrayList;
0023: import java.util.Date;
0024: import java.util.HashMap;
0025: import java.util.List;
0026:
0027: /**
0028: * The JavaExtensionLibrary is a FunctionLibrary that binds XPath function calls to
0029: * calls on Java methods (or constructors, or fields). It performs a mapping from
0030: * the namespace URI of the function to the Java class (the mapping is partly table
0031: * driven and partly algorithmic), and maps the local name of the function to the
0032: * Java method, constructor, or field within the class. If the Java methods are
0033: * polymorphic, then it tries to select the appropriate method based on the static types
0034: * of the supplied arguments. Binding is done entirely at XPath compilation time.
0035: */
0036:
0037: public class JavaExtensionLibrary implements FunctionLibrary {
0038:
0039: private Configuration config;
0040:
0041: // HashMap containing URI->Class mappings. This includes conventional
0042: // URIs such as the Saxon and EXSLT namespaces, and mapping defined by
0043: // the user using saxon:script
0044:
0045: private HashMap explicitMappings = new HashMap(10);
0046:
0047: // Output destination for debug messages. At present this cannot be configured.
0048:
0049: private transient PrintStream diag = System.err;
0050:
0051: /**
0052: * Construct a JavaExtensionLibrary and establish the default uri->class mappings.
0053: * @param config The Saxon configuration
0054: */
0055:
0056: public JavaExtensionLibrary(Configuration config) {
0057: this .config = config;
0058: setDefaultURIMappings();
0059: }
0060:
0061: /**
0062: * Define initial mappings of "well known" namespace URIs to Java classes (this covers
0063: * the Saxon and EXSLT extensions). The method is protected so it can be overridden in
0064: * a subclass.
0065: */
0066: protected void setDefaultURIMappings() {
0067: declareJavaClass(NamespaceConstant.SAXON,
0068: net.sf.saxon.functions.Extensions.class);
0069: declareJavaClass(NamespaceConstant.EXSLT_COMMON,
0070: net.sf.saxon.exslt.Common.class);
0071: declareJavaClass(NamespaceConstant.EXSLT_SETS,
0072: net.sf.saxon.exslt.Sets.class);
0073: declareJavaClass(NamespaceConstant.EXSLT_MATH,
0074: net.sf.saxon.exslt.Math.class);
0075: declareJavaClass(NamespaceConstant.EXSLT_DATES_AND_TIMES,
0076: net.sf.saxon.exslt.Date.class);
0077: declareJavaClass(NamespaceConstant.EXSLT_RANDOM,
0078: net.sf.saxon.exslt.Random.class);
0079: }
0080:
0081: /**
0082: * Declare a mapping from a specific namespace URI to a Java class
0083: * @param uri the namespace URI of the function name
0084: * @param theClass the Java class that implements the functions in this namespace
0085: */
0086:
0087: public void declareJavaClass(String uri, Class theClass) {
0088: explicitMappings.put(uri, theClass);
0089: }
0090:
0091: /**
0092: * Test whether an extension function with a given name and arity is available. This supports
0093: * the function-available() function in XSLT. This method may be called either at compile time
0094: * or at run time.
0095: * @param fingerprint The code that identifies the function name in the NamePool. This must
0096: * match the supplied URI and local name.
0097: * @param uri The URI of the function name
0098: * @param local The local part of the function name
0099: * @param arity The number of arguments. This is set to -1 in the case of the single-argument
0100: * function-available() function; in this case the method should return true if there is some
0101: * matching extension function, regardless of its arity.
0102: */
0103:
0104: public boolean isAvailable(int fingerprint, String uri,
0105: String local, int arity) {
0106: if (!config.isAllowExternalFunctions()) {
0107: return false;
0108: }
0109: Class reqClass;
0110: try {
0111: reqClass = getExternalJavaClass(uri);
0112: if (reqClass == null) {
0113: return false;
0114: }
0115: } catch (Exception err) {
0116: return false;
0117: }
0118: int significantArgs;
0119:
0120: Class theClass = reqClass;
0121:
0122: // if the method name is "new", look for a matching constructor
0123:
0124: if ("new".equals(local)) {
0125:
0126: int mod = theClass.getModifiers();
0127: if (Modifier.isAbstract(mod)) {
0128: return false;
0129: } else if (Modifier.isInterface(mod)) {
0130: return false;
0131: } else if (Modifier.isPrivate(mod)) {
0132: return false;
0133: } else if (Modifier.isProtected(mod)) {
0134: return false;
0135: }
0136:
0137: if (arity == -1)
0138: return true;
0139:
0140: Constructor[] constructors = theClass.getConstructors();
0141: for (int c = 0; c < constructors.length; c++) {
0142: Constructor theConstructor = constructors[c];
0143: if (theConstructor.getParameterTypes().length == arity) {
0144: return true;
0145: }
0146: }
0147: return false;
0148: } else {
0149:
0150: // convert any hyphens in the name, camelCasing the following character
0151:
0152: String name = toCamelCase(local, false, diag);
0153:
0154: // look through the methods of this class to find one that matches the local name
0155:
0156: Method[] methods = theClass.getMethods();
0157: for (int m = 0; m < methods.length; m++) {
0158:
0159: Method theMethod = methods[m];
0160: if (theMethod.getName().equals(name)
0161: && Modifier.isPublic(theMethod.getModifiers())) {
0162: if (arity == -1)
0163: return true;
0164: Class[] theParameterTypes = theMethod
0165: .getParameterTypes();
0166: boolean isStatic = Modifier.isStatic(theMethod
0167: .getModifiers());
0168:
0169: // if the method is not static, the first supplied argument is the instance, so
0170: // discount it
0171:
0172: significantArgs = (isStatic ? arity : arity - 1);
0173:
0174: if (significantArgs >= 0) {
0175:
0176: if (theParameterTypes.length == significantArgs
0177: && (significantArgs == 0 || theParameterTypes[0] != XPathContext.class)) {
0178: return true;
0179: }
0180:
0181: // we allow the method to have an extra parameter if the first parameter is XPathContext
0182:
0183: if (theParameterTypes.length == significantArgs + 1
0184: && theParameterTypes[0] == XPathContext.class) {
0185: return true;
0186: }
0187: }
0188: }
0189: }
0190:
0191: // look through the fields of this class to find those that matches the local name
0192:
0193: Field[] fields = theClass.getFields();
0194: for (int m = 0; m < fields.length; m++) {
0195:
0196: Field theField = fields[m];
0197: if (theField.getName().equals(name)
0198: && Modifier.isPublic(theField.getModifiers())) {
0199: if (arity == -1)
0200: return true;
0201: boolean isStatic = Modifier.isStatic(theField
0202: .getModifiers());
0203:
0204: // if the field is not static, the first supplied argument is the instance, so
0205: // discount it
0206:
0207: significantArgs = (isStatic ? arity : arity - 1);
0208:
0209: if (significantArgs == 0) {
0210: return true;
0211: }
0212: }
0213: }
0214:
0215: return false;
0216: }
0217:
0218: }
0219:
0220: /**
0221: * Bind an extension function, given the URI and local parts of the function name,
0222: * and the list of expressions supplied as arguments. This method is called at compile
0223: * time.
0224: * @param nameCode The namepool code of the function name. This must match the supplied
0225: * URI and local name.
0226: * @param uri The URI of the function name
0227: * @param local The local part of the function name
0228: * @param staticArgs The expressions supplied statically in the function call. The intention is
0229: * that the static type of the arguments (obtainable via getItemType() and getCardinality()) may
0230: * be used as part of the binding algorithm.
0231: * @return An object representing the extension function to be called, if one is found;
0232: * null if no extension function was found matching the required name, arity, or signature.
0233: */
0234:
0235: public Expression bind(int nameCode, String uri, String local,
0236: Expression[] staticArgs) throws XPathException {
0237:
0238: boolean debug = config.isTraceExternalFunctions();
0239: if (!config.isAllowExternalFunctions()) {
0240: if (debug) {
0241: diag
0242: .println("Calls to extension functions have been disabled");
0243: }
0244: return null;
0245: }
0246:
0247: Class reqClass;
0248: Exception theException = null;
0249: ArrayList candidateMethods = new ArrayList(10);
0250: Class resultClass = null;
0251:
0252: try {
0253: reqClass = getExternalJavaClass(uri);
0254: if (reqClass == null) {
0255: return null;
0256: }
0257: } catch (Exception err) {
0258: throw new StaticError("Cannot load external Java class",
0259: err);
0260: }
0261:
0262: if (debug) {
0263: diag.println("Looking for method " + local
0264: + " in Java class " + reqClass);
0265: diag.println("Number of actual arguments = "
0266: + staticArgs.length);
0267: }
0268:
0269: int numArgs = staticArgs.length;
0270: int significantArgs;
0271:
0272: Class theClass = reqClass;
0273:
0274: // if the method name is "new", look for a matching constructor
0275:
0276: if ("new".equals(local)) {
0277:
0278: if (debug) {
0279: diag.println("Looking for a constructor");
0280: }
0281:
0282: int mod = theClass.getModifiers();
0283: if (Modifier.isAbstract(mod)) {
0284: theException = new StaticError("Class " + theClass
0285: + " is abstract");
0286: } else if (Modifier.isInterface(mod)) {
0287: theException = new StaticError(theClass
0288: + " is an interface");
0289: } else if (Modifier.isPrivate(mod)) {
0290: theException = new StaticError("Class " + theClass
0291: + " is private");
0292: } else if (Modifier.isProtected(mod)) {
0293: theException = new StaticError("Class " + theClass
0294: + " is protected");
0295: }
0296:
0297: if (theException != null) {
0298: if (debug) {
0299: diag.println("Cannot construct an instance: "
0300: + theException.getMessage());
0301: }
0302: return null;
0303: }
0304:
0305: Constructor[] constructors = theClass.getConstructors();
0306: for (int c = 0; c < constructors.length; c++) {
0307: Constructor theConstructor = constructors[c];
0308: if (debug) {
0309: diag.println("Found a constructor with "
0310: + theConstructor.getParameterTypes().length
0311: + " arguments");
0312: }
0313: if (theConstructor.getParameterTypes().length == numArgs) {
0314: candidateMethods.add(theConstructor);
0315: }
0316: }
0317: if (candidateMethods.size() == 0) {
0318: theException = new StaticError("No constructor with "
0319: + numArgs
0320: + (numArgs == 1 ? " parameter" : " parameters")
0321: + " found in class " + theClass.getName());
0322: if (debug) {
0323: diag.println(theException.getMessage());
0324: }
0325: return null;
0326: }
0327: } else {
0328:
0329: // convert any hyphens in the name, camelCasing the following character
0330:
0331: String name = toCamelCase(local, debug, diag);
0332:
0333: // look through the methods of this class to find one that matches the local name
0334:
0335: Method[] methods = theClass.getMethods();
0336: boolean consistentReturnType = true;
0337: for (int m = 0; m < methods.length; m++) {
0338:
0339: Method theMethod = methods[m];
0340:
0341: if (debug) {
0342: if (theMethod.getName().equals(name)) {
0343: diag.println("Trying method "
0344: + theMethod.getName()
0345: + ": name matches");
0346: if (!Modifier
0347: .isPublic(theMethod.getModifiers())) {
0348: diag
0349: .println(" -- but the method is not public");
0350: }
0351: } else {
0352: diag.println("Trying method "
0353: + theMethod.getName()
0354: + ": name does not match");
0355: }
0356: }
0357:
0358: if (theMethod.getName().equals(name)
0359: && Modifier.isPublic(theMethod.getModifiers())) {
0360:
0361: if (consistentReturnType) {
0362: if (resultClass == null) {
0363: resultClass = theMethod.getReturnType();
0364: } else {
0365: consistentReturnType = (theMethod
0366: .getReturnType() == resultClass);
0367: }
0368: }
0369: Class[] theParameterTypes = theMethod
0370: .getParameterTypes();
0371: boolean isStatic = Modifier.isStatic(theMethod
0372: .getModifiers());
0373:
0374: // if the method is not static, the first supplied argument is the instance, so
0375: // discount it
0376:
0377: if (debug) {
0378: diag.println("Method is "
0379: + (isStatic ? "" : "not ") + "static");
0380: }
0381:
0382: significantArgs = (isStatic ? numArgs : numArgs - 1);
0383:
0384: if (significantArgs >= 0) {
0385:
0386: if (debug) {
0387: diag
0388: .println("Method has "
0389: + theParameterTypes.length
0390: + " argument"
0391: + (theParameterTypes.length == 1 ? ""
0392: : "s")
0393: + "; expecting "
0394: + significantArgs);
0395: }
0396:
0397: if (theParameterTypes.length == significantArgs
0398: && (significantArgs == 0 || theParameterTypes[0] != XPathContext.class)) {
0399: if (debug) {
0400: diag
0401: .println("Found a candidate method:");
0402: diag.println(" " + theMethod);
0403: }
0404: candidateMethods.add(theMethod);
0405: }
0406:
0407: // we allow the method to have an extra parameter if the first parameter is XPathContext
0408:
0409: if (theParameterTypes.length == significantArgs + 1
0410: && theParameterTypes[0] == XPathContext.class) {
0411: if (debug) {
0412: diag
0413: .println("Method is a candidate because first argument is XPathContext");
0414: }
0415: candidateMethods.add(theMethod);
0416: }
0417: }
0418: }
0419: }
0420:
0421: // Code added by GS -- start
0422:
0423: // look through the fields of this class to find those that matches the local name
0424:
0425: Field[] fields = theClass.getFields();
0426: for (int m = 0; m < fields.length; m++) {
0427:
0428: Field theField = fields[m];
0429:
0430: if (debug) {
0431: if (theField.getName().equals(name)) {
0432: diag
0433: .println("Trying field "
0434: + theField.getName()
0435: + ": name matches");
0436: if (!Modifier.isPublic(theField.getModifiers())) {
0437: diag
0438: .println(" -- but the field is not public");
0439: }
0440: } else {
0441: diag.println("Trying field "
0442: + theField.getName()
0443: + ": name does not match");
0444: }
0445: }
0446:
0447: if (theField.getName().equals(name)
0448: && Modifier.isPublic(theField.getModifiers())) {
0449: if (consistentReturnType) {
0450: if (resultClass == null) {
0451: resultClass = theField.getType();
0452: } else {
0453: consistentReturnType = (theField.getType() == resultClass);
0454: }
0455: }
0456: boolean isStatic = Modifier.isStatic(theField
0457: .getModifiers());
0458:
0459: // if the field is not static, the first supplied argument is the instance, so
0460: // discount it
0461:
0462: if (debug) {
0463: diag.println("Field is "
0464: + (isStatic ? "" : "not ") + "static");
0465: }
0466:
0467: significantArgs = (isStatic ? numArgs : numArgs - 1);
0468:
0469: if (significantArgs == 0) {
0470: if (debug) {
0471: diag.println("Found a candidate field:");
0472: diag.println(" " + theField);
0473: }
0474: candidateMethods.add(theField);
0475: }
0476: }
0477: }
0478:
0479: // End of code added by GS
0480:
0481: // No method found?
0482:
0483: if (candidateMethods.size() == 0) {
0484: theException = new StaticError(
0485: "No method or field matching "
0486: + name
0487: + " with "
0488: + numArgs
0489: + (numArgs == 1 ? " parameter"
0490: : " parameters")
0491: + " found in class "
0492: + theClass.getName());
0493: if (debug) {
0494: diag.println(theException.getMessage());
0495: }
0496: return null;
0497: }
0498: }
0499: if (candidateMethods.size() == 0) {
0500: if (debug) {
0501: diag
0502: .println("There is no suitable method matching the arguments of function "
0503: + local);
0504: }
0505: return null;
0506: }
0507: AccessibleObject method = getBestFit(candidateMethods,
0508: staticArgs, theClass);
0509: if (method == null) {
0510: if (candidateMethods.size() > 1) {
0511: // There was more than one candidate method, and we can't decide which to use.
0512: // This may be because insufficient type information is available at this stage.
0513: // Return an UnresolvedExtensionFunction, and try to resolve it later when more
0514: // type information is known.
0515: return new UnresolvedExtensionFunction(nameCode,
0516: theClass, candidateMethods, staticArgs);
0517: }
0518: return null;
0519: } else {
0520: ExtensionFunctionFactory factory = config
0521: .getExtensionFunctionFactory();
0522: return factory.makeExtensionFunctionCall(nameCode,
0523: theClass, method, staticArgs);
0524: }
0525: }
0526:
0527: /**
0528: * Get the best fit amongst all the candidate methods, constructors, or fields, based on the static types
0529: * of the supplied arguments
0530: * @param candidateMethods a list of all the methods, fields, and constructors that match the extension
0531: * function call in name and arity (but not necessarily in the types of the arguments)
0532: * @param args the expressions supplied as arguments.F
0533: * @return the result is either a Method or a Constructor or a Field, or null if no unique best fit
0534: * method could be found.
0535: */
0536:
0537: private AccessibleObject getBestFit(List candidateMethods,
0538: Expression[] args, Class theClass) {
0539: boolean debug = config.isTraceExternalFunctions();
0540: int candidates = candidateMethods.size();
0541:
0542: if (candidates == 1) {
0543: // short cut: there is only one candidate method
0544: return (AccessibleObject) candidateMethods.get(0);
0545:
0546: } else {
0547: // choose the best fit method or constructor or field
0548: // for each pair of candidate methods, eliminate either or both of the pair
0549: // if one argument is less-preferred
0550:
0551: if (debug) {
0552: diag.println("Finding best fit method with arguments:");
0553: for (int v = 0; v < args.length; v++) {
0554: args[v].display(10, config.getNamePool(), diag);
0555: }
0556: }
0557:
0558: boolean eliminated[] = new boolean[candidates];
0559: for (int i = 0; i < candidates; i++) {
0560: eliminated[i] = false;
0561: }
0562:
0563: if (debug) {
0564: for (int i = 0; i < candidates; i++) {
0565: int[] pref_i = getConversionPreferences(args,
0566: (AccessibleObject) candidateMethods.get(i),
0567: theClass);
0568: diag.println("Trying option " + i + ": "
0569: + candidateMethods.get(i).toString());
0570: if (pref_i == null) {
0571: diag
0572: .println("Arguments cannot be converted to required types");
0573: } else {
0574: String prefs = "[";
0575: for (int p = 0; p < pref_i.length; p++) {
0576: if (p != 0)
0577: prefs += ", ";
0578: prefs += pref_i[p];
0579: }
0580: prefs += "]";
0581: diag.println("Conversion preferences are "
0582: + prefs);
0583: }
0584: }
0585: }
0586:
0587: for (int i = 0; i < candidates; i++) {
0588: int[] pref_i = getConversionPreferences(args,
0589: (AccessibleObject) candidateMethods.get(i),
0590: theClass);
0591:
0592: if (pref_i == null) {
0593: eliminated[i] = true;
0594: }
0595: if (!eliminated[i]) {
0596: for (int j = i + 1; j < candidates; j++) {
0597: if (!eliminated[j]) {
0598: int[] pref_j = getConversionPreferences(
0599: args,
0600: (AccessibleObject) candidateMethods
0601: .get(j), theClass);
0602: if (pref_j == null) {
0603: eliminated[j] = true;
0604: } else {
0605: for (int k = 0; k < pref_j.length; k++) {
0606: if (pref_i[k] > pref_j[k]
0607: && !eliminated[i]) { // high number means less preferred
0608: eliminated[i] = true;
0609: if (debug) {
0610: diag
0611: .println("Eliminating option "
0612: + i);
0613: }
0614: }
0615: if (pref_i[k] < pref_j[k]
0616: && !eliminated[j]) {
0617: eliminated[j] = true;
0618: if (debug) {
0619: diag
0620: .println("Eliminating option "
0621: + j);
0622: }
0623: }
0624: }
0625: }
0626: }
0627: }
0628: }
0629: }
0630:
0631: int remaining = 0;
0632: AccessibleObject theMethod = null;
0633: for (int r = 0; r < candidates; r++) {
0634: if (!eliminated[r]) {
0635: theMethod = (AccessibleObject) candidateMethods
0636: .get(r);
0637: remaining++;
0638: }
0639: }
0640:
0641: if (debug) {
0642: diag.println("Number of candidate methods remaining: "
0643: + remaining);
0644: }
0645:
0646: if (remaining == 0) {
0647: if (debug) {
0648: diag
0649: .println("There are "
0650: + candidates
0651: + " candidate Java methods matching the function name, but none is a unique best match");
0652: }
0653: return null;
0654: }
0655:
0656: if (remaining > 1) {
0657: if (debug) {
0658: diag
0659: .println("There are several Java methods that match the function name equally well");
0660: }
0661: return null;
0662: }
0663:
0664: return theMethod;
0665: }
0666: }
0667:
0668: /**
0669: * Convert a name to camelCase (by removing hyphens and changing the following
0670: * letter to capitals)
0671: * @param name the name to be converted to camelCase
0672: * @param debug true if tracing is required
0673: * @return the camelCased name
0674: */
0675:
0676: private static String toCamelCase(String name, boolean debug,
0677: PrintStream diag) {
0678: if (name.indexOf('-') >= 0) {
0679: FastStringBuffer buff = new FastStringBuffer(name.length());
0680: boolean afterHyphen = false;
0681: for (int n = 0; n < name.length(); n++) {
0682: char c = name.charAt(n);
0683: if (c == '-') {
0684: afterHyphen = true;
0685: } else {
0686: if (afterHyphen) {
0687: buff.append(Character.toUpperCase(c));
0688: } else {
0689: buff.append(c);
0690: }
0691: afterHyphen = false;
0692: }
0693: }
0694: name = buff.toString();
0695: if (debug) {
0696: diag.println("Seeking a method with adjusted name "
0697: + name);
0698: }
0699: }
0700: return name;
0701: }
0702:
0703: /**
0704: * Get an array of integers representing the conversion distances of each "real" argument
0705: * to a given method
0706: * @param args: the actual expressions supplied in the function call
0707: * @param method: the method or constructor.
0708: * @return an array of integers, one for each argument, indicating the conversion
0709: * distances. A high number indicates low preference. If any of the arguments cannot
0710: * be converted to the corresponding type defined in the method signature, return null.
0711: */
0712:
0713: private int[] getConversionPreferences(Expression[] args,
0714: AccessibleObject method, Class theClass) {
0715:
0716: Class[] params;
0717: int firstArg;
0718: TypeHierarchy th = config.getNamePool().getTypeHierarchy();
0719: if (method instanceof Constructor) {
0720: firstArg = 0;
0721: params = ((Constructor) method).getParameterTypes();
0722: } else if (method instanceof Method) {
0723: boolean isStatic = Modifier.isStatic(((Method) method)
0724: .getModifiers());
0725: firstArg = (isStatic ? 0 : 1);
0726: params = ((Method) method).getParameterTypes();
0727: } else if (method instanceof Field) {
0728: boolean isStatic = Modifier.isStatic(((Field) method)
0729: .getModifiers());
0730: firstArg = (isStatic ? 0 : 1);
0731: params = NO_PARAMS;
0732: } else {
0733: throw new AssertionError("property " + method
0734: + " was neither constructor, method, nor field");
0735: }
0736:
0737: int noOfArgs = args.length;
0738: int preferences[] = new int[noOfArgs];
0739: int firstParam = 0;
0740:
0741: if (params.length > 0 && params[0] == XPathContext.class) {
0742: firstParam = 1;
0743: }
0744: for (int i = firstArg; i < noOfArgs; i++) {
0745: preferences[i] = getConversionPreference(th, args[i],
0746: params[i + firstParam - firstArg]);
0747: if (preferences[i] == -1) {
0748: return null;
0749: }
0750: }
0751:
0752: if (firstArg == 1) {
0753: preferences[0] = getConversionPreference(th, args[0],
0754: theClass);
0755: if (preferences[0] == -1) {
0756: return null;
0757: }
0758: }
0759:
0760: return preferences;
0761: }
0762:
0763: /**
0764: * Get the conversion preference from a given XPath type to a given Java class
0765: * @param arg the supplied XPath expression (the static type of this expression
0766: * is used as input to the algorithm)
0767: * @param required the Java class of the relevant argument of the Java method
0768: * @return the conversion preference. A high number indicates a low preference;
0769: * -1 indicates that conversion is not possible.
0770: */
0771:
0772: private int getConversionPreference(TypeHierarchy th,
0773: Expression arg, Class required) {
0774: ItemType itemType = arg.getItemType(th);
0775: int cardinality = arg.getCardinality();
0776: if (required == Object.class) {
0777: return 100;
0778: } else if (Cardinality.allowsMany(cardinality)) {
0779: if (required.isAssignableFrom(SequenceIterator.class)) {
0780: return 20;
0781: } else if (required.isAssignableFrom(Value.class)) {
0782: return 21;
0783: } else if (Collection.class.isAssignableFrom(required)) {
0784: return 22;
0785: } else if (required.isArray()) {
0786: return 24;
0787: // sort out at run-time whether the component type of the array is actually suitable
0788: } else {
0789: return 80; // conversion possible only if external object model supports it
0790: }
0791: } else {
0792: if (Type.isNodeType(itemType)) {
0793: if (required.isAssignableFrom(NodeInfo.class)) {
0794: return 20;
0795: } else if (required
0796: .isAssignableFrom(DocumentInfo.class)) {
0797: return 21;
0798: } else {
0799: return 80;
0800: }
0801: } else if (itemType instanceof ExternalObjectType) {
0802: Class ext = ((ExternalObjectType) itemType)
0803: .getJavaClass();
0804: if (required.isAssignableFrom(ext)) {
0805: return 10;
0806: } else {
0807: return -1;
0808: }
0809: } else {
0810: int primitiveType = itemType.getPrimitiveType();
0811: return atomicConversionPreference(primitiveType,
0812: required);
0813: }
0814: }
0815: }
0816:
0817: private static final Class[] NO_PARAMS = new Class[0];
0818:
0819: /**
0820: * Get the conversion preference from an XPath primitive atomic type to a Java class
0821: * @param primitiveType integer code identifying the XPath primitive type, for example
0822: * {@link net.sf.saxon.type.Type#INTEGER} or {@link net.sf.saxon.type.Type#STRING}
0823: * @param required The Java Class named in the method signature
0824: * @return an integer indicating the relative preference for converting this primitive type
0825: * to this Java class. A high number indicates a low preference. All values are in the range
0826: * 50 to 100. For example, the conversion of an XPath String to {@link net.sf.saxon.value.StringValue} is 50, while
0827: * XPath String to {@link java.lang.String} is 51. The value -1 indicates that the conversion is not allowed.
0828: */
0829:
0830: protected int atomicConversionPreference(int primitiveType,
0831: Class required) {
0832: if (required == Object.class)
0833: return 100;
0834: switch (primitiveType) {
0835: case Type.STRING:
0836: if (required.isAssignableFrom(StringValue.class))
0837: return 50;
0838: if (required == String.class)
0839: return 51;
0840: if (required == CharSequence.class)
0841: return 51;
0842: return -1;
0843: case Type.DOUBLE:
0844: if (required.isAssignableFrom(DoubleValue.class))
0845: return 50;
0846: if (required == double.class)
0847: return 50;
0848: if (required == Double.class)
0849: return 51;
0850: return -1;
0851: case Type.FLOAT:
0852: if (required.isAssignableFrom(FloatValue.class))
0853: return 50;
0854: if (required == float.class)
0855: return 50;
0856: if (required == Float.class)
0857: return 51;
0858: if (required == double.class)
0859: return 52;
0860: if (required == Double.class)
0861: return 53;
0862: return -1;
0863: case Type.DECIMAL:
0864: if (required.isAssignableFrom(DecimalValue.class))
0865: return 50;
0866: if (required == BigDecimal.class)
0867: return 50;
0868: if (required == double.class)
0869: return 51;
0870: if (required == Double.class)
0871: return 52;
0872: if (required == float.class)
0873: return 53;
0874: if (required == Float.class)
0875: return 54;
0876: return -1;
0877: case Type.INTEGER:
0878: if (required.isAssignableFrom(IntegerValue.class))
0879: return 50;
0880: if (required == BigInteger.class)
0881: return 51;
0882: if (required == BigDecimal.class)
0883: return 52;
0884: if (required == long.class)
0885: return 53;
0886: if (required == Long.class)
0887: return 54;
0888: if (required == int.class)
0889: return 55;
0890: if (required == Integer.class)
0891: return 56;
0892: if (required == short.class)
0893: return 57;
0894: if (required == Short.class)
0895: return 58;
0896: if (required == byte.class)
0897: return 59;
0898: if (required == Byte.class)
0899: return 60;
0900: if (required == double.class)
0901: return 61;
0902: if (required == Double.class)
0903: return 62;
0904: if (required == float.class)
0905: return 63;
0906: if (required == Float.class)
0907: return 64;
0908: return -1;
0909: case Type.BOOLEAN:
0910: if (required.isAssignableFrom(BooleanValue.class))
0911: return 50;
0912: if (required == boolean.class)
0913: return 51;
0914: if (required == Boolean.class)
0915: return 52;
0916: return -1;
0917: case Type.DATE:
0918: case Type.G_DAY:
0919: case Type.G_MONTH_DAY:
0920: case Type.G_MONTH:
0921: case Type.G_YEAR_MONTH:
0922: case Type.G_YEAR:
0923: if (required.isAssignableFrom(DateValue.class))
0924: return 50;
0925: if (required.isAssignableFrom(Date.class))
0926: return 51;
0927: return -1;
0928: case Type.DATE_TIME:
0929: if (required.isAssignableFrom(DateTimeValue.class))
0930: return 50;
0931: if (required.isAssignableFrom(Date.class))
0932: return 51;
0933: return -1;
0934: case Type.TIME:
0935: if (required.isAssignableFrom(TimeValue.class))
0936: return 50;
0937: return -1;
0938: case Type.DURATION:
0939: case Type.YEAR_MONTH_DURATION:
0940: case Type.DAY_TIME_DURATION:
0941: if (required.isAssignableFrom(DurationValue.class))
0942: return 50;
0943: return -1;
0944: case Type.ANY_URI:
0945: if (required.isAssignableFrom(AnyURIValue.class))
0946: return 50;
0947: if (required == URI.class)
0948: return 51;
0949: if (required == URL.class)
0950: return 52;
0951: if (required == String.class)
0952: return 53;
0953: if (required == CharSequence.class)
0954: return 53;
0955: return -1;
0956: case Type.QNAME:
0957: if (required.isAssignableFrom(QNameValue.class))
0958: return 50;
0959: //if (required.isAssignableFrom(QName.class)) return 51;
0960: // TODO: reinstate above line under JDK 1.5
0961: if (required.getClass().getName().equals(
0962: "javax.xml.namespace.QName"))
0963: return 51;
0964: return -1;
0965: case Type.BASE64_BINARY:
0966: if (required.isAssignableFrom(Base64BinaryValue.class))
0967: return 50;
0968: return -1;
0969: case Type.HEX_BINARY:
0970: if (required.isAssignableFrom(HexBinaryValue.class))
0971: return 50;
0972: return -1;
0973: case Type.UNTYPED_ATOMIC:
0974: return 50;
0975: default:
0976: return -1;
0977: }
0978: }
0979:
0980: /**
0981: * Get an external Java class corresponding to a given namespace prefix, if there is
0982: * one.
0983: * @param uri The namespace URI corresponding to the prefix used in the function call.
0984: * @return the Java class name if a suitable class exists, otherwise return null.
0985: */
0986:
0987: private Class getExternalJavaClass(String uri) {
0988:
0989: // First see if an explicit mapping has been registered for this URI
0990:
0991: Class c = (Class) explicitMappings.get(uri);
0992: if (c != null) {
0993: return c;
0994: }
0995:
0996: // Failing that, try to identify a class directly from the URI
0997:
0998: try {
0999:
1000: // support the URN format java:full.class.Name
1001:
1002: if (uri.startsWith("java:")) {
1003: return config.getClass(uri.substring(5), config
1004: .isTraceExternalFunctions(), null);
1005: }
1006:
1007: // extract the class name as anything in the URI after the last "/"
1008: // if there is one, or the whole class name otherwise
1009:
1010: int slash = uri.lastIndexOf('/');
1011: if (slash < 0) {
1012: return config.getClass(uri, config
1013: .isTraceExternalFunctions(), null);
1014: } else if (slash == uri.length() - 1) {
1015: return null;
1016: } else {
1017: return config.getClass(uri.substring(slash + 1), config
1018: .isTraceExternalFunctions(), null);
1019: }
1020: } catch (XPathException err) {
1021: return null;
1022: }
1023: }
1024:
1025: /**
1026: * Inner class representing an unresolved extension function call. This arises when there is insufficient
1027: * static type information available at the time the function call is parsed to determine which of several
1028: * candidate Java methods to invoke. The function call cannot be executed; it must be resolved to an
1029: * actual Java method during the analysis phase.
1030: */
1031:
1032: private class UnresolvedExtensionFunction extends
1033: CompileTimeFunction {
1034:
1035: List candidateMethods;
1036: int nameCode;
1037: Class theClass;
1038:
1039: public UnresolvedExtensionFunction(int nameCode,
1040: Class theClass, List candidateMethods,
1041: Expression[] staticArgs) {
1042: setArguments(staticArgs);
1043: this .nameCode = nameCode;
1044: this .theClass = theClass;
1045: this .candidateMethods = candidateMethods;
1046: }
1047:
1048: /**
1049: * Type-check the expression.
1050: */
1051:
1052: public Expression typeCheck(StaticContext env,
1053: ItemType contextItemType) throws XPathException {
1054: for (int i = 0; i < argument.length; i++) {
1055: Expression exp = argument[i].typeCheck(env,
1056: contextItemType);
1057: if (exp != argument[i]) {
1058: adoptChildExpression(exp);
1059: argument[i] = exp;
1060: }
1061: }
1062: AccessibleObject method = getBestFit(candidateMethods,
1063: argument, theClass);
1064: if (method == null) {
1065: StaticError err = new StaticError(
1066: "There is more than one method matching the function call "
1067: + config.getNamePool().getDisplayName(
1068: nameCode)
1069: + ", and there is insufficient type information to determine which one should be used");
1070: err.setLocator(this );
1071: throw err;
1072: } else {
1073: ExtensionFunctionFactory factory = config
1074: .getExtensionFunctionFactory();
1075: return factory.makeExtensionFunctionCall(nameCode,
1076: theClass, method, argument);
1077: }
1078: }
1079: }
1080:
1081: /**
1082: * This method creates a copy of a FunctionLibrary: if the original FunctionLibrary allows
1083: * new functions to be added, then additions to this copy will not affect the original, or
1084: * vice versa.
1085: *
1086: * @return a copy of this function library. This must be an instance of the original class.
1087: */
1088:
1089: public FunctionLibrary copy() {
1090: JavaExtensionLibrary jel = new JavaExtensionLibrary(config);
1091: jel.explicitMappings = new HashMap(explicitMappings);
1092: jel.diag = diag;
1093: return jel;
1094: }
1095:
1096: }
1097:
1098: //
1099: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
1100: // you may not use this file except in compliance with the License. You may obtain a copy of the
1101: // License at http://www.mozilla.org/MPL/
1102: //
1103: // Software distributed under the License is distributed on an "AS IS" basis,
1104: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
1105: // See the License for the specific language governing rights and limitations under the License.
1106: //
1107: // The Original Code is: all this file.
1108: //
1109: // The Initial Developer of the Original Code is Michael H. Kay.
1110: //
1111: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
1112: //
1113: // Contributor(s): none.
1114: //
|