1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the  "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 *     http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18/*
19 * $Id: EnvironmentCheck.java 468646 2006-10-28 06:57:58Z minchau $
20 */
21package org.apache.xalan.xslt;
22
23import java.io.File;
24import java.io.FileWriter;
25import java.io.PrintWriter;
26import java.lang.reflect.Field;
27import java.lang.reflect.Method;
28import java.util.Enumeration;
29import java.util.Hashtable;
30import java.util.StringTokenizer;
31import java.util.Vector;
32
33import org.w3c.dom.Document;
34import org.w3c.dom.Element;
35import org.w3c.dom.Node;
36
37/**
38 * Utility class to report simple information about the environment.
39 * Simplistic reporting about certain classes found in your JVM may
40 * help answer some FAQs for simple problems.
41 *
42 * <p>Usage-command line:
43 * <code>
44 * java org.apache.xalan.xslt.EnvironmentCheck [-out outFile]
45 * </code></p>
46 *
47 * <p>Usage-from program:
48 * <code>
49 * boolean environmentOK =
50 * (new EnvironmentCheck()).checkEnvironment(yourPrintWriter);
51 * </code></p>
52 *
53 * <p>Usage-from stylesheet:
54 * <code><pre>
55 *    &lt;?xml version="1.0"?&gt;
56 *    &lt;xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
57 *        xmlns:xalan="http://xml.apache.org/xalan"
58 *        exclude-result-prefixes="xalan"&gt;
59 *    &lt;xsl:output indent="yes"/&gt;
60 *    &lt;xsl:template match="/"&gt;
61 *      &lt;xsl:copy-of select="xalan:checkEnvironment()"/&gt;
62 *    &lt;/xsl:template&gt;
63 *    &lt;/xsl:stylesheet&gt;
64 * </pre></code></p>
65 *
66 * <p>Xalan users reporting problems are encouraged to use this class
67 * to see if there are potential problems with their actual
68 * Java environment <b>before</b> reporting a bug.  Note that you
69 * should both check from the JVM/JRE's command line as well as
70 * temporarily calling checkEnvironment() directly from your code,
71 * since the classpath may differ (especially for servlets, etc).</p>
72 *
73 * <p>Also see http://xml.apache.org/xalan-j/faq.html</p>
74 *
75 * <p>Note: This class is pretty simplistic:
76 * results are not necessarily definitive nor will it find all
77 * problems related to environment setup.  Also, you should avoid
78 * calling this in deployed production code, both because it is
79 * quite slow and because it forces classes to get loaded.</p>
80 *
81 * <p>Note: This class explicitly has very limited compile-time
82 * dependencies to enable easy compilation and usage even when
83 * Xalan, DOM/SAX/JAXP, etc. are not present.</p>
84 *
85 * <p>Note: for an improved version of this utility, please see
86 * the xml-commons' project Which utility which does the same kind
87 * of thing but in a much simpler manner.</p>
88 *
89 * @author Shane_Curcuru@us.ibm.com
90 * @version $Id: EnvironmentCheck.java 468646 2006-10-28 06:57:58Z minchau $
91 */
92public class EnvironmentCheck
93{
94
95  /**
96   * Command line runnability: checks for [-out outFilename] arg.
97   * <p>Command line entrypoint; Sets output and calls
98   * {@link #checkEnvironment(PrintWriter)}.</p>
99   * @param args command line args
100   */
101  public static void main(String[] args)
102  {
103    // Default to System.out, autoflushing
104    PrintWriter sendOutputTo = new PrintWriter(System.out, true);
105
106    // Read our simplistic input args, if supplied
107    for (int i = 0; i < args.length; i++)
108    {
109      if ("-out".equalsIgnoreCase(args[i]))
110      {
111        i++;
112
113        if (i < args.length)
114        {
115          try
116          {
117            sendOutputTo = new PrintWriter(new FileWriter(args[i], true));
118          }
119          catch (Exception e)
120          {
121            System.err.println("# WARNING: -out " + args[i] + " threw "
122                               + e.toString());
123          }
124        }
125        else
126        {
127          System.err.println(
128            "# WARNING: -out argument should have a filename, output sent to console");
129        }
130      }
131    }
132
133    EnvironmentCheck app = new EnvironmentCheck();
134    app.checkEnvironment(sendOutputTo);
135  }
136
137  /**
138   * Programmatic entrypoint: Report on basic Java environment
139   * and CLASSPATH settings that affect Xalan.
140   *
141   * <p>Note that this class is not advanced enough to tell you
142   * everything about the environment that affects Xalan, and
143   * sometimes reports errors that will not actually affect
144   * Xalan's behavior.  Currently, it very simplistically
145   * checks the JVM's environment for some basic properties and
146   * logs them out; it will report a problem if it finds a setting
147   * or .jar file that is <i>likely</i> to cause problems.</p>
148   *
149   * <p>Advanced users can peruse the code herein to help them
150   * investigate potential environment problems found; other users
151   * may simply send the output from this tool along with any bugs
152   * they submit to help us in the debugging process.</p>
153   *
154   * @param pw PrintWriter to send output to; can be sent to a
155   * file that will look similar to a Properties file; defaults
156   * to System.out if null
157   * @return true if your environment appears to have no major
158   * problems; false if potential environment problems found
159   * @see #getEnvironmentHash()
160   */
161  public boolean checkEnvironment(PrintWriter pw)
162  {
163
164    // Use user-specified output writer if non-null
165    if (null != pw)
166      outWriter = pw;
167
168    // Setup a hash to store various environment information in
169    Hashtable hash = getEnvironmentHash();
170
171    // Check for ERROR keys in the hashtable, and print report
172    boolean environmentHasErrors = writeEnvironmentReport(hash);
173
174    if (environmentHasErrors)
175    {
176      // Note: many logMsg calls have # at the start to
177      //  fake a property-file like output
178      logMsg("# WARNING: Potential problems found in your environment!");
179      logMsg("#    Check any 'ERROR' items above against the Xalan FAQs");
180      logMsg("#    to correct potential problems with your classes/jars");
181      logMsg("#    http://xml.apache.org/xalan-j/faq.html");
182      if (null != outWriter)
183        outWriter.flush();
184      return false;
185    }
186    else
187    {
188      logMsg("# YAHOO! Your environment seems to be OK.");
189      if (null != outWriter)
190        outWriter.flush();
191      return true;
192    }
193  }
194
195  /**
196   * Fill a hash with basic environment settings that affect Xalan.
197   *
198   * <p>Worker method called from various places.</p>
199   * <p>Various system and CLASSPATH, etc. properties are put into
200   * the hash as keys with a brief description of the current state
201   * of that item as the value.  Any serious problems will be put in
202   * with a key that is prefixed with {@link #ERROR 'ERROR.'} so it
203   * stands out in any resulting report; also a key with just that
204   * constant will be set as well for any error.</p>
205   * <p>Note that some legitimate cases are flaged as potential
206   * errors - namely when a developer recompiles xalan.jar on their
207   * own - and even a non-error state doesn't guaruntee that
208   * everything in the environment is correct.  But this will help
209   * point out the most common classpath and system property
210   * problems that we've seen.</p>
211   *
212   * @return Hashtable full of useful environment info about Xalan
213   * and related system properties, etc.
214   */
215  public Hashtable getEnvironmentHash()
216  {
217    // Setup a hash to store various environment information in
218    Hashtable hash = new Hashtable();
219
220    // Call various worker methods to fill in the hash
221    //  These are explicitly separate for maintenance and so
222    //  advanced users could call them standalone
223    checkJAXPVersion(hash);
224    checkProcessorVersion(hash);
225    checkParserVersion(hash);
226    checkAntVersion(hash);
227    checkDOMVersion(hash);
228    checkSAXVersion(hash);
229    checkSystemProperties(hash);
230
231    return hash;
232  }
233
234  /**
235   * Dump a basic Xalan environment report to outWriter.
236   *
237   * <p>This dumps a simple header and then each of the entries in
238   * the Hashtable to our PrintWriter; it does special processing
239   * for entries that are .jars found in the classpath.</p>
240   *
241   * @param h Hashtable of items to report on; presumably
242   * filled in by our various check*() methods
243   * @return true if your environment appears to have no major
244   * problems; false if potential environment problems found
245   * @see #appendEnvironmentReport(Node, Document, Hashtable)
246   * for an equivalent that appends to a Node instead
247   */
248  protected boolean writeEnvironmentReport(Hashtable h)
249  {
250
251    if (null == h)
252    {
253      logMsg("# ERROR: writeEnvironmentReport called with null Hashtable");
254      return false;
255    }
256
257    boolean errors = false;
258
259    logMsg(
260      "#---- BEGIN writeEnvironmentReport($Revision: 468646 $): Useful stuff found: ----");
261
262    // Fake the Properties-like output
263    for (Enumeration keys = h.keys();
264         keys.hasMoreElements();
265        /* no increment portion */
266        )
267    {
268      Object key = keys.nextElement();
269      String keyStr = (String) key;
270      try
271      {
272        // Special processing for classes found..
273        if (keyStr.startsWith(FOUNDCLASSES))
274        {
275          Vector v = (Vector) h.get(keyStr);
276          errors |= logFoundJars(v, keyStr);
277        }
278        // ..normal processing for all other entries
279        else
280        {
281          // Note: we could just check for the ERROR key by itself,
282          //    since we now set that, but since we have to go
283          //    through the whole hash anyway, do it this way,
284          //    which is safer for maintenance
285          if (keyStr.startsWith(ERROR))
286          {
287            errors = true;
288          }
289          logMsg(keyStr + "=" + h.get(keyStr));
290        }
291      }
292      catch (Exception e)
293      {
294        logMsg("Reading-" + key + "= threw: " + e.toString());
295      }
296    }
297
298    logMsg(
299      "#----- END writeEnvironmentReport: Useful properties found: -----");
300
301    return errors;
302  }
303
304  /** Prefixed to hash keys that signify serious problems.  */
305  public static final String ERROR = "ERROR.";
306
307  /** Added to descriptions that signify potential problems.  */
308  public static final String WARNING = "WARNING.";
309
310  /** Value for any error found.  */
311  public static final String ERROR_FOUND = "At least one error was found!";
312
313  /** Prefixed to hash keys that signify version numbers.  */
314  public static final String VERSION = "version.";
315
316  /** Prefixed to hash keys that signify .jars found in classpath.  */
317  public static final String FOUNDCLASSES = "foundclasses.";
318
319  /** Marker that a class or .jar was found.  */
320  public static final String CLASS_PRESENT = "present-unknown-version";
321
322  /** Marker that a class or .jar was not found.  */
323  public static final String CLASS_NOTPRESENT = "not-present";
324
325  /** Listing of common .jar files that include Xalan-related classes.  */
326  public String[] jarNames =
327  {
328    "xalan.jar", "xalansamples.jar", "xalanj1compat.jar", "xalanservlet.jar",
329    "serializer.jar",   // Serializer (shared between Xalan & Xerces)
330    "xerces.jar",       // Xerces-J 1.x
331    "xercesImpl.jar",   // Xerces-J 2.x
332    "testxsl.jar",
333    "crimson.jar",
334    "lotusxsl.jar",
335    "jaxp.jar", "parser.jar", "dom.jar", "sax.jar", "xml.jar",
336    "xml-apis.jar",
337    "xsltc.jar"
338  };
339
340  /**
341   * Print out report of .jars found in a classpath.
342   *
343   * Takes the information encoded from a checkPathForJars()
344   * call and dumps it out to our PrintWriter.
345   *
346   * @param v Vector of Hashtables of .jar file info
347   * @param desc description to print out in header
348   *
349   * @return false if OK, true if any .jars were reported
350   * as having errors
351   * @see #checkPathForJars(String, String[])
352   */
353  protected boolean logFoundJars(Vector v, String desc)
354  {
355
356    if ((null == v) || (v.size() < 1))
357      return false;
358
359    boolean errors = false;
360
361    logMsg("#---- BEGIN Listing XML-related jars in: " + desc + " ----");
362
363    for (int i = 0; i < v.size(); i++)
364    {
365      Hashtable subhash = (Hashtable) v.elementAt(i);
366
367      for (Enumeration keys = subhash.keys();
368           keys.hasMoreElements();
369           /* no increment portion */
370          )
371      {
372        Object key = keys.nextElement();
373        String keyStr = (String) key;
374        try
375        {
376          if (keyStr.startsWith(ERROR))
377          {
378            errors = true;
379          }
380          logMsg(keyStr + "=" + subhash.get(keyStr));
381
382        }
383        catch (Exception e)
384        {
385          errors = true;
386          logMsg("Reading-" + key + "= threw: " + e.toString());
387        }
388      }
389    }
390
391    logMsg("#----- END Listing XML-related jars in: " + desc + " -----");
392
393    return errors;
394  }
395
396  /**
397   * Stylesheet extension entrypoint: Dump a basic Xalan
398   * environment report from getEnvironmentHash() to a Node.
399   *
400   * <p>Copy of writeEnvironmentReport that creates a Node suitable
401   * for other processing instead of a properties-like text output.
402   * </p>
403   * @param container Node to append our report to
404   * @param factory Document providing createElement, etc. services
405   * @param h Hash presumably from {@link #getEnvironmentHash()}
406   * @see #writeEnvironmentReport(Hashtable)
407   * for an equivalent that writes to a PrintWriter instead
408   */
409  public void appendEnvironmentReport(Node container, Document factory, Hashtable h)
410  {
411    if ((null == container) || (null == factory))
412    {
413      return;
414    }
415
416    try
417    {
418      Element envCheckNode = factory.createElement("EnvironmentCheck");
419      envCheckNode.setAttribute("version", "$Revision: 468646 $");
420      container.appendChild(envCheckNode);
421
422      if (null == h)
423      {
424        Element statusNode = factory.createElement("status");
425        statusNode.setAttribute("result", "ERROR");
426        statusNode.appendChild(factory.createTextNode("appendEnvironmentReport called with null Hashtable!"));
427        envCheckNode.appendChild(statusNode);
428        return;
429      }
430
431      boolean errors = false;
432
433      Element hashNode = factory.createElement("environment");
434      envCheckNode.appendChild(hashNode);
435
436      for (Enumeration keys = h.keys();
437           keys.hasMoreElements();
438          /* no increment portion */
439          )
440      {
441        Object key = keys.nextElement();
442        String keyStr = (String) key;
443        try
444        {
445          // Special processing for classes found..
446          if (keyStr.startsWith(FOUNDCLASSES))
447          {
448            Vector v = (Vector) h.get(keyStr);
449            // errors |= logFoundJars(v, keyStr);
450            errors |= appendFoundJars(hashNode, factory, v, keyStr);
451          }
452          // ..normal processing for all other entries
453          else
454          {
455            // Note: we could just check for the ERROR key by itself,
456            //    since we now set that, but since we have to go
457            //    through the whole hash anyway, do it this way,
458            //    which is safer for maintenance
459            if (keyStr.startsWith(ERROR))
460            {
461              errors = true;
462            }
463            Element node = factory.createElement("item");
464            node.setAttribute("key", keyStr);
465            node.appendChild(factory.createTextNode((String)h.get(keyStr)));
466            hashNode.appendChild(node);
467          }
468        }
469        catch (Exception e)
470        {
471          errors = true;
472          Element node = factory.createElement("item");
473          node.setAttribute("key", keyStr);
474          node.appendChild(factory.createTextNode(ERROR + " Reading " + key + " threw: " + e.toString()));
475          hashNode.appendChild(node);
476        }
477      } // end of for...
478
479      Element statusNode = factory.createElement("status");
480      statusNode.setAttribute("result", (errors ? "ERROR" : "OK" ));
481      envCheckNode.appendChild(statusNode);
482    }
483    catch (Exception e2)
484    {
485      System.err.println("appendEnvironmentReport threw: " + e2.toString());
486      e2.printStackTrace();
487    }
488  }
489
490  /**
491   * Print out report of .jars found in a classpath.
492   *
493   * Takes the information encoded from a checkPathForJars()
494   * call and dumps it out to our PrintWriter.
495   *
496   * @param container Node to append our report to
497   * @param factory Document providing createElement, etc. services
498   * @param v Vector of Hashtables of .jar file info
499   * @param desc description to print out in header
500   *
501   * @return false if OK, true if any .jars were reported
502   * as having errors
503   * @see #checkPathForJars(String, String[])
504   */
505  protected boolean appendFoundJars(Node container, Document factory,
506        Vector v, String desc)
507  {
508
509    if ((null == v) || (v.size() < 1))
510      return false;
511
512    boolean errors = false;
513
514    for (int i = 0; i < v.size(); i++)
515    {
516      Hashtable subhash = (Hashtable) v.elementAt(i);
517
518      for (Enumeration keys = subhash.keys();
519           keys.hasMoreElements();
520           /* no increment portion */
521          )
522      {
523        Object key = keys.nextElement();
524        try
525        {
526          String keyStr = (String) key;
527          if (keyStr.startsWith(ERROR))
528          {
529            errors = true;
530          }
531          Element node = factory.createElement("foundJar");
532          node.setAttribute("name", keyStr.substring(0, keyStr.indexOf("-")));
533          node.setAttribute("desc", keyStr.substring(keyStr.indexOf("-") + 1));
534          node.appendChild(factory.createTextNode((String)subhash.get(keyStr)));
535          container.appendChild(node);
536        }
537        catch (Exception e)
538        {
539          errors = true;
540          Element node = factory.createElement("foundJar");
541          node.appendChild(factory.createTextNode(ERROR + " Reading " + key + " threw: " + e.toString()));
542          container.appendChild(node);
543        }
544      }
545    }
546    return errors;
547  }
548
549  /**
550   * Fillin hash with info about SystemProperties.
551   *
552   * Logs java.class.path and other likely paths; then attempts
553   * to search those paths for .jar files with Xalan-related classes.
554   *
555   * //@todo NOTE: We don't actually search java.ext.dirs for
556   * //  *.jar files therein! This should be updated
557   *
558   * @param h Hashtable to put information in
559   * @see #jarNames
560   * @see #checkPathForJars(String, String[])
561   */
562  protected void checkSystemProperties(Hashtable h)
563  {
564
565    if (null == h)
566      h = new Hashtable();
567
568    // Grab java version for later use
569    try
570    {
571      String javaVersion = System.getProperty("java.version");
572
573      h.put("java.version", javaVersion);
574    }
575    catch (SecurityException se)
576    {
577
578      // For applet context, etc.
579      h.put(
580        "java.version",
581        "WARNING: SecurityException thrown accessing system version properties");
582    }
583
584    // Printout jar files on classpath(s) that may affect operation
585    //  Do this in order
586    try
587    {
588
589      // This is present in all JVM's
590      String cp = System.getProperty("java.class.path");
591
592      h.put("java.class.path", cp);
593
594      Vector classpathJars = checkPathForJars(cp, jarNames);
595
596      if (null != classpathJars)
597        h.put(FOUNDCLASSES + "java.class.path", classpathJars);
598
599      // Also check for JDK 1.2+ type classpaths
600      String othercp = System.getProperty("sun.boot.class.path");
601
602      if (null != othercp)
603      {
604        h.put("sun.boot.class.path", othercp);
605
606        classpathJars = checkPathForJars(othercp, jarNames);
607
608        if (null != classpathJars)
609          h.put(FOUNDCLASSES + "sun.boot.class.path", classpathJars);
610      }
611
612      //@todo NOTE: We don't actually search java.ext.dirs for
613      //  *.jar files therein! This should be updated
614      othercp = System.getProperty("java.ext.dirs");
615
616      if (null != othercp)
617      {
618        h.put("java.ext.dirs", othercp);
619
620        classpathJars = checkPathForJars(othercp, jarNames);
621
622        if (null != classpathJars)
623          h.put(FOUNDCLASSES + "java.ext.dirs", classpathJars);
624      }
625
626      //@todo also check other System properties' paths?
627      //  v2 = checkPathForJars(System.getProperty("sun.boot.library.path"), jarNames);   // ?? may not be needed
628      //  v3 = checkPathForJars(System.getProperty("java.library.path"), jarNames);   // ?? may not be needed
629    }
630    catch (SecurityException se2)
631    {
632      // For applet context, etc.
633      h.put(
634        "java.class.path",
635        "WARNING: SecurityException thrown accessing system classpath properties");
636    }
637  }
638
639  /**
640   * Cheap-o listing of specified .jars found in the classpath.
641   *
642   * cp should be separated by the usual File.pathSeparator.  We
643   * then do a simplistic search of the path for any requested
644   * .jar filenames, and return a listing of their names and
645   * where (apparently) they came from.
646   *
647   * @param cp classpath to search
648   * @param jars array of .jar base filenames to look for
649   *
650   * @return Vector of Hashtables filled with info about found .jars
651   * @see #jarNames
652   * @see #logFoundJars(Vector, String)
653   * @see #appendFoundJars(Node, Document, Vector, String )
654   * @see #getApparentVersion(String, long)
655   */
656  protected Vector checkPathForJars(String cp, String[] jars)
657  {
658
659    if ((null == cp) || (null == jars) || (0 == cp.length())
660            || (0 == jars.length))
661      return null;
662
663    Vector v = new Vector();
664    StringTokenizer st = new StringTokenizer(cp, File.pathSeparator);
665
666    while (st.hasMoreTokens())
667    {
668
669      // Look at each classpath entry for each of our requested jarNames
670      String filename = st.nextToken();
671
672      for (int i = 0; i < jars.length; i++)
673      {
674        if (filename.indexOf(jars[i]) > -1)
675        {
676          File f = new File(filename);
677
678          if (f.exists())
679          {
680
681            // If any requested jarName exists, report on
682            //  the details of that .jar file
683            try
684            {
685              Hashtable h = new Hashtable(2);
686              // Note "-" char is looked for in appendFoundJars
687              h.put(jars[i] + "-path", f.getAbsolutePath());
688
689              // We won't bother reporting on the xalan.jar apparent version
690              // since this requires knowing the jar size of the xalan.jar
691              // before we build it.
692              // For other jars, eg. xml-apis.jar and xercesImpl.jar, we
693              // report the apparent version of the file we've found
694              if (!("xalan.jar".equalsIgnoreCase(jars[i]))) {
695                h.put(jars[i] + "-apparent.version",
696                    getApparentVersion(jars[i], f.length()));
697              }
698              v.addElement(h);
699            }
700            catch (Exception e)
701            {
702
703              /* no-op, don't add it  */
704            }
705          }
706          else
707          {
708            Hashtable h = new Hashtable(2);
709            // Note "-" char is looked for in appendFoundJars
710            h.put(jars[i] + "-path", WARNING + " Classpath entry: "
711                  + filename + " does not exist");
712            h.put(jars[i] + "-apparent.version", CLASS_NOTPRESENT);
713            v.addElement(h);
714          }
715        }
716      }
717    }
718
719    return v;
720  }
721
722  /**
723   * Cheap-o method to determine the product version of a .jar.
724   *
725   * Currently does a lookup into a local table of some recent
726   * shipped Xalan builds to determine where the .jar probably
727   * came from.  Note that if you recompile Xalan or Xerces
728   * yourself this will likely report a potential error, since
729   * we can't certify builds other than the ones we ship.
730   * Only reports against selected posted Xalan-J builds.
731   *
732   * //@todo actually look up version info in manifests
733   *
734   * @param jarName base filename of the .jarfile
735   * @param jarSize size of the .jarfile
736   *
737   * @return String describing where the .jar file probably
738   * came from
739   */
740  protected String getApparentVersion(String jarName, long jarSize)
741  {
742    // If we found a matching size and it's for our
743    //  jar, then return it's description
744    // Lookup in static jarVersions Hashtable
745    String foundSize = (String) jarVersions.get(new Long(jarSize));
746
747    if ((null != foundSize) && (foundSize.startsWith(jarName)))
748    {
749      return foundSize;
750    }
751    else
752    {
753      if ("xerces.jar".equalsIgnoreCase(jarName)
754              || "xercesImpl.jar".equalsIgnoreCase(jarName))
755//              || "xalan.jar".equalsIgnoreCase(jarName))
756      {
757
758        // For xalan.jar and xerces.jar/xercesImpl.jar, which we ship together:
759        // The jar is not from a shipped copy of xalan-j, so
760        //  it's up to the user to ensure that it's compatible
761        return jarName + " " + WARNING + CLASS_PRESENT;
762      }
763      else
764      {
765
766        // Otherwise, it's just a jar we don't have the version info calculated for
767        return jarName + " " + CLASS_PRESENT;
768      }
769    }
770  }
771
772  /**
773   * Report version information about JAXP interfaces.
774   *
775   * Currently distinguishes between JAXP 1.0.1 and JAXP 1.1,
776   * and not found; only tests the interfaces, and does not
777   * check for reference implementation versions.
778   *
779   * @param h Hashtable to put information in
780   */
781  protected void checkJAXPVersion(Hashtable h)
782  {
783
784    if (null == h)
785      h = new Hashtable();
786
787    final Class noArgs[] = new Class[0];
788    Class clazz = null;
789
790    try
791    {
792      final String JAXP1_CLASS = "javax.xml.parsers.DocumentBuilder";
793      final String JAXP11_METHOD = "getDOMImplementation";
794
795      clazz = ObjectFactory.findProviderClass(
796        JAXP1_CLASS, ObjectFactory.findClassLoader(), true);
797
798      Method method = clazz.getMethod(JAXP11_METHOD, noArgs);
799
800      // If we succeeded, we at least have JAXP 1.1 available
801      h.put(VERSION + "JAXP", "1.1 or higher");
802    }
803    catch (Exception e)
804    {
805      if (null != clazz)
806      {
807
808        // We must have found the class itself, just not the
809        //  method, so we (probably) have JAXP 1.0.1
810        h.put(ERROR + VERSION + "JAXP", "1.0.1");
811        h.put(ERROR, ERROR_FOUND);
812      }
813      else
814      {
815        // We couldn't even find the class, and don't have
816        //  any JAXP support at all, or only have the
817        //  transform half of it
818        h.put(ERROR + VERSION + "JAXP", CLASS_NOTPRESENT);
819        h.put(ERROR, ERROR_FOUND);
820      }
821    }
822  }
823
824  /**
825   * Report product version information from Xalan-J.
826   *
827   * Looks for version info in xalan.jar from Xalan-J products.
828   *
829   * @param h Hashtable to put information in
830   */
831  protected void checkProcessorVersion(Hashtable h)
832  {
833
834    if (null == h)
835      h = new Hashtable();
836
837    try
838    {
839      final String XALAN1_VERSION_CLASS =
840        "org.apache.xalan.xslt.XSLProcessorVersion";
841
842      Class clazz = ObjectFactory.findProviderClass(
843        XALAN1_VERSION_CLASS, ObjectFactory.findClassLoader(), true);
844
845      // Found Xalan-J 1.x, grab it's version fields
846      StringBuffer buf = new StringBuffer();
847      Field f = clazz.getField("PRODUCT");
848
849      buf.append(f.get(null));
850      buf.append(';');
851
852      f = clazz.getField("LANGUAGE");
853
854      buf.append(f.get(null));
855      buf.append(';');
856
857      f = clazz.getField("S_VERSION");
858
859      buf.append(f.get(null));
860      buf.append(';');
861      h.put(VERSION + "xalan1", buf.toString());
862    }
863    catch (Exception e1)
864    {
865      h.put(VERSION + "xalan1", CLASS_NOTPRESENT);
866    }
867
868    try
869    {
870      // NOTE: This is the old Xalan 2.0, 2.1, 2.2 version class,
871      //    is being replaced by class below
872      final String XALAN2_VERSION_CLASS =
873        "org.apache.xalan.processor.XSLProcessorVersion";
874
875      Class clazz = ObjectFactory.findProviderClass(
876        XALAN2_VERSION_CLASS, ObjectFactory.findClassLoader(), true);
877
878      // Found Xalan-J 2.x, grab it's version fields
879      StringBuffer buf = new StringBuffer();
880      Field f = clazz.getField("S_VERSION");
881      buf.append(f.get(null));
882
883      h.put(VERSION + "xalan2x", buf.toString());
884    }
885    catch (Exception e2)
886    {
887      h.put(VERSION + "xalan2x", CLASS_NOTPRESENT);
888    }
889    try
890    {
891      // NOTE: This is the new Xalan 2.2+ version class
892      final String XALAN2_2_VERSION_CLASS =
893        "org.apache.xalan.Version";
894      final String XALAN2_2_VERSION_METHOD = "getVersion";
895      final Class noArgs[] = new Class[0];
896
897      Class clazz = ObjectFactory.findProviderClass(
898        XALAN2_2_VERSION_CLASS, ObjectFactory.findClassLoader(), true);
899
900      Method method = clazz.getMethod(XALAN2_2_VERSION_METHOD, noArgs);
901      Object returnValue = method.invoke(null, new Object[0]);
902
903      h.put(VERSION + "xalan2_2", (String)returnValue);
904    }
905    catch (Exception e2)
906    {
907      h.put(VERSION + "xalan2_2", CLASS_NOTPRESENT);
908    }
909  }
910
911  /**
912   * Report product version information from common parsers.
913   *
914   * Looks for version info in xerces.jar/xercesImpl.jar/crimson.jar.
915   *
916   * //@todo actually look up version info in crimson manifest
917   *
918   * @param h Hashtable to put information in
919   */
920  protected void checkParserVersion(Hashtable h)
921  {
922
923    if (null == h)
924      h = new Hashtable();
925
926    try
927    {
928      final String XERCES1_VERSION_CLASS = "org.apache.xerces.framework.Version";
929
930      Class clazz = ObjectFactory.findProviderClass(
931        XERCES1_VERSION_CLASS, ObjectFactory.findClassLoader(), true);
932
933      // Found Xerces-J 1.x, grab it's version fields
934      Field f = clazz.getField("fVersion");
935      String parserVersion = (String) f.get(null);
936
937      h.put(VERSION + "xerces1", parserVersion);
938    }
939    catch (Exception e)
940    {
941      h.put(VERSION + "xerces1", CLASS_NOTPRESENT);
942    }
943
944    // Look for xerces1 and xerces2 parsers separately
945    try
946    {
947      final String XERCES2_VERSION_CLASS = "org.apache.xerces.impl.Version";
948
949      Class clazz = ObjectFactory.findProviderClass(
950        XERCES2_VERSION_CLASS, ObjectFactory.findClassLoader(), true);
951
952      // Found Xerces-J 2.x, grab it's version fields
953      Field f = clazz.getField("fVersion");
954      String parserVersion = (String) f.get(null);
955
956      h.put(VERSION + "xerces2", parserVersion);
957    }
958    catch (Exception e)
959    {
960      h.put(VERSION + "xerces2", CLASS_NOTPRESENT);
961    }
962
963    try
964    {
965      final String CRIMSON_CLASS = "org.apache.crimson.parser.Parser2";
966
967      Class clazz = ObjectFactory.findProviderClass(
968        CRIMSON_CLASS, ObjectFactory.findClassLoader(), true);
969
970      //@todo determine specific crimson version
971      h.put(VERSION + "crimson", CLASS_PRESENT);
972    }
973    catch (Exception e)
974    {
975      h.put(VERSION + "crimson", CLASS_NOTPRESENT);
976    }
977  }
978
979  /**
980   * Report product version information from Ant.
981   *
982   * @param h Hashtable to put information in
983   */
984  protected void checkAntVersion(Hashtable h)
985  {
986
987    if (null == h)
988      h = new Hashtable();
989
990    try
991    {
992      final String ANT_VERSION_CLASS = "org.apache.tools.ant.Main";
993      final String ANT_VERSION_METHOD = "getAntVersion"; // noArgs
994      final Class noArgs[] = new Class[0];
995
996      Class clazz = ObjectFactory.findProviderClass(
997        ANT_VERSION_CLASS, ObjectFactory.findClassLoader(), true);
998
999      Method method = clazz.getMethod(ANT_VERSION_METHOD, noArgs);
1000      Object returnValue = method.invoke(null, new Object[0]);
1001
1002      h.put(VERSION + "ant", (String)returnValue);
1003    }
1004    catch (Exception e)
1005    {
1006      h.put(VERSION + "ant", CLASS_NOTPRESENT);
1007    }
1008  }
1009
1010  /**
1011   * Report version info from DOM interfaces.
1012   *
1013   * Currently distinguishes between pre-DOM level 2, the DOM
1014   * level 2 working draft, the DOM level 2 final draft,
1015   * and not found.
1016   *
1017   * @param h Hashtable to put information in
1018   */
1019  protected void checkDOMVersion(Hashtable h)
1020  {
1021
1022    if (null == h)
1023      h = new Hashtable();
1024
1025    final String DOM_LEVEL2_CLASS = "org.w3c.dom.Document";
1026    final String DOM_LEVEL2_METHOD = "createElementNS";  // String, String
1027    final String DOM_LEVEL2WD_CLASS = "org.w3c.dom.Node";
1028    final String DOM_LEVEL2WD_METHOD = "supported";  // String, String
1029    final String DOM_LEVEL2FD_CLASS = "org.w3c.dom.Node";
1030    final String DOM_LEVEL2FD_METHOD = "isSupported";  // String, String
1031    final Class twoStringArgs[] = { java.lang.String.class,
1032                                    java.lang.String.class };
1033
1034    try
1035    {
1036      Class clazz = ObjectFactory.findProviderClass(
1037        DOM_LEVEL2_CLASS, ObjectFactory.findClassLoader(), true);
1038
1039      Method method = clazz.getMethod(DOM_LEVEL2_METHOD, twoStringArgs);
1040
1041      // If we succeeded, we have loaded interfaces from a
1042      //  level 2 DOM somewhere
1043      h.put(VERSION + "DOM", "2.0");
1044
1045      try
1046      {
1047        // Check for the working draft version, which is
1048        //  commonly found, but won't work anymore
1049        clazz = ObjectFactory.findProviderClass(
1050          DOM_LEVEL2WD_CLASS, ObjectFactory.findClassLoader(), true);
1051
1052        method = clazz.getMethod(DOM_LEVEL2WD_METHOD, twoStringArgs);
1053
1054        h.put(ERROR + VERSION + "DOM.draftlevel", "2.0wd");
1055        h.put(ERROR, ERROR_FOUND);
1056      }
1057      catch (Exception e2)
1058      {
1059        try
1060        {
1061          // Check for the final draft version as well
1062          clazz = ObjectFactory.findProviderClass(
1063            DOM_LEVEL2FD_CLASS, ObjectFactory.findClassLoader(), true);
1064
1065          method = clazz.getMethod(DOM_LEVEL2FD_METHOD, twoStringArgs);
1066
1067          h.put(VERSION + "DOM.draftlevel", "2.0fd");
1068        }
1069        catch (Exception e3)
1070        {
1071          h.put(ERROR + VERSION + "DOM.draftlevel", "2.0unknown");
1072          h.put(ERROR, ERROR_FOUND);
1073        }
1074      }
1075    }
1076    catch (Exception e)
1077    {
1078      h.put(ERROR + VERSION + "DOM",
1079            "ERROR attempting to load DOM level 2 class: " + e.toString());
1080      h.put(ERROR, ERROR_FOUND);
1081    }
1082
1083    //@todo load an actual DOM implmementation and query it as well
1084    //@todo load an actual DOM implmementation and check if
1085    //  isNamespaceAware() == true, which is needed to parse
1086    //  xsl stylesheet files into a DOM
1087  }
1088
1089  /**
1090   * Report version info from SAX interfaces.
1091   *
1092   * Currently distinguishes between SAX 2, SAX 2.0beta2,
1093   * SAX1, and not found.
1094   *
1095   * @param h Hashtable to put information in
1096   */
1097  protected void checkSAXVersion(Hashtable h)
1098  {
1099
1100    if (null == h)
1101      h = new Hashtable();
1102
1103    final String SAX_VERSION1_CLASS = "org.xml.sax.Parser";
1104    final String SAX_VERSION1_METHOD = "parse";  // String
1105    final String SAX_VERSION2_CLASS = "org.xml.sax.XMLReader";
1106    final String SAX_VERSION2_METHOD = "parse";  // String
1107    final String SAX_VERSION2BETA_CLASSNF = "org.xml.sax.helpers.AttributesImpl";
1108    final String SAX_VERSION2BETA_METHODNF = "setAttributes";  // Attributes
1109    final Class oneStringArg[] = { java.lang.String.class };
1110    // Note this introduces a minor compile dependency on SAX...
1111    final Class attributesArg[] = { org.xml.sax.Attributes.class };
1112
1113    try
1114    {
1115      // This method was only added in the final SAX 2.0 release;
1116      //  see changes.html "Changes from SAX 2.0beta2 to SAX 2.0prerelease"
1117      Class clazz = ObjectFactory.findProviderClass(
1118        SAX_VERSION2BETA_CLASSNF, ObjectFactory.findClassLoader(), true);
1119
1120      Method method = clazz.getMethod(SAX_VERSION2BETA_METHODNF, attributesArg);
1121
1122      // If we succeeded, we have loaded interfaces from a
1123      //  real, final SAX version 2.0 somewhere
1124      h.put(VERSION + "SAX", "2.0");
1125    }
1126    catch (Exception e)
1127    {
1128      // If we didn't find the SAX 2.0 class, look for a 2.0beta2
1129      h.put(ERROR + VERSION + "SAX",
1130            "ERROR attempting to load SAX version 2 class: " + e.toString());
1131      h.put(ERROR, ERROR_FOUND);
1132
1133      try
1134      {
1135        Class clazz = ObjectFactory.findProviderClass(
1136          SAX_VERSION2_CLASS, ObjectFactory.findClassLoader(), true);
1137
1138        Method method = clazz.getMethod(SAX_VERSION2_METHOD, oneStringArg);
1139
1140        // If we succeeded, we have loaded interfaces from a
1141        //  SAX version 2.0beta2 or earlier; these might work but
1142        //  you should really have the final SAX 2.0
1143        h.put(VERSION + "SAX-backlevel", "2.0beta2-or-earlier");
1144      }
1145      catch (Exception e2)
1146      {
1147        // If we didn't find the SAX 2.0beta2 class, look for a 1.0 one
1148        h.put(ERROR + VERSION + "SAX",
1149              "ERROR attempting to load SAX version 2 class: " + e.toString());
1150        h.put(ERROR, ERROR_FOUND);
1151
1152        try
1153        {
1154          Class clazz = ObjectFactory.findProviderClass(
1155            SAX_VERSION1_CLASS, ObjectFactory.findClassLoader(), true);
1156
1157          Method method = clazz.getMethod(SAX_VERSION1_METHOD, oneStringArg);
1158
1159          // If we succeeded, we have loaded interfaces from a
1160          //  SAX version 1.0 somewhere; which won't work very
1161          //  well for JAXP 1.1 or beyond!
1162          h.put(VERSION + "SAX-backlevel", "1.0");
1163        }
1164        catch (Exception e3)
1165        {
1166          // If we didn't find the SAX 2.0 class, look for a 1.0 one
1167          // Note that either 1.0 or no SAX are both errors
1168          h.put(ERROR + VERSION + "SAX-backlevel",
1169                "ERROR attempting to load SAX version 1 class: " + e3.toString());
1170
1171        }
1172      }
1173    }
1174  }
1175
1176  /**
1177   * Manual table of known .jar sizes.
1178   * Only includes shipped versions of certain projects.
1179   * key=jarsize, value=jarname ' from ' distro name
1180   * Note assumption: two jars cannot have the same size!
1181   *
1182   * @see #getApparentVersion(String, long)
1183   */
1184  private static Hashtable jarVersions = new Hashtable();
1185
1186  /**
1187   * Static initializer for jarVersions table.
1188   * Doing this just once saves time and space.
1189   *
1190   * @see #getApparentVersion(String, long)
1191   */
1192  static
1193  {
1194    // Note: hackish Hashtable, this could use improvement
1195    jarVersions.put(new Long(857192), "xalan.jar from xalan-j_1_1");
1196    jarVersions.put(new Long(440237), "xalan.jar from xalan-j_1_2");
1197    jarVersions.put(new Long(436094), "xalan.jar from xalan-j_1_2_1");
1198    jarVersions.put(new Long(426249), "xalan.jar from xalan-j_1_2_2");
1199    jarVersions.put(new Long(702536), "xalan.jar from xalan-j_2_0_0");
1200    jarVersions.put(new Long(720930), "xalan.jar from xalan-j_2_0_1");
1201    jarVersions.put(new Long(732330), "xalan.jar from xalan-j_2_1_0");
1202    jarVersions.put(new Long(872241), "xalan.jar from xalan-j_2_2_D10");
1203    jarVersions.put(new Long(882739), "xalan.jar from xalan-j_2_2_D11");
1204    jarVersions.put(new Long(923866), "xalan.jar from xalan-j_2_2_0");
1205    jarVersions.put(new Long(905872), "xalan.jar from xalan-j_2_3_D1");
1206    jarVersions.put(new Long(906122), "xalan.jar from xalan-j_2_3_0");
1207    jarVersions.put(new Long(906248), "xalan.jar from xalan-j_2_3_1");
1208    jarVersions.put(new Long(983377), "xalan.jar from xalan-j_2_4_D1");
1209    jarVersions.put(new Long(997276), "xalan.jar from xalan-j_2_4_0");
1210    jarVersions.put(new Long(1031036), "xalan.jar from xalan-j_2_4_1");
1211    // Stop recording xalan.jar sizes as of Xalan Java 2.5.0
1212
1213    jarVersions.put(new Long(596540), "xsltc.jar from xalan-j_2_2_0");
1214    jarVersions.put(new Long(590247), "xsltc.jar from xalan-j_2_3_D1");
1215    jarVersions.put(new Long(589914), "xsltc.jar from xalan-j_2_3_0");
1216    jarVersions.put(new Long(589915), "xsltc.jar from xalan-j_2_3_1");
1217    jarVersions.put(new Long(1306667), "xsltc.jar from xalan-j_2_4_D1");
1218    jarVersions.put(new Long(1328227), "xsltc.jar from xalan-j_2_4_0");
1219    jarVersions.put(new Long(1344009), "xsltc.jar from xalan-j_2_4_1");
1220    jarVersions.put(new Long(1348361), "xsltc.jar from xalan-j_2_5_D1");
1221    // Stop recording xsltc.jar sizes as of Xalan Java 2.5.0
1222
1223    jarVersions.put(new Long(1268634), "xsltc.jar-bundled from xalan-j_2_3_0");
1224
1225    jarVersions.put(new Long(100196), "xml-apis.jar from xalan-j_2_2_0 or xalan-j_2_3_D1");
1226    jarVersions.put(new Long(108484), "xml-apis.jar from xalan-j_2_3_0, or xalan-j_2_3_1 from xml-commons-1.0.b2");
1227    jarVersions.put(new Long(109049), "xml-apis.jar from xalan-j_2_4_0 from xml-commons RIVERCOURT1 branch");
1228    jarVersions.put(new Long(113749), "xml-apis.jar from xalan-j_2_4_1 from factoryfinder-build of xml-commons RIVERCOURT1");
1229    jarVersions.put(new Long(124704), "xml-apis.jar from tck-jaxp-1_2_0 branch of xml-commons");
1230    jarVersions.put(new Long(124724), "xml-apis.jar from tck-jaxp-1_2_0 branch of xml-commons, tag: xml-commons-external_1_2_01");
1231    jarVersions.put(new Long(194205), "xml-apis.jar from head branch of xml-commons, tag: xml-commons-external_1_3_02");
1232
1233    // If the below were more common I would update it to report
1234    //  errors better; but this is so old hardly anyone has it
1235    jarVersions.put(new Long(424490), "xalan.jar from Xerces Tools releases - ERROR:DO NOT USE!");
1236
1237    jarVersions.put(new Long(1591855), "xerces.jar from xalan-j_1_1 from xerces-1...");
1238    jarVersions.put(new Long(1498679), "xerces.jar from xalan-j_1_2 from xerces-1_2_0.bin");
1239    jarVersions.put(new Long(1484896), "xerces.jar from xalan-j_1_2_1 from xerces-1_2_1.bin");
1240    jarVersions.put(new Long(804460),  "xerces.jar from xalan-j_1_2_2 from xerces-1_2_2.bin");
1241    jarVersions.put(new Long(1499244), "xerces.jar from xalan-j_2_0_0 from xerces-1_2_3.bin");
1242    jarVersions.put(new Long(1605266), "xerces.jar from xalan-j_2_0_1 from xerces-1_3_0.bin");
1243    jarVersions.put(new Long(904030), "xerces.jar from xalan-j_2_1_0 from xerces-1_4.bin");
1244    jarVersions.put(new Long(904030), "xerces.jar from xerces-1_4_0.bin");
1245    jarVersions.put(new Long(1802885), "xerces.jar from xerces-1_4_2.bin");
1246    jarVersions.put(new Long(1734594), "xerces.jar from Xerces-J-bin.2.0.0.beta3");
1247    jarVersions.put(new Long(1808883), "xerces.jar from xalan-j_2_2_D10,D11,D12 or xerces-1_4_3.bin");
1248    jarVersions.put(new Long(1812019), "xerces.jar from xalan-j_2_2_0");
1249    jarVersions.put(new Long(1720292), "xercesImpl.jar from xalan-j_2_3_D1");
1250    jarVersions.put(new Long(1730053), "xercesImpl.jar from xalan-j_2_3_0 or xalan-j_2_3_1 from xerces-2_0_0");
1251    jarVersions.put(new Long(1728861), "xercesImpl.jar from xalan-j_2_4_D1 from xerces-2_0_1");
1252    jarVersions.put(new Long(972027), "xercesImpl.jar from xalan-j_2_4_0 from xerces-2_1");
1253    jarVersions.put(new Long(831587), "xercesImpl.jar from xalan-j_2_4_1 from xerces-2_2");
1254    jarVersions.put(new Long(891817), "xercesImpl.jar from xalan-j_2_5_D1 from xerces-2_3");
1255    jarVersions.put(new Long(895924), "xercesImpl.jar from xerces-2_4");
1256    jarVersions.put(new Long(1010806), "xercesImpl.jar from Xerces-J-bin.2.6.2");
1257    jarVersions.put(new Long(1203860), "xercesImpl.jar from Xerces-J-bin.2.7.1");
1258
1259    jarVersions.put(new Long(37485), "xalanj1compat.jar from xalan-j_2_0_0");
1260    jarVersions.put(new Long(38100), "xalanj1compat.jar from xalan-j_2_0_1");
1261
1262    jarVersions.put(new Long(18779), "xalanservlet.jar from xalan-j_2_0_0");
1263    jarVersions.put(new Long(21453), "xalanservlet.jar from xalan-j_2_0_1");
1264    jarVersions.put(new Long(24826), "xalanservlet.jar from xalan-j_2_3_1 or xalan-j_2_4_1");
1265    jarVersions.put(new Long(24831), "xalanservlet.jar from xalan-j_2_4_1");
1266    // Stop recording xalanservlet.jar sizes as of Xalan Java 2.5.0; now a .war file
1267
1268    // For those who've downloaded JAXP from sun
1269    jarVersions.put(new Long(5618), "jaxp.jar from jaxp1.0.1");
1270    jarVersions.put(new Long(136133), "parser.jar from jaxp1.0.1");
1271    jarVersions.put(new Long(28404), "jaxp.jar from jaxp-1.1");
1272    jarVersions.put(new Long(187162), "crimson.jar from jaxp-1.1");
1273    jarVersions.put(new Long(801714), "xalan.jar from jaxp-1.1");
1274    jarVersions.put(new Long(196399), "crimson.jar from crimson-1.1.1");
1275    jarVersions.put(new Long(33323), "jaxp.jar from crimson-1.1.1 or jakarta-ant-1.4.1b1");
1276    jarVersions.put(new Long(152717), "crimson.jar from crimson-1.1.2beta2");
1277    jarVersions.put(new Long(88143), "xml-apis.jar from crimson-1.1.2beta2");
1278    jarVersions.put(new Long(206384), "crimson.jar from crimson-1.1.3 or jakarta-ant-1.4.1b1");
1279
1280    // jakarta-ant: since many people use ant these days
1281    jarVersions.put(new Long(136198), "parser.jar from jakarta-ant-1.3 or 1.2");
1282    jarVersions.put(new Long(5537), "jaxp.jar from jakarta-ant-1.3 or 1.2");
1283  }
1284
1285  /** Simple PrintWriter we send output to; defaults to System.out.  */
1286  protected PrintWriter outWriter = new PrintWriter(System.out, true);
1287
1288  /**
1289   * Bottleneck output: calls outWriter.println(s).
1290   * @param s String to print
1291   */
1292  protected void logMsg(String s)
1293  {
1294    outWriter.println(s);
1295  }
1296}
1297