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 ; 16 import java.lang.reflect.*; 17 import java.math.BigDecimal ; 18 import java.math.BigInteger ; 19 import java.net.URI ; 20 import java.net.URL ; 21 import java.util.ArrayList ; 22 import java.util.Date ; 23 import java.util.HashMap ; 24 import java.util.List ; 25 26 35 36 public class JavaExtensionLibrary implements FunctionLibrary { 37 38 private Configuration config; 39 40 44 private HashMap explicitMappings = new HashMap (10); 45 46 48 private transient PrintStream diag = System.err; 49 50 54 55 public JavaExtensionLibrary(Configuration config) { 56 this.config = config; 57 setDefaultURIMappings(); 58 } 59 60 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 79 80 public void declareJavaClass(String uri, Class theClass) { 81 explicitMappings.put(uri, theClass); 82 } 83 84 96 97 public boolean isAvailable(int fingerprint, String uri, String local, int arity) { 98 if (!config.isAllowExternalFunctions()) { 99 return false; 100 } 101 Class reqClass; 102 try { 103 reqClass = getExternalJavaClass(uri); 104 if (reqClass == null) { 105 return false; 106 } 107 } catch (Exception err) { 108 return false; 109 } 110 int significantArgs; 111 112 Class theClass = reqClass; 113 114 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 143 String name = toCamelCase(local, false, diag); 144 145 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 [] theParameterTypes = theMethod.getParameterTypes(); 154 boolean isStatic = Modifier.isStatic(theMethod.getModifiers()); 155 156 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 170 if (theParameterTypes.length == significantArgs + 1 && 171 theParameterTypes[0] == XPathContext.class) { 172 return true; 173 } 174 } 175 } 176 } 177 178 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 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 218 219 public Expression bind(int nameCode, String uri, String 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 reqClass; 231 Exception theException = null; 232 ArrayList candidateMethods = new ArrayList (10); 233 Class resultClass = null; 234 235 try { 236 reqClass = getExternalJavaClass(uri); 237 if (reqClass == null) { 238 return null; 239 } 240 } catch (Exception 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 theClass = reqClass; 253 254 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 303 String name = toCamelCase(local, debug, diag); 304 305 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 [] theParameterTypes = theMethod.getParameterTypes(); 336 boolean isStatic = Modifier.isStatic(theMethod.getModifiers()); 337 338 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 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 379 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 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 430 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 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 475 476 private AccessibleObject getBestFit(List candidateMethods, Expression[] args, Class theClass) { 477 boolean debug = config.isTraceExternalFunctions(); 478 int candidates = candidateMethods.size(); 479 480 if (candidates == 1) { 481 return (AccessibleObject) candidateMethods.get(0); 483 484 } else { 485 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 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]) { 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 597 598 private static String toCamelCase(String name, boolean debug, PrintStream 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 632 633 private int[] getConversionPreferences(Expression[] args, AccessibleObject method, Class theClass) { 634 635 Class [] 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 ("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 685 686 private int getConversionPreference(Expression arg, Class required) { 687 ItemType itemType = arg.getItemType(); 688 int cardinality = arg.getCardinality(); 689 if (required == Object .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 } else { 702 return 80; } 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 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 [] NO_PARAMS = new Class [0]; 728 729 730 740 741 protected int atomicConversionPreference(int primitiveType, Class required) { 742 if (required == Object .class) return 100; 743 switch (primitiveType) { 744 case Type.STRING: 745 if (required.isAssignableFrom(StringValue.class)) return 50; 746 if (required == String .class) return 51; 747 if (required == CharSequence .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 .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 .class) return 51; 758 if (required == double.class) return 52; 759 if (required == Double .class) return 53; 760 return -1; 761 case Type.DECIMAL: 762 if (required.isAssignableFrom(DecimalValue.class)) return 50; 763 if (required == BigDecimal .class) return 50; 764 if (required == double.class) return 51; 765 if (required == Double .class) return 52; 766 if (required == float.class) return 53; 767 if (required == Float .class) return 54; 768 return -1; 769 case Type.INTEGER: 770 if (required.isAssignableFrom(IntegerValue.class)) return 50; 771 if (required == BigInteger .class) return 51; 772 if (required == BigDecimal .class) return 52; 773 if (required == long.class) return 53; 774 if (required == Long .class) return 54; 775 if (required == int.class) return 55; 776 if (required == Integer .class) return 56; 777 if (required == short.class) return 57; 778 if (required == Short .class) return 58; 779 if (required == byte.class) return 59; 780 if (required == Byte .class) return 60; 781 if (required == double.class) return 61; 782 if (required == Double .class) return 62; 783 if (required == float.class) return 63; 784 if (required == Float .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 .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 .class)) return 51; 799 return -1; 800 case Type.DATE_TIME: 801 if (required.isAssignableFrom(DateTimeValue.class)) return 50; 802 if (required.isAssignableFrom(Date .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 .class) return 51; 815 if (required == URL .class) return 52; 816 if (required == String .class) return 53; 817 if (required == CharSequence .class) return 53; 818 return -1; 819 case Type.QNAME: 820 if (required.isAssignableFrom(QNameValue.class)) return 50; 821 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 844 845 private Class getExternalJavaClass(String uri) { 846 847 849 Class c = (Class ) explicitMappings.get(uri); 850 if (c != null) { 851 return c; 852 } 853 854 856 try { 857 858 860 if (uri.startsWith("java:")) { 861 return config.getClass(uri.substring(5), config.isTraceExternalFunctions(), null); 862 } 863 864 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 886 887 private class UnresolvedExtensionFunction extends CompileTimeFunction { 888 889 List candidateMethods; 890 int nameCode; 891 Class theClass; 892 893 894 public UnresolvedExtensionFunction(int nameCode, Class theClass, List candidateMethods, Expression[] staticArgs) { 895 setArguments(staticArgs); 896 this.nameCode = nameCode; 897 this.theClass = theClass; 898 this.candidateMethods = candidateMethods; 899 } 900 901 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 934 935 public FunctionLibrary copy() { 936 JavaExtensionLibrary jel = new JavaExtensionLibrary(config); 937 jel.explicitMappings = new HashMap (explicitMappings); 938 jel.diag = diag; 939 return jel; 940 } 941 942 } 943 944 | Popular Tags |