001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018 /*
019 * $Id: MethodResolver.java 1225263 2011-12-28 18:36:36Z mrglavas $
020 */
021 package org.apache.xalan.extensions;
022
023 import java.lang.reflect.Constructor;
024 import java.lang.reflect.Method;
025 import java.lang.reflect.Modifier;
026
027 import javax.xml.transform.TransformerException;
028
029 import org.apache.xalan.res.XSLMessages;
030 import org.apache.xalan.res.XSLTErrorResources;
031 import org.apache.xml.dtm.DTM;
032 import org.apache.xml.dtm.DTMIterator;
033 import org.apache.xml.dtm.ref.DTMNodeIterator;
034 import org.apache.xpath.objects.XObject;
035 import org.apache.xpath.objects.XRTreeFrag;
036 import org.apache.xpath.objects.XString;
037 import org.w3c.dom.Node;
038 import org.w3c.dom.NodeList;
039 import org.w3c.dom.traversal.NodeIterator;
040
041 /**
042 * Utility class to help resolve method overloading with Xalan XSLT
043 * argument types.
044 */
045 public class MethodResolver
046 {
047
048 /**
049 * Specifies a search for static methods only.
050 */
051 public static final int STATIC_ONLY = 1;
052
053 /**
054 * Specifies a search for instance methods only.
055 */
056 public static final int INSTANCE_ONLY = 2;
057
058 /**
059 * Specifies a search for both static and instance methods.
060 */
061 public static final int STATIC_AND_INSTANCE = 3;
062
063 /**
064 * Specifies a Dynamic method search. If the method being
065 * evaluated is a static method, all arguments are used.
066 * Otherwise, it is an instance method and only arguments
067 * beginning with the second argument are used.
068 */
069 public static final int DYNAMIC = 4;
070
071 /**
072 * Given a class, figure out the resolution of
073 * the Java Constructor from the XSLT argument types, and perform the
074 * conversion of the arguments.
075 * @param classObj the Class of the object to be constructed.
076 * @param argsIn An array of XSLT/XPath arguments.
077 * @param argsOut An array of the exact size as argsIn, which will be
078 * populated with converted arguments if a suitable method is found.
079 * @return A constructor that will work with the argsOut array.
080 * @throws TransformerException may be thrown for Xalan conversion
081 * exceptions.
082 */
083 public static Constructor getConstructor(Class classObj,
084 Object[] argsIn,
085 Object[][] argsOut,
086 ExpressionContext exprContext)
087 throws NoSuchMethodException,
088 SecurityException,
089 TransformerException
090 {
091 Constructor bestConstructor = null;
092 Class[] bestParamTypes = null;
093 Constructor[] constructors = classObj.getConstructors();
094 int nMethods = constructors.length;
095 int bestScore = Integer.MAX_VALUE;
096 int bestScoreCount = 0;
097 for(int i = 0; i < nMethods; i++)
098 {
099 Constructor ctor = constructors[i];
100 Class[] paramTypes = ctor.getParameterTypes();
101 int numberMethodParams = paramTypes.length;
102 int paramStart = 0;
103 boolean isFirstExpressionContext = false;
104 int scoreStart;
105 // System.out.println("numberMethodParams: "+numberMethodParams);
106 // System.out.println("argsIn.length: "+argsIn.length);
107 // System.out.println("exprContext: "+exprContext);
108 if(numberMethodParams == (argsIn.length+1))
109 {
110 Class javaClass = paramTypes[0];
111 // System.out.println("first javaClass: "+javaClass.getName());
112 if(ExpressionContext.class.isAssignableFrom(javaClass))
113 {
114 isFirstExpressionContext = true;
115 scoreStart = 0;
116 paramStart++;
117 // System.out.println("Incrementing paramStart: "+paramStart);
118 }
119 else
120 continue;
121 }
122 else
123 scoreStart = 1000;
124
125 if(argsIn.length == (numberMethodParams - paramStart))
126 {
127 // then we have our candidate.
128 int score = scoreMatch(paramTypes, paramStart, argsIn, scoreStart);
129 // System.out.println("score: "+score);
130 if(-1 == score)
131 continue;
132 if(score < bestScore)
133 {
134 // System.out.println("Assigning best ctor: "+ctor);
135 bestConstructor = ctor;
136 bestParamTypes = paramTypes;
137 bestScore = score;
138 bestScoreCount = 1;
139 }
140 else if (score == bestScore)
141 bestScoreCount++;
142 }
143 }
144
145 if(null == bestConstructor)
146 {
147 throw new NoSuchMethodException(errString("function", "constructor", classObj,
148 "", 0, argsIn));
149 }
150 /*** This is commented out until we can do a better object -> object scoring
151 else if (bestScoreCount > 1)
152 throw new TransformerException(XSLMessages.createMessage(XSLTErrorResources.ER_MORE_MATCH_CONSTRUCTOR, new Object[]{classObj.getName()})); //"More than one best match for constructor for "
153 + classObj.getName());
154 ***/
155 else
156 convertParams(argsIn, argsOut, bestParamTypes, exprContext);
157
158 return bestConstructor;
159 }
160
161
162 /**
163 * Given the name of a method, figure out the resolution of
164 * the Java Method from the XSLT argument types, and perform the
165 * conversion of the arguments.
166 * @param classObj The Class of the object that should have the method.
167 * @param name The name of the method to be invoked.
168 * @param argsIn An array of XSLT/XPath arguments.
169 * @param argsOut An array of the exact size as argsIn, which will be
170 * populated with converted arguments if a suitable method is found.
171 * @return A method that will work with the argsOut array.
172 * @throws TransformerException may be thrown for Xalan conversion
173 * exceptions.
174 */
175 public static Method getMethod(Class classObj,
176 String name,
177 Object[] argsIn,
178 Object[][] argsOut,
179 ExpressionContext exprContext,
180 int searchMethod)
181 throws NoSuchMethodException,
182 SecurityException,
183 TransformerException
184 {
185 // System.out.println("---> Looking for method: "+name);
186 // System.out.println("---> classObj: "+classObj);
187 if (name.indexOf("-")>0)
188 name = replaceDash(name);
189 Method bestMethod = null;
190 Class[] bestParamTypes = null;
191 Method[] methods = classObj.getMethods();
192 int nMethods = methods.length;
193 int bestScore = Integer.MAX_VALUE;
194 int bestScoreCount = 0;
195 boolean isStatic;
196 for(int i = 0; i < nMethods; i++)
197 {
198 Method method = methods[i];
199 // System.out.println("looking at method: "+method);
200 int xsltParamStart = 0;
201 if(method.getName().equals(name))
202 {
203 isStatic = Modifier.isStatic(method.getModifiers());
204 switch(searchMethod)
205 {
206 case STATIC_ONLY:
207 if (!isStatic)
208 {
209 continue;
210 }
211 break;
212
213 case INSTANCE_ONLY:
214 if (isStatic)
215 {
216 continue;
217 }
218 break;
219
220 case STATIC_AND_INSTANCE:
221 break;
222
223 case DYNAMIC:
224 if (!isStatic)
225 xsltParamStart = 1;
226 }
227 int javaParamStart = 0;
228 Class[] paramTypes = method.getParameterTypes();
229 int numberMethodParams = paramTypes.length;
230 boolean isFirstExpressionContext = false;
231 int scoreStart;
232 // System.out.println("numberMethodParams: "+numberMethodParams);
233 // System.out.println("argsIn.length: "+argsIn.length);
234 // System.out.println("exprContext: "+exprContext);
235 int argsLen = (null != argsIn) ? argsIn.length : 0;
236 if(numberMethodParams == (argsLen-xsltParamStart+1))
237 {
238 Class javaClass = paramTypes[0];
239 if(ExpressionContext.class.isAssignableFrom(javaClass))
240 {
241 isFirstExpressionContext = true;
242 scoreStart = 0;
243 javaParamStart++;
244 }
245 else
246 {
247 continue;
248 }
249 }
250 else
251 scoreStart = 1000;
252
253 if((argsLen - xsltParamStart) == (numberMethodParams - javaParamStart))
254 {
255 // then we have our candidate.
256 int score = scoreMatch(paramTypes, javaParamStart, argsIn, scoreStart);
257 // System.out.println("score: "+score);
258 if(-1 == score)
259 continue;
260 if(score < bestScore)
261 {
262 // System.out.println("Assigning best method: "+method);
263 bestMethod = method;
264 bestParamTypes = paramTypes;
265 bestScore = score;
266 bestScoreCount = 1;
267 }
268 else if (score == bestScore)
269 bestScoreCount++;
270 }
271 }
272 }
273
274 if (null == bestMethod)
275 {
276 throw new NoSuchMethodException(errString("function", "method", classObj,
277 name, searchMethod, argsIn));
278 }
279 /*** This is commented out until we can do a better object -> object scoring
280 else if (bestScoreCount > 1)
281 throw new TransformerException(XSLMessages.createMessage(XSLTErrorResources.ER_MORE_MATCH_METHOD, new Object[]{name})); //"More than one best match for method " + name);
282 ***/
283 else
284 convertParams(argsIn, argsOut, bestParamTypes, exprContext);
285
286 return bestMethod;
287 }
288
289 /**
290 * To support EXSLT extensions, convert names with dash to allowable Java names:
291 * e.g., convert abc-xyz to abcXyz.
292 * Note: dashes only appear in middle of an EXSLT function or element name.
293 */
294 private static String replaceDash(String name)
295 {
296 char dash = '-';
297 StringBuffer buff = new StringBuffer("");
298 for (int i=0; i<name.length(); i++)
299 {
300 if (name.charAt(i) == dash)
301 {}
302 else if (i > 0 && name.charAt(i-1) == dash)
303 buff.append(Character.toUpperCase(name.charAt(i)));
304 else
305 buff.append(name.charAt(i));
306 }
307 return buff.toString();
308 }
309
310 /**
311 * Given the name of a method, figure out the resolution of
312 * the Java Method
313 * @param classObj The Class of the object that should have the method.
314 * @param name The name of the method to be invoked.
315 * @return A method that will work to be called as an element.
316 * @throws TransformerException may be thrown for Xalan conversion
317 * exceptions.
318 */
319 public static Method getElementMethod(Class classObj,
320 String name)
321 throws NoSuchMethodException,
322 SecurityException,
323 TransformerException
324 {
325 // System.out.println("---> Looking for element method: "+name);
326 // System.out.println("---> classObj: "+classObj);
327 Method bestMethod = null;
328 Method[] methods = classObj.getMethods();
329 int nMethods = methods.length;
330 int bestScoreCount = 0;
331 for(int i = 0; i < nMethods; i++)
332 {
333 Method method = methods[i];
334 // System.out.println("looking at method: "+method);
335 if(method.getName().equals(name))
336 {
337 Class[] paramTypes = method.getParameterTypes();
338 if ( (paramTypes.length == 2)
339 && paramTypes[1].isAssignableFrom(org.apache.xalan.templates.ElemExtensionCall.class)
340 && paramTypes[0].isAssignableFrom(org.apache.xalan.extensions.XSLProcessorContext.class) )
341 {
342 if ( ++bestScoreCount == 1 )
343 bestMethod = method;
344 else
345 break;
346 }
347 }
348 }
349
350 if (null == bestMethod)
351 {
352 throw new NoSuchMethodException(errString("element", "method", classObj,
353 name, 0, null));
354 }
355 else if (bestScoreCount > 1)
356 throw new TransformerException(XSLMessages.createMessage(XSLTErrorResources.ER_MORE_MATCH_ELEMENT, new Object[]{name})); //"More than one best match for element method " + name);
357
358 return bestMethod;
359 }
360
361
362 /**
363 * Convert a set of parameters based on a set of paramTypes.
364 * @param argsIn An array of XSLT/XPath arguments.
365 * @param argsOut An array of the exact size as argsIn, which will be
366 * populated with converted arguments.
367 * @param paramTypes An array of class objects, of the exact same
368 * size as argsIn and argsOut.
369 * @throws TransformerException may be thrown for Xalan conversion
370 * exceptions.
371 */
372 public static void convertParams(Object[] argsIn,
373 Object[][] argsOut, Class[] paramTypes,
374 ExpressionContext exprContext)
375 throws javax.xml.transform.TransformerException
376 {
377 // System.out.println("In convertParams");
378 if (paramTypes == null)
379 argsOut[0] = null;
380 else
381 {
382 int nParams = paramTypes.length;
383 argsOut[0] = new Object[nParams];
384 int paramIndex = 0;
385 if((nParams > 0)
386 && ExpressionContext.class.isAssignableFrom(paramTypes[0]))
387 {
388 argsOut[0][0] = exprContext;
389 // System.out.println("Incrementing paramIndex in convertParams: "+paramIndex);
390 paramIndex++;
391 }
392
393 if (argsIn != null)
394 {
395 for(int i = argsIn.length - nParams + paramIndex ; paramIndex < nParams; i++, paramIndex++)
396 {
397 // System.out.println("paramTypes[i]: "+paramTypes[i]);
398 argsOut[0][paramIndex] = convert(argsIn[i], paramTypes[paramIndex]);
399 }
400 }
401 }
402 }
403
404 /**
405 * Simple class to hold information about allowed conversions
406 * and their relative scores, for use by the table below.
407 */
408 static class ConversionInfo
409 {
410 ConversionInfo(Class cl, int score)
411 {
412 m_class = cl;
413 m_score = score;
414 }
415
416 Class m_class; // Java class to convert to.
417 int m_score; // Match score, closer to zero is more matched.
418 }
419
420 private static final int SCOREBASE=1;
421
422 /**
423 * Specification of conversions from XSLT type CLASS_UNKNOWN
424 * (i.e. some unknown Java object) to allowed Java types.
425 */
426 private final static ConversionInfo[] m_javaObjConversions = {
427 new ConversionInfo(Double.TYPE, 11),
428 new ConversionInfo(Float.TYPE, 12),
429 new ConversionInfo(Long.TYPE, 13),
430 new ConversionInfo(Integer.TYPE, 14),
431 new ConversionInfo(Short.TYPE, 15),
432 new ConversionInfo(Character.TYPE, 16),
433 new ConversionInfo(Byte.TYPE, 17),
434 new ConversionInfo(java.lang.String.class, 18)
435 };
436
437 /**
438 * Specification of conversions from XSLT type CLASS_BOOLEAN
439 * to allowed Java types.
440 */
441 private final static ConversionInfo[] m_booleanConversions = {
442 new ConversionInfo(Boolean.TYPE, 0),
443 new ConversionInfo(java.lang.Boolean.class, 1),
444 new ConversionInfo(java.lang.Object.class, 2),
445 new ConversionInfo(java.lang.String.class, 3)
446 };
447
448 /**
449 * Specification of conversions from XSLT type CLASS_NUMBER
450 * to allowed Java types.
451 */
452 private final static ConversionInfo[] m_numberConversions = {
453 new ConversionInfo(Double.TYPE, 0),
454 new ConversionInfo(java.lang.Double.class, 1),
455 new ConversionInfo(Float.TYPE, 3),
456 new ConversionInfo(Long.TYPE, 4),
457 new ConversionInfo(Integer.TYPE, 5),
458 new ConversionInfo(Short.TYPE, 6),
459 new ConversionInfo(Character.TYPE, 7),
460 new ConversionInfo(Byte.TYPE, 8),
461 new ConversionInfo(Boolean.TYPE, 9),
462 new ConversionInfo(java.lang.String.class, 10),
463 new ConversionInfo(java.lang.Object.class, 11)
464 };
465
466 /**
467 * Specification of conversions from XSLT type CLASS_STRING
468 * to allowed Java types.
469 */
470 private final static ConversionInfo[] m_stringConversions = {
471 new ConversionInfo(java.lang.String.class, 0),
472 new ConversionInfo(java.lang.Object.class, 1),
473 new ConversionInfo(Character.TYPE, 2),
474 new ConversionInfo(Double.TYPE, 3),
475 new ConversionInfo(Float.TYPE, 3),
476 new ConversionInfo(Long.TYPE, 3),
477 new ConversionInfo(Integer.TYPE, 3),
478 new ConversionInfo(Short.TYPE, 3),
479 new ConversionInfo(Byte.TYPE, 3),
480 new ConversionInfo(Boolean.TYPE, 4)
481 };
482
483 /**
484 * Specification of conversions from XSLT type CLASS_RTREEFRAG
485 * to allowed Java types.
486 */
487 private final static ConversionInfo[] m_rtfConversions = {
488 new ConversionInfo(org.w3c.dom.traversal.NodeIterator.class, 0),
489 new ConversionInfo(org.w3c.dom.NodeList.class, 1),
490 new ConversionInfo(org.w3c.dom.Node.class, 2),
491 new ConversionInfo(java.lang.String.class, 3),
492 new ConversionInfo(java.lang.Object.class, 5),
493 new ConversionInfo(Character.TYPE, 6),
494 new ConversionInfo(Double.TYPE, 7),
495 new ConversionInfo(Float.TYPE, 7),
496 new ConversionInfo(Long.TYPE, 7),
497 new ConversionInfo(Integer.TYPE, 7),
498 new ConversionInfo(Short.TYPE, 7),
499 new ConversionInfo(Byte.TYPE, 7),
500 new ConversionInfo(Boolean.TYPE, 8)
501 };
502
503 /**
504 * Specification of conversions from XSLT type CLASS_NODESET
505 * to allowed Java types. (This is the same as for CLASS_RTREEFRAG)
506 */
507 private final static ConversionInfo[] m_nodesetConversions = {
508 new ConversionInfo(org.w3c.dom.traversal.NodeIterator.class, 0),
509 new ConversionInfo(org.w3c.dom.NodeList.class, 1),
510 new ConversionInfo(org.w3c.dom.Node.class, 2),
511 new ConversionInfo(java.lang.String.class, 3),
512 new ConversionInfo(java.lang.Object.class, 5),
513 new ConversionInfo(Character.TYPE, 6),
514 new ConversionInfo(Double.TYPE, 7),
515 new ConversionInfo(Float.TYPE, 7),
516 new ConversionInfo(Long.TYPE, 7),
517 new ConversionInfo(Integer.TYPE, 7),
518 new ConversionInfo(Short.TYPE, 7),
519 new ConversionInfo(Byte.TYPE, 7),
520 new ConversionInfo(Boolean.TYPE, 8)
521 };
522
523 /**
524 * Order is significant in the list below, based on
525 * XObject.CLASS_XXX values.
526 */
527 private final 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 /**
538 * Score the conversion of a set of XSLT arguments to a
539 * given set of Java parameters.
540 * If any invocations of this function for a method with
541 * the same name return the same positive value, then a conflict
542 * has occured, and an error should be signaled.
543 * @param javaParamTypes Must be filled with valid class names, and
544 * of the same length as xsltArgs.
545 * @param xsltArgs Must be filled with valid object instances, and
546 * of the same length as javeParamTypes.
547 * @return -1 for no allowed conversion, or a positive score
548 * that is closer to zero for more preferred, or further from
549 * zero for less preferred.
550 */
551 public static int scoreMatch(Class[] javaParamTypes, int javaParamsStart,
552 Object[] xsltArgs, int score)
553 {
554 if ((xsltArgs == null) || (javaParamTypes == null))
555 return score;
556 int nParams = xsltArgs.length;
557 for(int i = nParams - javaParamTypes.length + javaParamsStart, javaParamTypesIndex = javaParamsStart;
558 i < nParams;
559 i++, javaParamTypesIndex++)
560 {
561 Object xsltObj = xsltArgs[i];
562 int xsltClassType = (xsltObj instanceof XObject)
563 ? ((XObject)xsltObj).getType()
564 : XObject.CLASS_UNKNOWN;
565 Class javaClass = javaParamTypes[javaParamTypesIndex];
566
567 // System.out.println("Checking xslt: "+xsltObj.getClass().getName()+
568 // " against java: "+javaClass.getName());
569
570 if(xsltClassType == XObject.CLASS_NULL)
571 {
572 // In Xalan I have objects of CLASS_NULL, though I'm not
573 // sure they're used any more. For now, do something funky.
574 if(!javaClass.isPrimitive())
575 {
576 // Then assume that a null can be used, but give it a low score.
577 score += 10;
578 continue;
579 }
580 else
581 return -1; // no match.
582 }
583
584 ConversionInfo[] convInfo = m_conversions[xsltClassType];
585 int nConversions = convInfo.length;
586 int k;
587 for(k = 0; k < nConversions; k++)
588 {
589 ConversionInfo cinfo = convInfo[k];
590 if(javaClass.isAssignableFrom(cinfo.m_class))
591 {
592 score += cinfo.m_score;
593 break; // from k loop
594 }
595 }
596
597 if (k == nConversions)
598 {
599 // If we get here, we haven't made a match on this parameter using
600 // the ConversionInfo array. We now try to handle the object -> object
601 // mapping which we can't handle through the array mechanism. To do this,
602 // we must determine the class of the argument passed from the stylesheet.
603
604 // If we were passed a subclass of XObject, representing one of the actual
605 // XSLT types, and we are here, we reject this extension method as a candidate
606 // because a match should have been made using the ConversionInfo array. If we
607 // were passed an XObject that encapsulates a non-XSLT type or we
608 // were passed a non-XSLT type directly, we continue.
609
610 // The current implementation (contributed by Kelly Campbell <camk@channelpoint.com>)
611 // checks to see if we were passed an XObject from the XSLT stylesheet. If not,
612 // we use the class of the object that was passed and make sure that it will
613 // map to the class type of the parameter in the extension function.
614 // If we were passed an XObject, we attempt to get the class of the actual
615 // object encapsulated inside the XObject. If the encapsulated object is null,
616 // we judge this method as a match but give it a low score.
617 // If the encapsulated object is not null, we use its type to determine
618 // whether this java method is a valid match for this extension function call.
619 // This approach eliminates the NullPointerException in the earlier implementation
620 // that resulted from passing an XObject encapsulating the null java object.
621
622 // TODO: This needs to be improved to assign relative scores to subclasses,
623 // etc.
624
625 if (XObject.CLASS_UNKNOWN == xsltClassType)
626 {
627 Class realClass = null;
628
629 if (xsltObj instanceof XObject)
630 {
631 Object realObj = ((XObject) xsltObj).object();
632 if (null != realObj)
633 {
634 realClass = realObj.getClass();
635 }
636 else
637 {
638 // do the same as if we were passed XObject.CLASS_NULL
639 score += 10;
640 continue;
641 }
642 }
643 else
644 {
645 realClass = xsltObj.getClass();
646 }
647
648 if (javaClass.isAssignableFrom(realClass))
649 {
650 score += 0; // TODO: To be assigned based on subclass "distance"
651 }
652 else
653 return -1;
654 }
655 else
656 return -1;
657 }
658 }
659 return score;
660 }
661
662 /**
663 * Convert the given XSLT object to an object of
664 * the given class.
665 * @param xsltObj The XSLT object that needs conversion.
666 * @param javaClass The type of object to convert to.
667 * @returns An object suitable for passing to the Method.invoke
668 * function in the args array, which may be null in some cases.
669 * @throws TransformerException may be thrown for Xalan conversion
670 * exceptions.
671 */
672 static Object convert(Object xsltObj, Class javaClass)
673 throws javax.xml.transform.TransformerException
674 {
675 if(xsltObj instanceof XObject)
676 {
677 XObject xobj = ((XObject)xsltObj);
678 int xsltClassType = xobj.getType();
679
680 switch(xsltClassType)
681 {
682 case XObject.CLASS_NULL:
683 return null;
684
685 case XObject.CLASS_BOOLEAN:
686 {
687 if(javaClass == java.lang.String.class)
688 return xobj.str();
689 else
690 return xobj.bool() ? Boolean.TRUE : Boolean.FALSE;
691 }
692 // break; Unreachable
693 case XObject.CLASS_NUMBER:
694 {
695 if(javaClass == java.lang.String.class)
696 return xobj.str();
697 else if(javaClass == Boolean.TYPE)
698 return xobj.bool() ? Boolean.TRUE : Boolean.FALSE;
699 else
700 {
701 return convertDoubleToNumber(xobj.num(), javaClass);
702 }
703 }
704 // break; Unreachable
705
706 case XObject.CLASS_STRING:
707 {
708 if((javaClass == java.lang.String.class) ||
709 (javaClass == java.lang.Object.class))
710 return xobj.str();
711 else if(javaClass == Character.TYPE)
712 {
713 String str = xobj.str();
714 if(str.length() > 0)
715 return new Character(str.charAt(0));
716 else
717 return null; // ??
718 }
719 else if(javaClass == Boolean.TYPE)
720 return xobj.bool() ? Boolean.TRUE : Boolean.FALSE;
721 else
722 {
723 return convertDoubleToNumber(xobj.num(), javaClass);
724 }
725 }
726 // break; Unreachable
727
728 case XObject.CLASS_RTREEFRAG:
729 {
730 // GLP: I don't see the reason for the isAssignableFrom call
731 // instead of an == test as is used everywhere else.
732 // Besides, if the javaClass is a subclass of NodeIterator
733 // the condition will be true and we'll create a NodeIterator
734 // which may not match the javaClass, causing a RuntimeException.
735 // if((NodeIterator.class.isAssignableFrom(javaClass)) ||
736 if ( (javaClass == NodeIterator.class) ||
737 (javaClass == java.lang.Object.class) )
738 {
739 DTMIterator dtmIter = ((XRTreeFrag) xobj).asNodeIterator();
740 return new DTMNodeIterator(dtmIter);
741 }
742 else if (javaClass == NodeList.class)
743 {
744 return ((XRTreeFrag) xobj).convertToNodeset();
745 }
746 // Same comment as above
747 // else if(Node.class.isAssignableFrom(javaClass))
748 else if(javaClass == Node.class)
749 {
750 DTMIterator iter = ((XRTreeFrag) xobj).asNodeIterator();
751 int rootHandle = iter.nextNode();
752 DTM dtm = iter.getDTM(rootHandle);
753 return dtm.getNode(dtm.getFirstChild(rootHandle));
754 }
755 else if(javaClass == java.lang.String.class)
756 {
757 return xobj.str();
758 }
759 else if(javaClass == Boolean.TYPE)
760 {
761 return xobj.bool() ? Boolean.TRUE : Boolean.FALSE;
762 }
763 else if(javaClass.isPrimitive())
764 {
765 return convertDoubleToNumber(xobj.num(), javaClass);
766 }
767 else
768 {
769 DTMIterator iter = ((XRTreeFrag) xobj).asNodeIterator();
770 int rootHandle = iter.nextNode();
771 DTM dtm = iter.getDTM(rootHandle);
772 Node child = dtm.getNode(dtm.getFirstChild(rootHandle));
773
774 if(javaClass.isAssignableFrom(child.getClass()))
775 return child;
776 else
777 return null;
778 }
779 }
780 // break; Unreachable
781
782 case XObject.CLASS_NODESET:
783 {
784 // GLP: I don't see the reason for the isAssignableFrom call
785 // instead of an == test as is used everywhere else.
786 // Besides, if the javaClass is a subclass of NodeIterator
787 // the condition will be true and we'll create a NodeIterator
788 // which may not match the javaClass, causing a RuntimeException.
789 // if((NodeIterator.class.isAssignableFrom(javaClass)) ||
790 if ( (javaClass == NodeIterator.class) ||
791 (javaClass == java.lang.Object.class) )
792 {
793 return xobj.nodeset();
794 }
795 // Same comment as above
796 // else if(NodeList.class.isAssignableFrom(javaClass))
797 else if(javaClass == NodeList.class)
798 {
799 return xobj.nodelist();
800 }
801 // Same comment as above
802 // else if(Node.class.isAssignableFrom(javaClass))
803 else if(javaClass == Node.class)
804 {
805 // Xalan ensures that iter() always returns an
806 // iterator positioned at the beginning.
807 DTMIterator ni = xobj.iter();
808 int handle = ni.nextNode();
809 if (handle != DTM.NULL)
810 return ni.getDTM(handle).getNode(handle); // may be null.
811 else
812 return null;
813 }
814 else if(javaClass == java.lang.String.class)
815 {
816 return xobj.str();
817 }
818 else if(javaClass == Boolean.TYPE)
819 {
820 return xobj.bool() ? Boolean.TRUE : Boolean.FALSE;
821 }
822 else if(javaClass.isPrimitive())
823 {
824 return convertDoubleToNumber(xobj.num(), javaClass);
825 }
826 else
827 {
828 DTMIterator iter = xobj.iter();
829 int childHandle = iter.nextNode();
830 DTM dtm = iter.getDTM(childHandle);
831 Node child = dtm.getNode(childHandle);
832 if(javaClass.isAssignableFrom(child.getClass()))
833 return child;
834 else
835 return null;
836 }
837 }
838 // break; Unreachable
839
840 // No default:, fall-through on purpose
841 } // end switch
842 xsltObj = xobj.object();
843
844 } // end if if(xsltObj instanceof XObject)
845
846 // At this point, we have a raw java object, not an XObject.
847 if (null != xsltObj)
848 {
849 if(javaClass == java.lang.String.class)
850 {
851 return xsltObj.toString();
852 }
853 else if(javaClass.isPrimitive())
854 {
855 // Assume a number conversion
856 XString xstr = new XString(xsltObj.toString());
857 double num = xstr.num();
858 return convertDoubleToNumber(num, javaClass);
859 }
860 else if(javaClass == java.lang.Class.class)
861 {
862 return xsltObj.getClass();
863 }
864 else
865 {
866 // Just pass the object directly, and hope for the best.
867 return xsltObj;
868 }
869 }
870 else
871 {
872 // Just pass the object directly, and hope for the best.
873 return xsltObj;
874 }
875 }
876
877 /**
878 * Do a standard conversion of a double to the specified type.
879 * @param num The number to be converted.
880 * @param javaClass The class type to be converted to.
881 * @return An object specified by javaClass, or a Double instance.
882 */
883 static Object convertDoubleToNumber(double num, Class javaClass)
884 {
885 // In the code below, I don't check for NaN, etc., instead
886 // using the standard Java conversion, as I think we should
887 // specify. See issue-runtime-errors.
888 if((javaClass == Double.TYPE) ||
889 (javaClass == java.lang.Double.class))
890 return new Double(num);
891 else if(javaClass == Float.TYPE)
892 return new Float(num);
893 else if(javaClass == Long.TYPE)
894 {
895 // Use standard Java Narrowing Primitive Conversion
896 // See http://java.sun.com/docs/books/jls/html/5.doc.html#175672
897 return new Long((long)num);
898 }
899 else if(javaClass == Integer.TYPE)
900 {
901 // Use standard Java Narrowing Primitive Conversion
902 // See http://java.sun.com/docs/books/jls/html/5.doc.html#175672
903 return new Integer((int)num);
904 }
905 else if(javaClass == Short.TYPE)
906 {
907 // Use standard Java Narrowing Primitive Conversion
908 // See http://java.sun.com/docs/books/jls/html/5.doc.html#175672
909 return new Short((short)num);
910 }
911 else if(javaClass == Character.TYPE)
912 {
913 // Use standard Java Narrowing Primitive Conversion
914 // See http://java.sun.com/docs/books/jls/html/5.doc.html#175672
915 return new Character((char)num);
916 }
917 else if(javaClass == Byte.TYPE)
918 {
919 // Use standard Java Narrowing Primitive Conversion
920 // See http://java.sun.com/docs/books/jls/html/5.doc.html#175672
921 return new Byte((byte)num);
922 }
923 else // Some other type of object
924 {
925 return new Double(num);
926 }
927 }
928
929
930 /**
931 * Format the message for the NoSuchMethodException containing
932 * all the information about the method we're looking for.
933 */
934 private static String errString(String callType, // "function" or "element"
935 String searchType, // "method" or "constructor"
936 Class classObj,
937 String funcName,
938 int searchMethod,
939 Object[] xsltArgs)
940 {
941 String resultString = "For extension " + callType
942 + ", could not find " + searchType + " ";
943 switch (searchMethod)
944 {
945 case STATIC_ONLY:
946 return resultString + "static " + classObj.getName() + "."
947 + funcName + "([ExpressionContext,] " + errArgs(xsltArgs, 0) + ").";
948
949 case INSTANCE_ONLY:
950 return resultString + classObj.getName() + "."
951 + funcName + "([ExpressionContext,] " + errArgs(xsltArgs, 0) + ").";
952
953 case STATIC_AND_INSTANCE:
954 return resultString + classObj.getName() + "." + funcName + "([ExpressionContext,] " + errArgs(xsltArgs, 0) + ").\n"
955 + "Checked both static and instance methods.";
956
957 case DYNAMIC:
958 return resultString + "static " + classObj.getName() + "." + funcName
959 + "([ExpressionContext, ]" + errArgs(xsltArgs, 0) + ") nor\n"
960 + classObj + "." + funcName + "([ExpressionContext,] " + errArgs(xsltArgs, 1) + ").";
961
962 default:
963 if (callType.equals("function")) // must be a constructor
964 {
965 return resultString + classObj.getName()
966 + "([ExpressionContext,] " + errArgs(xsltArgs, 0) + ").";
967 }
968 else // must be an element call
969 {
970 return resultString + classObj.getName() + "." + funcName
971 + "(org.apache.xalan.extensions.XSLProcessorContext, "
972 + "org.apache.xalan.templates.ElemExtensionCall).";
973 }
974 }
975
976 }
977
978
979 private static String errArgs(Object[] xsltArgs, int startingArg)
980 {
981 StringBuffer returnArgs = new StringBuffer();
982 for (int i = startingArg; i < xsltArgs.length; i++)
983 {
984 if (i != startingArg)
985 returnArgs.append(", ");
986 if (xsltArgs[i] instanceof XObject)
987 returnArgs.append(((XObject) xsltArgs[i]).getTypeString());
988 else
989 returnArgs.append(xsltArgs[i].getClass().getName());
990 }
991 return returnArgs.toString();
992 }
993
994 }