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