001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements. See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership. The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the  "License");
007     * you may not use this file except in compliance with the License.
008     * You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    /*
019     * $Id: EnvironmentCheck.java 468646 2006-10-28 06:57:58Z minchau $
020     */
021    package org.apache.xalan.xslt;
022    
023    import java.io.File;
024    import java.io.FileWriter;
025    import java.io.PrintWriter;
026    import java.lang.reflect.Field;
027    import java.lang.reflect.Method;
028    import java.util.Enumeration;
029    import java.util.Hashtable;
030    import java.util.StringTokenizer;
031    import java.util.Vector;
032    
033    import org.w3c.dom.Document;
034    import org.w3c.dom.Element;
035    import org.w3c.dom.Node;
036    
037    /**
038     * Utility class to report simple information about the environment.
039     * Simplistic reporting about certain classes found in your JVM may 
040     * help answer some FAQs for simple problems.
041     *
042     * <p>Usage-command line:  
043     * <code>
044     * java org.apache.xalan.xslt.EnvironmentCheck [-out outFile]
045     * </code></p>
046     * 
047     * <p>Usage-from program:  
048     * <code>
049     * boolean environmentOK = 
050     * (new EnvironmentCheck()).checkEnvironment(yourPrintWriter);
051     * </code></p>
052     *
053     * <p>Usage-from stylesheet:  
054     * <code><pre>
055     *    &lt;?xml version="1.0"?&gt;
056     *    &lt;xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
057     *        xmlns:xalan="http://xml.apache.org/xalan"
058     *        exclude-result-prefixes="xalan"&gt;
059     *    &lt;xsl:output indent="yes"/&gt;
060     *    &lt;xsl:template match="/"&gt;
061     *      &lt;xsl:copy-of select="xalan:checkEnvironment()"/&gt;
062     *    &lt;/xsl:template&gt;
063     *    &lt;/xsl:stylesheet&gt;
064     * </pre></code></p>
065     *  
066     * <p>Xalan users reporting problems are encouraged to use this class 
067     * to see if there are potential problems with their actual 
068     * Java environment <b>before</b> reporting a bug.  Note that you 
069     * should both check from the JVM/JRE's command line as well as 
070     * temporarily calling checkEnvironment() directly from your code, 
071     * since the classpath may differ (especially for servlets, etc).</p>
072     *
073     * <p>Also see http://xml.apache.org/xalan-j/faq.html</p>
074     *
075     * <p>Note: This class is pretty simplistic: 
076     * results are not necessarily definitive nor will it find all 
077     * problems related to environment setup.  Also, you should avoid 
078     * calling this in deployed production code, both because it is 
079     * quite slow and because it forces classes to get loaded.</p>
080     *
081     * <p>Note: This class explicitly has very limited compile-time 
082     * dependencies to enable easy compilation and usage even when 
083     * Xalan, DOM/SAX/JAXP, etc. are not present.</p>
084     * 
085     * <p>Note: for an improved version of this utility, please see 
086     * the xml-commons' project Which utility which does the same kind 
087     * of thing but in a much simpler manner.</p>
088     *
089     * @author Shane_Curcuru@us.ibm.com
090     * @version $Id: EnvironmentCheck.java 468646 2006-10-28 06:57:58Z minchau $
091     */
092    public class EnvironmentCheck
093    {
094    
095      /**
096       * Command line runnability: checks for [-out outFilename] arg.
097       * <p>Command line entrypoint; Sets output and calls 
098       * {@link #checkEnvironment(PrintWriter)}.</p>
099       * @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    }