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