Home | History | Annotate | Download | only in xslt
      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  */
     21 package org.apache.xalan.xslt;
     22 
     23 import java.io.File;
     24 import java.io.FileWriter;
     25 import java.io.PrintWriter;
     26 import java.lang.reflect.Field;
     27 import java.lang.reflect.Method;
     28 import java.util.Enumeration;
     29 import java.util.Hashtable;
     30 import java.util.StringTokenizer;
     31 import java.util.Vector;
     32 
     33 import org.w3c.dom.Document;
     34 import org.w3c.dom.Element;
     35 import 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 (at) us.ibm.com
     90  * @version $Id: EnvironmentCheck.java 468646 2006-10-28 06:57:58Z minchau $
     91  */
     92 public 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