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    }