1. /*
  2. * The Apache Software License, Version 1.1
  3. *
  4. *
  5. * Copyright (c) 1999 The Apache Software Foundation. All rights
  6. * reserved.
  7. *
  8. * Redistribution and use in source and binary forms, with or without
  9. * modification, are permitted provided that the following conditions
  10. * are met:
  11. *
  12. * 1. Redistributions of source code must retain the above copyright
  13. * notice, this list of conditions and the following disclaimer.
  14. *
  15. * 2. Redistributions in binary form must reproduce the above copyright
  16. * notice, this list of conditions and the following disclaimer in
  17. * the documentation and/or other materials provided with the
  18. * distribution.
  19. *
  20. * 3. The end-user documentation included with the redistribution,
  21. * if any, must include the following acknowledgment:
  22. * "This product includes software developed by the
  23. * Apache Software Foundation (http://www.apache.org/)."
  24. * Alternately, this acknowledgment may appear in the software itself,
  25. * if and wherever such third-party acknowledgments normally appear.
  26. *
  27. * 4. The names "Xalan" and "Apache Software Foundation" must
  28. * not be used to endorse or promote products derived from this
  29. * software without prior written permission. For written
  30. * permission, please contact apache@apache.org.
  31. *
  32. * 5. Products derived from this software may not be called "Apache",
  33. * nor may "Apache" appear in their name, without prior written
  34. * permission of the Apache Software Foundation.
  35. *
  36. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  37. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  38. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  39. * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  40. * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  41. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  42. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  43. * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  44. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  45. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  46. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  47. * SUCH DAMAGE.
  48. * ====================================================================
  49. *
  50. * This software consists of voluntary contributions made by many
  51. * individuals on behalf of the Apache Software Foundation and was
  52. * originally based on software copyright (c) 1999, Lotus
  53. * Development Corporation., http://www.lotus.com. For more
  54. * information on the Apache Software Foundation, please see
  55. * <http://www.apache.org/>.
  56. */
  57. package org.apache.xalan.extensions;
  58. import java.lang.reflect.Method;
  59. import java.lang.reflect.Constructor;
  60. import java.lang.reflect.Modifier;
  61. import org.w3c.dom.Node;
  62. import org.w3c.dom.NodeList;
  63. import org.w3c.dom.traversal.NodeIterator;
  64. import org.apache.xpath.objects.XObject;
  65. import org.apache.xpath.objects.XString;
  66. import org.apache.xpath.objects.XRTreeFrag;
  67. import org.apache.xml.dtm.*;
  68. import org.apache.xml.dtm.ref.DTMNodeIterator;
  69. import org.apache.xml.dtm.ref.DTMNodeList;
  70. import org.apache.xml.dtm.ref.DTMNodeProxy;
  71. import org.apache.xalan.res.XSLMessages;
  72. import org.apache.xalan.res.XSLTErrorResources;
  73. import javax.xml.transform.TransformerException;
  74. /**
  75. * Utility class to help resolve method overloading with Xalan XSLT
  76. * argument types.
  77. */
  78. public class MethodResolver
  79. {
  80. /**
  81. * Specifies a search for static methods only.
  82. */
  83. public static final int STATIC_ONLY = 1;
  84. /**
  85. * Specifies a search for instance methods only.
  86. */
  87. public static final int INSTANCE_ONLY = 2;
  88. /**
  89. * Specifies a search for both static and instance methods.
  90. */
  91. public static final int STATIC_AND_INSTANCE = 3;
  92. /**
  93. * Specifies a Dynamic method search. If the method being
  94. * evaluated is a static method, all arguments are used.
  95. * Otherwise, it is an instance method and only arguments
  96. * beginning with the second argument are used.
  97. */
  98. public static final int DYNAMIC = 4;
  99. /**
  100. * Given a class, figure out the resolution of
  101. * the Java Constructor from the XSLT argument types, and perform the
  102. * conversion of the arguments.
  103. * @param classObj the Class of the object to be constructed.
  104. * @param argsIn An array of XSLT/XPath arguments.
  105. * @param argsOut An array of the exact size as argsIn, which will be
  106. * populated with converted arguments if a suitable method is found.
  107. * @return A constructor that will work with the argsOut array.
  108. * @throws TransformerException may be thrown for Xalan conversion
  109. * exceptions.
  110. */
  111. public static Constructor getConstructor(Class classObj,
  112. Object[] argsIn,
  113. Object[][] argsOut,
  114. ExpressionContext exprContext)
  115. throws NoSuchMethodException,
  116. SecurityException,
  117. TransformerException
  118. {
  119. Constructor bestConstructor = null;
  120. Class[] bestParamTypes = null;
  121. Constructor[] constructors = classObj.getConstructors();
  122. int nMethods = constructors.length;
  123. int bestScore = Integer.MAX_VALUE;
  124. int bestScoreCount = 0;
  125. for(int i = 0; i < nMethods; i++)
  126. {
  127. Constructor ctor = constructors[i];
  128. Class[] paramTypes = ctor.getParameterTypes();
  129. int numberMethodParams = paramTypes.length;
  130. int paramStart = 0;
  131. boolean isFirstExpressionContext = false;
  132. int scoreStart;
  133. // System.out.println("numberMethodParams: "+numberMethodParams);
  134. // System.out.println("argsIn.length: "+argsIn.length);
  135. // System.out.println("exprContext: "+exprContext);
  136. if(numberMethodParams == (argsIn.length+1))
  137. {
  138. Class javaClass = paramTypes[0];
  139. // System.out.println("first javaClass: "+javaClass.getName());
  140. if(ExpressionContext.class.isAssignableFrom(javaClass))
  141. {
  142. isFirstExpressionContext = true;
  143. scoreStart = 0;
  144. paramStart++;
  145. // System.out.println("Incrementing paramStart: "+paramStart);
  146. }
  147. else
  148. continue;
  149. }
  150. else
  151. scoreStart = 1000;
  152. if(argsIn.length == (numberMethodParams - paramStart))
  153. {
  154. // then we have our candidate.
  155. int score = scoreMatch(paramTypes, paramStart, argsIn, scoreStart);
  156. // System.out.println("score: "+score);
  157. if(-1 == score)
  158. continue;
  159. if(score < bestScore)
  160. {
  161. // System.out.println("Assigning best ctor: "+ctor);
  162. bestConstructor = ctor;
  163. bestParamTypes = paramTypes;
  164. bestScore = score;
  165. bestScoreCount = 1;
  166. }
  167. else if (score == bestScore)
  168. bestScoreCount++;
  169. }
  170. }
  171. if(null == bestConstructor)
  172. {
  173. throw new NoSuchMethodException(errString("function", "constructor", classObj,
  174. "", 0, argsIn));
  175. }
  176. /*** This is commented out until we can do a better object -> object scoring
  177. else if (bestScoreCount > 1)
  178. throw new TransformerException(XSLMessages.createMessage(XSLTErrorResources.ER_MORE_MATCH_CONSTRUCTOR, new Object[]{classObj.getName()})); //"More than one best match for constructor for "
  179. + classObj.getName());
  180. ***/
  181. else
  182. convertParams(argsIn, argsOut, bestParamTypes, exprContext);
  183. return bestConstructor;
  184. }
  185. /**
  186. * Given the name of a method, figure out the resolution of
  187. * the Java Method from the XSLT argument types, and perform the
  188. * conversion of the arguments.
  189. * @param classObj The Class of the object that should have the method.
  190. * @param name The name of the method to be invoked.
  191. * @param argsIn An array of XSLT/XPath arguments.
  192. * @param argsOut An array of the exact size as argsIn, which will be
  193. * populated with converted arguments if a suitable method is found.
  194. * @return A method that will work with the argsOut array.
  195. * @throws TransformerException may be thrown for Xalan conversion
  196. * exceptions.
  197. */
  198. public static Method getMethod(Class classObj,
  199. String name,
  200. Object[] argsIn,
  201. Object[][] argsOut,
  202. ExpressionContext exprContext,
  203. int searchMethod)
  204. throws NoSuchMethodException,
  205. SecurityException,
  206. TransformerException
  207. {
  208. // System.out.println("---> Looking for method: "+name);
  209. // System.out.println("---> classObj: "+classObj);
  210. if (name.indexOf("-")>0)
  211. name = replaceDash(name);
  212. Method bestMethod = null;
  213. Class[] bestParamTypes = null;
  214. Method[] methods = classObj.getMethods();
  215. int nMethods = methods.length;
  216. int bestScore = Integer.MAX_VALUE;
  217. int bestScoreCount = 0;
  218. boolean isStatic;
  219. for(int i = 0; i < nMethods; i++)
  220. {
  221. Method method = methods[i];
  222. // System.out.println("looking at method: "+method);
  223. int xsltParamStart = 0;
  224. if(method.getName().equals(name))
  225. {
  226. isStatic = Modifier.isStatic(method.getModifiers());
  227. switch(searchMethod)
  228. {
  229. case STATIC_ONLY:
  230. if (!isStatic)
  231. {
  232. continue;
  233. }
  234. break;
  235. case INSTANCE_ONLY:
  236. if (isStatic)
  237. {
  238. continue;
  239. }
  240. break;
  241. case STATIC_AND_INSTANCE:
  242. break;
  243. case DYNAMIC:
  244. if (!isStatic)
  245. xsltParamStart = 1;
  246. }
  247. int javaParamStart = 0;
  248. Class[] paramTypes = method.getParameterTypes();
  249. int numberMethodParams = paramTypes.length;
  250. boolean isFirstExpressionContext = false;
  251. int scoreStart;
  252. // System.out.println("numberMethodParams: "+numberMethodParams);
  253. // System.out.println("argsIn.length: "+argsIn.length);
  254. // System.out.println("exprContext: "+exprContext);
  255. int argsLen = (null != argsIn) ? argsIn.length : 0;
  256. if(numberMethodParams == (argsLen-xsltParamStart+1))
  257. {
  258. Class javaClass = paramTypes[0];
  259. if(ExpressionContext.class.isAssignableFrom(javaClass))
  260. {
  261. isFirstExpressionContext = true;
  262. scoreStart = 0;
  263. javaParamStart++;
  264. }
  265. else
  266. {
  267. continue;
  268. }
  269. }
  270. else
  271. scoreStart = 1000;
  272. if((argsLen - xsltParamStart) == (numberMethodParams - javaParamStart))
  273. {
  274. // then we have our candidate.
  275. int score = scoreMatch(paramTypes, javaParamStart, argsIn, scoreStart);
  276. // System.out.println("score: "+score);
  277. if(-1 == score)
  278. continue;
  279. if(score < bestScore)
  280. {
  281. // System.out.println("Assigning best method: "+method);
  282. bestMethod = method;
  283. bestParamTypes = paramTypes;
  284. bestScore = score;
  285. bestScoreCount = 1;
  286. }
  287. else if (score == bestScore)
  288. bestScoreCount++;
  289. }
  290. }
  291. }
  292. if (null == bestMethod)
  293. {
  294. throw new NoSuchMethodException(errString("function", "method", classObj,
  295. name, searchMethod, argsIn));
  296. }
  297. /*** This is commented out until we can do a better object -> object scoring
  298. else if (bestScoreCount > 1)
  299. throw new TransformerException(XSLMessages.createMessage(XSLTErrorResources.ER_MORE_MATCH_METHOD, new Object[]{name})); //"More than one best match for method " + name);
  300. ***/
  301. else
  302. convertParams(argsIn, argsOut, bestParamTypes, exprContext);
  303. return bestMethod;
  304. }
  305. /**
  306. * To support EXSLT extensions, convert names with dash to allowable Java names:
  307. * e.g., convert abc-xyz to abcXyz.
  308. * Note: dashes only appear in middle of an EXSLT function or element name.
  309. */
  310. private static String replaceDash(String name)
  311. {
  312. char dash = '-';
  313. StringBuffer buff = new StringBuffer("");
  314. for (int i=0; i<name.length(); i++)
  315. {
  316. if (name.charAt(i) == dash)
  317. {}
  318. else if (i > 0 && name.charAt(i-1) == dash)
  319. buff.append(Character.toUpperCase(name.charAt(i)));
  320. else
  321. buff.append(name.charAt(i));
  322. }
  323. return buff.toString();
  324. }
  325. /**
  326. * Given the name of a method, figure out the resolution of
  327. * the Java Method
  328. * @param classObj The Class of the object that should have the method.
  329. * @param name The name of the method to be invoked.
  330. * @return A method that will work to be called as an element.
  331. * @throws TransformerException may be thrown for Xalan conversion
  332. * exceptions.
  333. */
  334. public static Method getElementMethod(Class classObj,
  335. String name)
  336. throws NoSuchMethodException,
  337. SecurityException,
  338. TransformerException
  339. {
  340. // System.out.println("---> Looking for element method: "+name);
  341. // System.out.println("---> classObj: "+classObj);
  342. Method bestMethod = null;
  343. Method[] methods = classObj.getMethods();
  344. int nMethods = methods.length;
  345. int bestScoreCount = 0;
  346. for(int i = 0; i < nMethods; i++)
  347. {
  348. Method method = methods[i];
  349. // System.out.println("looking at method: "+method);
  350. if(method.getName().equals(name))
  351. {
  352. Class[] paramTypes = method.getParameterTypes();
  353. if ( (paramTypes.length == 2)
  354. && paramTypes[1].isAssignableFrom(org.apache.xalan.templates.ElemExtensionCall.class)
  355. && paramTypes[0].isAssignableFrom(org.apache.xalan.extensions.XSLProcessorContext.class) )
  356. {
  357. if ( ++bestScoreCount == 1 )
  358. bestMethod = method;
  359. else
  360. break;
  361. }
  362. }
  363. }
  364. if (null == bestMethod)
  365. {
  366. throw new NoSuchMethodException(errString("element", "method", classObj,
  367. name, 0, null));
  368. }
  369. else if (bestScoreCount > 1)
  370. throw new TransformerException(XSLMessages.createMessage(XSLTErrorResources.ER_MORE_MATCH_ELEMENT, new Object[]{name})); //"More than one best match for element method " + name);
  371. return bestMethod;
  372. }
  373. /**
  374. * Convert a set of parameters based on a set of paramTypes.
  375. * @param argsIn An array of XSLT/XPath arguments.
  376. * @param argsOut An array of the exact size as argsIn, which will be
  377. * populated with converted arguments.
  378. * @param paramTypes An array of class objects, of the exact same
  379. * size as argsIn and argsOut.
  380. * @throws TransformerException may be thrown for Xalan conversion
  381. * exceptions.
  382. */
  383. public static void convertParams(Object[] argsIn,
  384. Object[][] argsOut, Class[] paramTypes,
  385. ExpressionContext exprContext)
  386. throws javax.xml.transform.TransformerException
  387. {
  388. // System.out.println("In convertParams");
  389. if (paramTypes == null)
  390. argsOut[0] = null;
  391. else
  392. {
  393. int nParams = paramTypes.length;
  394. argsOut[0] = new Object[nParams];
  395. int paramIndex = 0;
  396. if((nParams > 0)
  397. && ExpressionContext.class.isAssignableFrom(paramTypes[0]))
  398. {
  399. argsOut[0][0] = exprContext;
  400. // System.out.println("Incrementing paramIndex in convertParams: "+paramIndex);
  401. paramIndex++;
  402. }
  403. if (argsIn != null)
  404. {
  405. for(int i = argsIn.length - nParams + paramIndex ; paramIndex < nParams; i++, paramIndex++)
  406. {
  407. // System.out.println("paramTypes[i]: "+paramTypes[i]);
  408. argsOut[0][paramIndex] = convert(argsIn[i], paramTypes[paramIndex]);
  409. }
  410. }
  411. }
  412. }
  413. /**
  414. * Simple class to hold information about allowed conversions
  415. * and their relative scores, for use by the table below.
  416. */
  417. static class ConversionInfo
  418. {
  419. ConversionInfo(Class cl, int score)
  420. {
  421. m_class = cl;
  422. m_score = score;
  423. }
  424. Class m_class; // Java class to convert to.
  425. int m_score; // Match score, closer to zero is more matched.
  426. }
  427. private static final int SCOREBASE=1;
  428. /**
  429. * Specification of conversions from XSLT type CLASS_UNKNOWN
  430. * (i.e. some unknown Java object) to allowed Java types.
  431. */
  432. static ConversionInfo[] m_javaObjConversions = {
  433. new ConversionInfo(Double.TYPE, 11),
  434. new ConversionInfo(Float.TYPE, 12),
  435. new ConversionInfo(Long.TYPE, 13),
  436. new ConversionInfo(Integer.TYPE, 14),
  437. new ConversionInfo(Short.TYPE, 15),
  438. new ConversionInfo(Character.TYPE, 16),
  439. new ConversionInfo(Byte.TYPE, 17),
  440. new ConversionInfo(java.lang.String.class, 18)
  441. };
  442. /**
  443. * Specification of conversions from XSLT type CLASS_BOOLEAN
  444. * to allowed Java types.
  445. */
  446. static ConversionInfo[] m_booleanConversions = {
  447. new ConversionInfo(Boolean.TYPE, 0),
  448. new ConversionInfo(java.lang.Boolean.class, 1),
  449. new ConversionInfo(java.lang.Object.class, 2),
  450. new ConversionInfo(java.lang.String.class, 3)
  451. };
  452. /**
  453. * Specification of conversions from XSLT type CLASS_NUMBER
  454. * to allowed Java types.
  455. */
  456. static ConversionInfo[] m_numberConversions = {
  457. new ConversionInfo(Double.TYPE, 0),
  458. new ConversionInfo(java.lang.Double.class, 1),
  459. new ConversionInfo(Float.TYPE, 3),
  460. new ConversionInfo(Long.TYPE, 4),
  461. new ConversionInfo(Integer.TYPE, 5),
  462. new ConversionInfo(Short.TYPE, 6),
  463. new ConversionInfo(Character.TYPE, 7),
  464. new ConversionInfo(Byte.TYPE, 8),
  465. new ConversionInfo(Boolean.TYPE, 9),
  466. new ConversionInfo(java.lang.String.class, 10),
  467. new ConversionInfo(java.lang.Object.class, 11)
  468. };
  469. /**
  470. * Specification of conversions from XSLT type CLASS_STRING
  471. * to allowed Java types.
  472. */
  473. static ConversionInfo[] m_stringConversions = {
  474. new ConversionInfo(java.lang.String.class, 0),
  475. new ConversionInfo(java.lang.Object.class, 1),
  476. new ConversionInfo(Character.TYPE, 2),
  477. new ConversionInfo(Double.TYPE, 3),
  478. new ConversionInfo(Float.TYPE, 3),
  479. new ConversionInfo(Long.TYPE, 3),
  480. new ConversionInfo(Integer.TYPE, 3),
  481. new ConversionInfo(Short.TYPE, 3),
  482. new ConversionInfo(Byte.TYPE, 3),
  483. new ConversionInfo(Boolean.TYPE, 4)
  484. };
  485. /**
  486. * Specification of conversions from XSLT type CLASS_RTREEFRAG
  487. * to allowed Java types.
  488. */
  489. static ConversionInfo[] m_rtfConversions = {
  490. new ConversionInfo(org.w3c.dom.traversal.NodeIterator.class, 0),
  491. new ConversionInfo(org.w3c.dom.NodeList.class, 1),
  492. new ConversionInfo(org.w3c.dom.Node.class, 2),
  493. new ConversionInfo(java.lang.String.class, 3),
  494. new ConversionInfo(java.lang.Object.class, 5),
  495. new ConversionInfo(Character.TYPE, 6),
  496. new ConversionInfo(Double.TYPE, 7),
  497. new ConversionInfo(Float.TYPE, 7),
  498. new ConversionInfo(Long.TYPE, 7),
  499. new ConversionInfo(Integer.TYPE, 7),
  500. new ConversionInfo(Short.TYPE, 7),
  501. new ConversionInfo(Byte.TYPE, 7),
  502. new ConversionInfo(Boolean.TYPE, 8)
  503. };
  504. /**
  505. * Specification of conversions from XSLT type CLASS_NODESET
  506. * to allowed Java types. (This is the same as for CLASS_RTREEFRAG)
  507. */
  508. static ConversionInfo[] m_nodesetConversions = {
  509. new ConversionInfo(org.w3c.dom.traversal.NodeIterator.class, 0),
  510. new ConversionInfo(org.w3c.dom.NodeList.class, 1),
  511. new ConversionInfo(org.w3c.dom.Node.class, 2),
  512. new ConversionInfo(java.lang.String.class, 3),
  513. new ConversionInfo(java.lang.Object.class, 5),
  514. new ConversionInfo(Character.TYPE, 6),
  515. new ConversionInfo(Double.TYPE, 7),
  516. new ConversionInfo(Float.TYPE, 7),
  517. new ConversionInfo(Long.TYPE, 7),
  518. new ConversionInfo(Integer.TYPE, 7),
  519. new ConversionInfo(Short.TYPE, 7),
  520. new ConversionInfo(Byte.TYPE, 7),
  521. new ConversionInfo(Boolean.TYPE, 8)
  522. };
  523. /**
  524. * Order is significant in the list below, based on
  525. * XObject.CLASS_XXX values.
  526. */
  527. static ConversionInfo[][] m_conversions =
  528. {
  529. m_javaObjConversions, // CLASS_UNKNOWN = 0;
  530. m_booleanConversions, // CLASS_BOOLEAN = 1;
  531. m_numberConversions, // CLASS_NUMBER = 2;
  532. m_stringConversions, // CLASS_STRING = 3;
  533. m_nodesetConversions, // CLASS_NODESET = 4;
  534. m_rtfConversions // CLASS_RTREEFRAG = 5;
  535. };
  536. /**
  537. * Score the conversion of a set of XSLT arguments to a
  538. * given set of Java parameters.
  539. * If any invocations of this function for a method with
  540. * the same name return the same positive value, then a conflict
  541. * has occured, and an error should be signaled.
  542. * @param javaParamTypes Must be filled with valid class names, and
  543. * of the same length as xsltArgs.
  544. * @param xsltArgs Must be filled with valid object instances, and
  545. * of the same length as javeParamTypes.
  546. * @return -1 for no allowed conversion, or a positive score
  547. * that is closer to zero for more preferred, or further from
  548. * zero for less preferred.
  549. */
  550. public static int scoreMatch(Class[] javaParamTypes, int javaParamsStart,
  551. Object[] xsltArgs, int score)
  552. {
  553. if ((xsltArgs == null) || (javaParamTypes == null))
  554. return score;
  555. int nParams = xsltArgs.length;
  556. for(int i = nParams - javaParamTypes.length + javaParamsStart, javaParamTypesIndex = javaParamsStart;
  557. i < nParams;
  558. i++, javaParamTypesIndex++)
  559. {
  560. Object xsltObj = xsltArgs[i];
  561. int xsltClassType = (xsltObj instanceof XObject)
  562. ? ((XObject)xsltObj).getType()
  563. : XObject.CLASS_UNKNOWN;
  564. Class javaClass = javaParamTypes[javaParamTypesIndex];
  565. // System.out.println("Checking xslt: "+xsltObj.getClass().getName()+
  566. // " against java: "+javaClass.getName());
  567. if(xsltClassType == XObject.CLASS_NULL)
  568. {
  569. // In Xalan I have objects of CLASS_NULL, though I'm not
  570. // sure they're used any more. For now, do something funky.
  571. if(!javaClass.isPrimitive())
  572. {
  573. // Then assume that a null can be used, but give it a low score.
  574. score += 10;
  575. continue;
  576. }
  577. else
  578. return -1; // no match.
  579. }
  580. ConversionInfo[] convInfo = m_conversions[xsltClassType];
  581. int nConversions = convInfo.length;
  582. int k;
  583. for(k = 0; k < nConversions; k++)
  584. {
  585. ConversionInfo cinfo = convInfo[k];
  586. if(javaClass.isAssignableFrom(cinfo.m_class))
  587. {
  588. score += cinfo.m_score;
  589. break; // from k loop
  590. }
  591. }
  592. if (k == nConversions)
  593. {
  594. // If we get here, we haven't made a match on this parameter using
  595. // the ConversionInfo array. We now try to handle the object -> object
  596. // mapping which we can't handle through the array mechanism. To do this,
  597. // we must determine the class of the argument passed from the stylesheet.
  598. // If we were passed a subclass of XObject, representing one of the actual
  599. // XSLT types, and we are here, we reject this extension method as a candidate
  600. // because a match should have been made using the ConversionInfo array. If we
  601. // were passed an XObject that encapsulates a non-XSLT type or we
  602. // were passed a non-XSLT type directly, we continue.
  603. // The current implementation (contributed by Kelly Campbell <camk@channelpoint.com>)
  604. // checks to see if we were passed an XObject from the XSLT stylesheet. If not,
  605. // we use the class of the object that was passed and make sure that it will
  606. // map to the class type of the parameter in the extension function.
  607. // If we were passed an XObject, we attempt to get the class of the actual
  608. // object encapsulated inside the XObject. If the encapsulated object is null,
  609. // we judge this method as a match but give it a low score.
  610. // If the encapsulated object is not null, we use its type to determine
  611. // whether this java method is a valid match for this extension function call.
  612. // This approach eliminates the NullPointerException in the earlier implementation
  613. // that resulted from passing an XObject encapsulating the null java object.
  614. // TODO: This needs to be improved to assign relative scores to subclasses,
  615. // etc.
  616. if (XObject.CLASS_UNKNOWN == xsltClassType)
  617. {
  618. Class realClass = null;
  619. if (xsltObj instanceof XObject)
  620. {
  621. Object realObj = ((XObject) xsltObj).object();
  622. if (null != realObj)
  623. {
  624. realClass = realObj.getClass();
  625. }
  626. else
  627. {
  628. // do the same as if we were passed XObject.CLASS_NULL
  629. score += 10;
  630. continue;
  631. }
  632. }
  633. else
  634. {
  635. realClass = xsltObj.getClass();
  636. }
  637. if (javaClass.isAssignableFrom(realClass))
  638. {
  639. score += 0; // TODO: To be assigned based on subclass "distance"
  640. }
  641. else
  642. return -1;
  643. }
  644. else
  645. return -1;
  646. }
  647. }
  648. return score;
  649. }
  650. /**
  651. * Convert the given XSLT object to an object of
  652. * the given class.
  653. * @param xsltObj The XSLT object that needs conversion.
  654. * @param javaClass The type of object to convert to.
  655. * @returns An object suitable for passing to the Method.invoke
  656. * function in the args array, which may be null in some cases.
  657. * @throws TransformerException may be thrown for Xalan conversion
  658. * exceptions.
  659. */
  660. static Object convert(Object xsltObj, Class javaClass)
  661. throws javax.xml.transform.TransformerException
  662. {
  663. if(xsltObj instanceof XObject)
  664. {
  665. XObject xobj = ((XObject)xsltObj);
  666. int xsltClassType = xobj.getType();
  667. switch(xsltClassType)
  668. {
  669. case XObject.CLASS_NULL:
  670. return null;
  671. case XObject.CLASS_BOOLEAN:
  672. {
  673. if(javaClass == java.lang.String.class)
  674. return xobj.str();
  675. else
  676. return new Boolean(xobj.bool());
  677. }
  678. // break; Unreachable
  679. case XObject.CLASS_NUMBER:
  680. {
  681. if(javaClass == java.lang.String.class)
  682. return xobj.str();
  683. else if(javaClass == Boolean.TYPE)
  684. return new Boolean(xobj.bool());
  685. else
  686. {
  687. return convertDoubleToNumber(xobj.num(), javaClass);
  688. }
  689. }
  690. // break; Unreachable
  691. case XObject.CLASS_STRING:
  692. {
  693. if((javaClass == java.lang.String.class) ||
  694. (javaClass == java.lang.Object.class))
  695. return xobj.str();
  696. else if(javaClass == Character.TYPE)
  697. {
  698. String str = xobj.str();
  699. if(str.length() > 0)
  700. return new Character(str.charAt(0));
  701. else
  702. return null; // ??
  703. }
  704. else if(javaClass == Boolean.TYPE)
  705. return new Boolean(xobj.bool());
  706. else
  707. {
  708. return convertDoubleToNumber(xobj.num(), javaClass);
  709. }
  710. }
  711. // break; Unreachable
  712. case XObject.CLASS_RTREEFRAG:
  713. {
  714. // GLP: I don't see the reason for the isAssignableFrom call
  715. // instead of an == test as is used everywhere else.
  716. // Besides, if the javaClass is a subclass of NodeIterator
  717. // the condition will be true and we'll create a NodeIterator
  718. // which may not match the javaClass, causing a RuntimeException.
  719. // if((NodeIterator.class.isAssignableFrom(javaClass)) ||
  720. if ( (javaClass == NodeIterator.class) ||
  721. (javaClass == java.lang.Object.class) )
  722. {
  723. DTMIterator dtmIter = ((XRTreeFrag) xobj).asNodeIterator();
  724. return new DTMNodeIterator(dtmIter);
  725. }
  726. else if (javaClass == NodeList.class)
  727. {
  728. return ((XRTreeFrag) xobj).convertToNodeset();
  729. }
  730. // Same comment as above
  731. // else if(Node.class.isAssignableFrom(javaClass))
  732. else if(javaClass == Node.class)
  733. {
  734. DTMIterator iter = ((XRTreeFrag) xobj).asNodeIterator();
  735. int rootHandle = iter.nextNode();
  736. DTM dtm = iter.getDTM(rootHandle);
  737. return dtm.getNode(dtm.getFirstChild(rootHandle));
  738. }
  739. else if(javaClass == java.lang.String.class)
  740. {
  741. return xobj.str();
  742. }
  743. else if(javaClass == Boolean.TYPE)
  744. {
  745. return new Boolean(xobj.bool());
  746. }
  747. else if(javaClass.isPrimitive())
  748. {
  749. return convertDoubleToNumber(xobj.num(), javaClass);
  750. }
  751. else
  752. {
  753. DTMIterator iter = ((XRTreeFrag) xobj).asNodeIterator();
  754. int rootHandle = iter.nextNode();
  755. DTM dtm = iter.getDTM(rootHandle);
  756. Node child = dtm.getNode(dtm.getFirstChild(rootHandle));
  757. if(javaClass.isAssignableFrom(child.getClass()))
  758. return child;
  759. else
  760. return null;
  761. }
  762. }
  763. // break; Unreachable
  764. case XObject.CLASS_NODESET:
  765. {
  766. // GLP: I don't see the reason for the isAssignableFrom call
  767. // instead of an == test as is used everywhere else.
  768. // Besides, if the javaClass is a subclass of NodeIterator
  769. // the condition will be true and we'll create a NodeIterator
  770. // which may not match the javaClass, causing a RuntimeException.
  771. // if((NodeIterator.class.isAssignableFrom(javaClass)) ||
  772. if ( (javaClass == NodeIterator.class) ||
  773. (javaClass == java.lang.Object.class) )
  774. {
  775. return xobj.nodeset();
  776. }
  777. // Same comment as above
  778. // else if(NodeList.class.isAssignableFrom(javaClass))
  779. else if(javaClass == NodeList.class)
  780. {
  781. return xobj.nodelist();
  782. }
  783. // Same comment as above
  784. // else if(Node.class.isAssignableFrom(javaClass))
  785. else if(javaClass == Node.class)
  786. {
  787. // Xalan ensures that iter() always returns an
  788. // iterator positioned at the beginning.
  789. DTMIterator ni = xobj.iter();
  790. int handle = ni.nextNode();
  791. if (handle != DTM.NULL)
  792. return ni.getDTM(handle).getNode(handle); // may be null.
  793. else
  794. return null;
  795. }
  796. else if(javaClass == java.lang.String.class)
  797. {
  798. return xobj.str();
  799. }
  800. else if(javaClass == Boolean.TYPE)
  801. {
  802. return new Boolean(xobj.bool());
  803. }
  804. else if(javaClass.isPrimitive())
  805. {
  806. return convertDoubleToNumber(xobj.num(), javaClass);
  807. }
  808. else
  809. {
  810. DTMIterator iter = xobj.iter();
  811. int childHandle = iter.nextNode();
  812. DTM dtm = iter.getDTM(childHandle);
  813. Node child = dtm.getNode(childHandle);
  814. if(javaClass.isAssignableFrom(child.getClass()))
  815. return child;
  816. else
  817. return null;
  818. }
  819. }
  820. // break; Unreachable
  821. // No default:, fall-through on purpose
  822. } // end switch
  823. xsltObj = xobj.object();
  824. } // end if if(xsltObj instanceof XObject)
  825. // At this point, we have a raw java object, not an XObject.
  826. if (null != xsltObj)
  827. {
  828. if(javaClass == java.lang.String.class)
  829. {
  830. return xsltObj.toString();
  831. }
  832. else if(javaClass.isPrimitive())
  833. {
  834. // Assume a number conversion
  835. XString xstr = new XString(xsltObj.toString());
  836. double num = xstr.num();
  837. return convertDoubleToNumber(num, javaClass);
  838. }
  839. else if(javaClass == java.lang.Class.class)
  840. {
  841. return xsltObj.getClass();
  842. }
  843. else
  844. {
  845. // Just pass the object directly, and hope for the best.
  846. return xsltObj;
  847. }
  848. }
  849. else
  850. {
  851. // Just pass the object directly, and hope for the best.
  852. return xsltObj;
  853. }
  854. }
  855. /**
  856. * Do a standard conversion of a double to the specified type.
  857. * @param num The number to be converted.
  858. * @param javaClass The class type to be converted to.
  859. * @return An object specified by javaClass, or a Double instance.
  860. */
  861. static Object convertDoubleToNumber(double num, Class javaClass)
  862. {
  863. // In the code below, I don't check for NaN, etc., instead
  864. // using the standard Java conversion, as I think we should
  865. // specify. See issue-runtime-errors.
  866. if((javaClass == Double.TYPE) ||
  867. (javaClass == java.lang.Double.class))
  868. return new Double(num);
  869. else if(javaClass == Float.TYPE)
  870. return new Float(num);
  871. else if(javaClass == Long.TYPE)
  872. {
  873. // Use standard Java Narrowing Primitive Conversion
  874. // See http://java.sun.com/docs/books/jls/html/5.doc.html#175672
  875. return new Long((long)num);
  876. }
  877. else if(javaClass == Integer.TYPE)
  878. {
  879. // Use standard Java Narrowing Primitive Conversion
  880. // See http://java.sun.com/docs/books/jls/html/5.doc.html#175672
  881. return new Integer((int)num);
  882. }
  883. else if(javaClass == Short.TYPE)
  884. {
  885. // Use standard Java Narrowing Primitive Conversion
  886. // See http://java.sun.com/docs/books/jls/html/5.doc.html#175672
  887. return new Short((short)num);
  888. }
  889. else if(javaClass == Character.TYPE)
  890. {
  891. // Use standard Java Narrowing Primitive Conversion
  892. // See http://java.sun.com/docs/books/jls/html/5.doc.html#175672
  893. return new Character((char)num);
  894. }
  895. else if(javaClass == Byte.TYPE)
  896. {
  897. // Use standard Java Narrowing Primitive Conversion
  898. // See http://java.sun.com/docs/books/jls/html/5.doc.html#175672
  899. return new Byte((byte)num);
  900. }
  901. else // Some other type of object
  902. {
  903. return new Double(num);
  904. }
  905. }
  906. /**
  907. * Format the message for the NoSuchMethodException containing
  908. * all the information about the method we're looking for.
  909. */
  910. private static String errString(String callType, // "function" or "element"
  911. String searchType, // "method" or "constructor"
  912. Class classObj,
  913. String funcName,
  914. int searchMethod,
  915. Object[] xsltArgs)
  916. {
  917. String resultString = "For extension " + callType
  918. + ", could not find " + searchType + " ";
  919. switch (searchMethod)
  920. {
  921. case STATIC_ONLY:
  922. return resultString + "static " + classObj.getName() + "."
  923. + funcName + "([ExpressionContext,] " + errArgs(xsltArgs, 0) + ").";
  924. case INSTANCE_ONLY:
  925. return resultString + classObj.getName() + "."
  926. + funcName + "([ExpressionContext,] " + errArgs(xsltArgs, 0) + ").";
  927. case STATIC_AND_INSTANCE:
  928. return resultString + classObj.getName() + "." + funcName + "([ExpressionContext,] " + errArgs(xsltArgs, 0) + ").\n"
  929. + "Checked both static and instance methods.";
  930. case DYNAMIC:
  931. return resultString + "static " + classObj.getName() + "." + funcName
  932. + "([ExpressionContext, ]" + errArgs(xsltArgs, 0) + ") nor\n"
  933. + classObj + "." + funcName + "([ExpressionContext,] " + errArgs(xsltArgs, 1) + ").";
  934. default:
  935. if (callType.equals("function")) // must be a constructor
  936. {
  937. return resultString + classObj.getName()
  938. + "([ExpressionContext,] " + errArgs(xsltArgs, 0) + ").";
  939. }
  940. else // must be an element call
  941. {
  942. return resultString + classObj.getName() + "." + funcName
  943. + "(org.apache.xalan.extensions.XSLProcessorContext, "
  944. + "org.apache.xalan.templates.ElemExtensionCall).";
  945. }
  946. }
  947. }
  948. private static String errArgs(Object[] xsltArgs, int startingArg)
  949. {
  950. StringBuffer returnArgs = new StringBuffer();
  951. for (int i = startingArg; i < xsltArgs.length; i++)
  952. {
  953. if (i != startingArg)
  954. returnArgs.append(", ");
  955. if (xsltArgs[i] instanceof XObject)
  956. returnArgs.append(((XObject) xsltArgs[i]).getTypeString());
  957. else
  958. returnArgs.append(xsltArgs[i].getClass().getName());
  959. }
  960. return returnArgs.toString();
  961. }
  962. }