blob: ef8e53d7b87fdcb7e7ae05485a2354484e41f6f7 [file] [log] [blame]
/*
* The Apache Software License, Version 1.1
*
*
* Copyright (c) 2001-2002 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Xalan" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation and was
* originally based on software copyright (c) 1999, Lotus
* Development Corporation., http://www.lotus.com. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
package org.apache.xalan.xslt;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.lang.reflect.Field;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;
// Used in append* methods only
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* Utility class to report simple information about the environment.
* Simplistic reporting about certain classes found in your JVM may
* help answer some FAQs for simple problems.
*
* <p>Usage-command line:
* <code>
* java org.apache.xalan.xslt.EnvironmentCheck [-out outFile]
* </code></p>
*
* <p>Usage-from program:
* <code>
* boolean environmentOK =
* (new EnvironmentCheck()).checkEnvironment(yourPrintWriter);
* </code></p>
*
* <p>Usage-from stylesheet:
* <code><pre>
* &lt;?xml version="1.0"?&gt;
* &lt;xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
* xmlns:xalan="http://xml.apache.org/xalan"
* exclude-result-prefixes="xalan"&gt;
* &lt;xsl:output indent="yes"/&gt;
* &lt;xsl:template match="/"&gt;
* &lt;xsl:copy-of select="xalan:checkEnvironment()"/&gt;
* &lt;/xsl:template&gt;
* &lt;/xsl:stylesheet&gt;
* </pre></code></p>
*
* <p>Xalan users reporting problems are encouraged to use this class
* to see if there are potential problems with their actual
* Java environment <b>before</b> reporting a bug. Note that you
* should both check from the JVM/JRE's command line as well as
* temporarily calling checkEnvironment() directly from your code,
* since the classpath may differ (especially for servlets, etc).</p>
*
* <p>Also see http://xml.apache.org/xalan-j/faq.html</p>
*
* <p>Note: This class is pretty simplistic: it does a fairly simple
* unordered search of the classpath; it only uses Class.forName()
* to load things, not actually querying the classloader; so the
* results are not necessarily definitive nor will it find all
* problems related to environment setup. Also, you should avoid
* calling this in deployed production code, both because it is
* quite slow and because it forces classes to get loaded.</p>
*
* <p>Note: This class explicitly has very limited compile-time
* dependencies to enable easy compilation and usage even when
* Xalan, DOM/SAX/JAXP, etc. are not present.</p>
*
* <p>Note: for an improved version of this utility, please see
* the xml-commons' project Which utility which does the same kind
* of thing but in a much simpler manner.</p>
*
* @author Shane_Curcuru@us.ibm.com
* @version $Id$
*/
public class EnvironmentCheck
{
/**
* Command line runnability: checks for [-out outFilename] arg.
* <p>Command line entrypoint; Sets output and calls
* {@link #checkEnvironment(PrintWriter)}.</p>
* @param args command line args
*/
public static void main(String[] args)
{
// Default to System.out, autoflushing
PrintWriter sendOutputTo = new PrintWriter(System.out, true);
// Read our simplistic input args, if supplied
for (int i = 0; i < args.length; i++)
{
if ("-out".equalsIgnoreCase(args[i]))
{
i++;
if (i < args.length)
{
try
{
sendOutputTo = new PrintWriter(new FileWriter(args[i], true));
}
catch (Exception e)
{
System.err.println("# WARNING: -out " + args[i] + " threw "
+ e.toString());
}
}
else
{
System.err.println(
"# WARNING: -out argument should have a filename, output sent to console");
}
}
}
EnvironmentCheck app = new EnvironmentCheck();
app.checkEnvironment(sendOutputTo);
}
/**
* Programmatic entrypoint: Report on basic Java environment
* and CLASSPATH settings that affect Xalan.
*
* <p>Note that this class is not advanced enough to tell you
* everything about the environment that affects Xalan, and
* sometimes reports errors that will not actually affect
* Xalan's behavior. Currently, it very simplistically
* checks the JVM's environment for some basic properties and
* logs them out; it will report a problem if it finds a setting
* or .jar file that is <i>likely</i> to cause problems.</p>
*
* <p>Advanced users can peruse the code herein to help them
* investigate potential environment problems found; other users
* may simply send the output from this tool along with any bugs
* they submit to help us in the debugging process.</p>
*
* @param pw PrintWriter to send output to; can be sent to a
* file that will look similar to a Properties file; defaults
* to System.out if null
* @return true if your environment appears to have no major
* problems; false if potential environment problems found
* @see #getEnvironmentHash()
*/
public boolean checkEnvironment(PrintWriter pw)
{
// Use user-specified output writer if non-null
if (null != pw)
outWriter = pw;
// Setup a hash to store various environment information in
Hashtable hash = getEnvironmentHash();
// Check for ERROR keys in the hashtable, and print report
boolean environmentHasErrors = writeEnvironmentReport(hash);
if (environmentHasErrors)
{
// Note: many logMsg calls have # at the start to
// fake a property-file like output
logMsg("# WARNING: Potential problems found in your environment!");
logMsg("# Check any 'ERROR' items above against the Xalan FAQs");
logMsg("# to correct potential problems with your classes/jars");
logMsg("# http://xml.apache.org/xalan-j/faq.html");
if (null != outWriter)
outWriter.flush();
return false;
}
else
{
logMsg("# YAHOO! Your environment seems to be OK.");
if (null != outWriter)
outWriter.flush();
return true;
}
}
/**
* Fill a hash with basic environment settings that affect Xalan.
*
* <p>Worker method called from various places.</p>
* <p>Various system and CLASSPATH, etc. properties are put into
* the hash as keys with a brief description of the current state
* of that item as the value. Any serious problems will be put in
* with a key that is prefixed with {@link #ERROR 'ERROR.'} so it
* stands out in any resulting report; also a key with just that
* constant will be set as well for any error.</p>
* <p>Note that some legitimate cases are flaged as potential
* errors - namely when a developer recompiles xalan.jar on their
* own - and even a non-error state doesn't guaruntee that
* everything in the environment is correct. But this will help
* point out the most common classpath and system property
* problems that we've seen.</p>
*
* @return Hashtable full of useful environment info about Xalan
* and related system properties, etc.
*/
public Hashtable getEnvironmentHash()
{
// Setup a hash to store various environment information in
Hashtable hash = new Hashtable();
// Call various worker methods to fill in the hash
// These are explicitly separate for maintenance and so
// advanced users could call them standalone
checkJAXPVersion(hash);
checkProcessorVersion(hash);
checkParserVersion(hash);
checkAntVersion(hash);
checkDOMVersion(hash);
checkSAXVersion(hash);
checkSystemProperties(hash);
return hash;
}
/**
* Dump a basic Xalan environment report to outWriter.
*
* <p>This dumps a simple header and then each of the entries in
* the Hashtable to our PrintWriter; it does special processing
* for entries that are .jars found in the classpath.</p>
*
* @param h Hashtable of items to report on; presumably
* filled in by our various check*() methods
* @return true if your environment appears to have no major
* problems; false if potential environment problems found
* @see #appendEnvironmentReport(Node, Document, Hashtable)
* for an equivalent that appends to a Node instead
*/
protected boolean writeEnvironmentReport(Hashtable h)
{
if (null == h)
{
logMsg("# ERROR: writeEnvironmentReport called with null Hashtable");
return false;
}
boolean errors = false;
logMsg(
"#---- BEGIN writeEnvironmentReport($Revision$): Useful stuff found: ----");
// Fake the Properties-like output
for (Enumeration enum = h.keys();
enum.hasMoreElements();
/* no increment portion */
)
{
Object key = enum.nextElement();
String keyStr = (String) key;
try
{
// Special processing for classes found..
if (keyStr.startsWith(FOUNDCLASSES))
{
Vector v = (Vector) h.get(keyStr);
errors |= logFoundJars(v, keyStr);
}
// ..normal processing for all other entries
else
{
// Note: we could just check for the ERROR key by itself,
// since we now set that, but since we have to go
// through the whole hash anyway, do it this way,
// which is safer for maintenance
if (keyStr.startsWith(ERROR))
{
errors = true;
}
logMsg(keyStr + "=" + h.get(keyStr));
}
}
catch (Exception e)
{
logMsg("Reading-" + key + "= threw: " + e.toString());
}
}
logMsg(
"#----- END writeEnvironmentReport: Useful properties found: -----");
return errors;
}
/** Prefixed to hash keys that signify serious problems. */
public static final String ERROR = "ERROR.";
/** Added to descriptions that signify potential problems. */
public static final String WARNING = "WARNING.";
/** Value for any error found. */
public static final String ERROR_FOUND = "At least one error was found!";
/** Prefixed to hash keys that signify version numbers. */
public static final String VERSION = "version.";
/** Prefixed to hash keys that signify .jars found in classpath. */
public static final String FOUNDCLASSES = "foundclasses.";
/** Marker that a class or .jar was found. */
public static final String CLASS_PRESENT = "present-unknown-version";
/** Marker that a class or .jar was not found. */
public static final String CLASS_NOTPRESENT = "not-present";
/** Listing of common .jar files that include Xalan-related classes. */
public String[] jarNames =
{
"xalan.jar", "xalansamples.jar", "xalanj1compat.jar", "xalanservlet.jar",
"xerces.jar", // Xerces-J 1.x
"xercesImpl.jar", // Xerces-J 2.x
"testxsl.jar",
"crimson.jar",
"lotusxsl.jar",
"jaxp.jar", "parser.jar", "dom.jar", "sax.jar", "xml.jar",
"xml-apis.jar",
"xsltc.jar"
};
/**
* Print out report of .jars found in a classpath.
*
* Takes the information encoded from a checkPathForJars()
* call and dumps it out to our PrintWriter.
*
* @param v Vector of Hashtables of .jar file info
* @param desc description to print out in header
*
* @return false if OK, true if any .jars were reported
* as having errors
* @see #checkPathForJars(String, String[])
*/
protected boolean logFoundJars(Vector v, String desc)
{
if ((null == v) || (v.size() < 1))
return false;
boolean errors = false;
logMsg("#---- BEGIN Listing XML-related jars in: " + desc + " ----");
for (int i = 0; i < v.size(); i++)
{
Hashtable subhash = (Hashtable) v.elementAt(i);
for (Enumeration enum = subhash.keys();
enum.hasMoreElements();
/* no increment portion */
)
{
Object key = enum.nextElement();
String keyStr = (String) key;
try
{
if (keyStr.startsWith(ERROR))
{
errors = true;
}
logMsg(keyStr + "=" + subhash.get(keyStr));
}
catch (Exception e)
{
errors = true;
logMsg("Reading-" + key + "= threw: " + e.toString());
}
}
}
logMsg("#----- END Listing XML-related jars in: " + desc + " -----");
return errors;
}
/**
* Stylesheet extension entrypoint: Dump a basic Xalan
* environment report from getEnvironmentHash() to a Node.
*
* <p>Copy of writeEnvironmentReport that creates a Node suitable
* for other processing instead of a properties-like text output.
* </p>
* @param container Node to append our report to
* @param factory Document providing createElement, etc. services
* @param h Hash presumably from {@link #getEnvironmentHash()}
* @see #writeEnvironmentReport(Hashtable)
* for an equivalent that writes to a PrintWriter instead
*/
public void appendEnvironmentReport(Node container, Document factory, Hashtable h)
{
if ((null == container) || (null == factory))
{
return;
}
try
{
Element envCheckNode = factory.createElement("EnvironmentCheck");
envCheckNode.setAttribute("version", "$Revision$");
container.appendChild(envCheckNode);
if (null == h)
{
Element statusNode = factory.createElement("status");
statusNode.setAttribute("result", "ERROR");
statusNode.appendChild(factory.createTextNode("appendEnvironmentReport called with null Hashtable!"));
envCheckNode.appendChild(statusNode);
return;
}
boolean errors = false;
Element hashNode = factory.createElement("environment");
envCheckNode.appendChild(hashNode);
for (Enumeration enum = h.keys();
enum.hasMoreElements();
/* no increment portion */
)
{
Object key = enum.nextElement();
String keyStr = (String) key;
try
{
// Special processing for classes found..
if (keyStr.startsWith(FOUNDCLASSES))
{
Vector v = (Vector) h.get(keyStr);
// errors |= logFoundJars(v, keyStr);
errors |= appendFoundJars(hashNode, factory, v, keyStr);
}
// ..normal processing for all other entries
else
{
// Note: we could just check for the ERROR key by itself,
// since we now set that, but since we have to go
// through the whole hash anyway, do it this way,
// which is safer for maintenance
if (keyStr.startsWith(ERROR))
{
errors = true;
}
Element node = factory.createElement("item");
node.setAttribute("key", keyStr);
node.appendChild(factory.createTextNode((String)h.get(keyStr)));
hashNode.appendChild(node);
}
}
catch (Exception e)
{
errors = true;
Element node = factory.createElement("item");
node.setAttribute("key", keyStr);
node.appendChild(factory.createTextNode(ERROR + " Reading " + key + " threw: " + e.toString()));
hashNode.appendChild(node);
}
} // end of for...
Element statusNode = factory.createElement("status");
statusNode.setAttribute("result", (errors ? "ERROR" : "OK" ));
envCheckNode.appendChild(statusNode);
}
catch (Exception e2)
{
System.err.println("appendEnvironmentReport threw: " + e2.toString());
e2.printStackTrace();
}
}
/**
* Print out report of .jars found in a classpath.
*
* Takes the information encoded from a checkPathForJars()
* call and dumps it out to our PrintWriter.
*
* @param container Node to append our report to
* @param factory Document providing createElement, etc. services
* @param v Vector of Hashtables of .jar file info
* @param desc description to print out in header
*
* @return false if OK, true if any .jars were reported
* as having errors
* @see #checkPathForJars(String, String[])
*/
protected boolean appendFoundJars(Node container, Document factory,
Vector v, String desc)
{
if ((null == v) || (v.size() < 1))
return false;
boolean errors = false;
for (int i = 0; i < v.size(); i++)
{
Hashtable subhash = (Hashtable) v.elementAt(i);
for (Enumeration enum = subhash.keys();
enum.hasMoreElements();
/* no increment portion */
)
{
Object key = enum.nextElement();
try
{
String keyStr = (String) key;
if (keyStr.startsWith(ERROR))
{
errors = true;
}
Element node = factory.createElement("foundJar");
node.setAttribute("name", keyStr.substring(0, keyStr.indexOf("-")));
node.setAttribute("desc", keyStr.substring(keyStr.indexOf("-") + 1));
node.appendChild(factory.createTextNode((String)subhash.get(keyStr)));
container.appendChild(node);
}
catch (Exception e)
{
errors = true;
Element node = factory.createElement("foundJar");
node.appendChild(factory.createTextNode(ERROR + " Reading " + key + " threw: " + e.toString()));
container.appendChild(node);
}
}
}
return errors;
}
/**
* Fillin hash with info about SystemProperties.
*
* Logs java.class.path and other likely paths; then attempts
* to search those paths for .jar files with Xalan-related classes.
*
* //@todo NOTE: We don't actually search java.ext.dirs for
* // *.jar files therein! This should be updated
*
* @param h Hashtable to put information in
* @see #jarNames
* @see #checkPathForJars(String, String[])
*/
protected void checkSystemProperties(Hashtable h)
{
if (null == h)
h = new Hashtable();
// Grab java version for later use
try
{
String javaVersion = System.getProperty("java.version");
h.put("java.version", javaVersion);
}
catch (SecurityException se)
{
// For applet context, etc.
h.put(
"java.version",
"WARNING: SecurityException thrown accessing system version properties");
}
// Printout jar files on classpath(s) that may affect operation
// Do this in order
try
{
// This is present in all JVM's
String cp = System.getProperty("java.class.path");
h.put("java.class.path", cp);
Vector classpathJars = checkPathForJars(cp, jarNames);
if (null != classpathJars)
h.put(FOUNDCLASSES + "java.class.path", classpathJars);
// Also check for JDK 1.2+ type classpaths
String othercp = System.getProperty("sun.boot.class.path");
if (null != othercp)
{
h.put("sun.boot.class.path", othercp);
classpathJars = checkPathForJars(othercp, jarNames);
if (null != classpathJars)
h.put(FOUNDCLASSES + "sun.boot.class.path", classpathJars);
}
//@todo NOTE: We don't actually search java.ext.dirs for
// *.jar files therein! This should be updated
othercp = System.getProperty("java.ext.dirs");
if (null != othercp)
{
h.put("java.ext.dirs", othercp);
classpathJars = checkPathForJars(othercp, jarNames);
if (null != classpathJars)
h.put(FOUNDCLASSES + "java.ext.dirs", classpathJars);
}
//@todo also check other System properties' paths?
// v2 = checkPathForJars(System.getProperty("sun.boot.library.path"), jarNames); // ?? may not be needed
// v3 = checkPathForJars(System.getProperty("java.library.path"), jarNames); // ?? may not be needed
}
catch (SecurityException se2)
{
// For applet context, etc.
h.put(
"java.class.path",
"WARNING: SecurityException thrown accessing system classpath properties");
}
}
/**
* Cheap-o listing of specified .jars found in the classpath.
*
* cp should be separated by the usual File.pathSeparator. We
* then do a simplistic search of the path for any requested
* .jar filenames, and return a listing of their names and
* where (apparently) they came from.
*
* @param cp classpath to search
* @param jars array of .jar base filenames to look for
*
* @return Vector of Hashtables filled with info about found .jars
* @see #jarNames
* @see #logFoundJars(Vector, String)
* @see #appendFoundJars(Node, Document, Vector, String )
* @see #getApparentVersion(String, long)
*/
protected Vector checkPathForJars(String cp, String[] jars)
{
if ((null == cp) || (null == jars) || (0 == cp.length())
|| (0 == jars.length))
return null;
Vector v = new Vector();
StringTokenizer st = new StringTokenizer(cp, File.pathSeparator);
while (st.hasMoreTokens())
{
// Look at each classpath entry for each of our requested jarNames
String filename = st.nextToken();
for (int i = 0; i < jars.length; i++)
{
if (filename.indexOf(jars[i]) > -1)
{
File f = new File(filename);
if (f.exists())
{
// If any requested jarName exists, report on
// the details of that .jar file
try
{
Hashtable h = new Hashtable(2);
// Note "-" char is looked for in appendFoundJars
h.put(jars[i] + "-path", f.getAbsolutePath());
h.put(jars[i] + "-apparent.version",
getApparentVersion(jars[i], f.length()));
v.addElement(h);
}
catch (Exception e)
{
/* no-op, don't add it */
}
}
else
{
Hashtable h = new Hashtable(2);
// Note "-" char is looked for in appendFoundJars
h.put(jars[i] + "-path", WARNING + " Classpath entry: "
+ filename + " does not exist");
h.put(jars[i] + "-apparent.version", CLASS_NOTPRESENT);
v.addElement(h);
}
}
}
}
return v;
}
/**
* Cheap-o method to determine the product version of a .jar.
*
* Currently does a lookup into a local table of some recent
* shipped Xalan builds to determine where the .jar probably
* came from. Note that if you recompile Xalan or Xerces
* yourself this will likely report a potential error, since
* we can't certify builds other than the ones we ship.
* Only reports against selected posted Xalan-J builds.
*
* //@todo actually look up version info in manifests
*
* @param jarName base filename of the .jarfile
* @param jarSize size of the .jarfile
*
* @return String describing where the .jar file probably
* came from
*/
protected String getApparentVersion(String jarName, long jarSize)
{
// If we found a matching size and it's for our
// jar, then return it's description
// Lookup in static jarVersions Hashtable
String foundSize = (String) jarVersions.get(new Long(jarSize));
if ((null != foundSize) && (foundSize.startsWith(jarName)))
{
return foundSize;
}
else
{
if ("xerces.jar".equalsIgnoreCase(jarName)
|| "xercesImpl.jar".equalsIgnoreCase(jarName)
|| "xalan.jar".equalsIgnoreCase(jarName))
{
// For xalan.jar and xerces.jar/xercesImpl.jar, which we ship together:
// The jar is not from a shipped copy of xalan-j, so
// it's up to the user to ensure that it's compatible
return jarName + " " + WARNING + CLASS_PRESENT;
}
else
{
// Otherwise, it's just a jar we don't have the version info calculated for
return jarName + " " + CLASS_PRESENT;
}
}
}
/**
* Report version information about JAXP interfaces.
*
* Currently distinguishes between JAXP 1.0.1 and JAXP 1.1,
* and not found; only tests the interfaces, and does not
* check for reference implementation versions.
*
* @param h Hashtable to put information in
*/
protected void checkJAXPVersion(Hashtable h)
{
if (null == h)
h = new Hashtable();
final Class noArgs[] = new Class[0];
Class clazz = null;
try
{
final String JAXP1_CLASS = "javax.xml.parsers.DocumentBuilder";
final String JAXP11_METHOD = "getDOMImplementation";
clazz = classForName(JAXP1_CLASS);
Method method = clazz.getMethod(JAXP11_METHOD, noArgs);
// If we succeeded, we at least have JAXP 1.1 available
h.put(VERSION + "JAXP", "1.1");
}
catch (Exception e)
{
if (null != clazz)
{
// We must have found the class itself, just not the
// method, so we (probably) have JAXP 1.0.1
h.put(ERROR + VERSION + "JAXP", "1.0.1");
h.put(ERROR, ERROR_FOUND);
}
else
{
// We couldn't even find the class, and don't have
// any JAXP support at all, or only have the
// transform half of it
h.put(ERROR + VERSION + "JAXP", CLASS_NOTPRESENT);
h.put(ERROR, ERROR_FOUND);
}
}
}
/**
* Report product version information from Xalan-J.
*
* Looks for version info in xalan.jar from Xalan-J products.
*
* @param h Hashtable to put information in
*/
protected void checkProcessorVersion(Hashtable h)
{
if (null == h)
h = new Hashtable();
try
{
final String XALAN1_VERSION_CLASS =
"org.apache.xalan.xslt.XSLProcessorVersion";
Class clazz = classForName(XALAN1_VERSION_CLASS);
// Found Xalan-J 1.x, grab it's version fields
StringBuffer buf = new StringBuffer();
Field f = clazz.getField("PRODUCT");
buf.append(f.get(null));
buf.append(';');
f = clazz.getField("LANGUAGE");
buf.append(f.get(null));
buf.append(';');
f = clazz.getField("S_VERSION");
buf.append(f.get(null));
buf.append(';');
h.put(VERSION + "xalan1", buf.toString());
}
catch (Exception e1)
{
h.put(VERSION + "xalan1", CLASS_NOTPRESENT);
}
try
{
// NOTE: This is the old Xalan 2.0, 2.1, 2.2 version class,
// is being replaced by class below
final String XALAN2_VERSION_CLASS =
"org.apache.xalan.processor.XSLProcessorVersion";
Class clazz = classForName(XALAN2_VERSION_CLASS);
// Found Xalan-J 2.x, grab it's version fields
StringBuffer buf = new StringBuffer();
Field f = clazz.getField("S_VERSION");
buf.append(f.get(null));
h.put(VERSION + "xalan2x", buf.toString());
}
catch (Exception e2)
{
h.put(VERSION + "xalan2x", CLASS_NOTPRESENT);
}
try
{
// NOTE: This is the new Xalan 2.2+ version class
final String XALAN2_2_VERSION_CLASS =
"org.apache.xalan.Version";
final String XALAN2_2_VERSION_METHOD = "getVersion";
final Class noArgs[] = new Class[0];
Class clazz = classForName(XALAN2_2_VERSION_CLASS);
Method method = clazz.getMethod(XALAN2_2_VERSION_METHOD, noArgs);
Object returnValue = method.invoke(null, new Object[0]);
h.put(VERSION + "xalan2_2", (String)returnValue);
}
catch (Exception e2)
{
h.put(VERSION + "xalan2_2", CLASS_NOTPRESENT);
}
}
/**
* Report product version information from common parsers.
*
* Looks for version info in xerces.jar/xercesImpl.jar/crimson.jar.
*
* //@todo actually look up version info in crimson manifest
*
* @param h Hashtable to put information in
*/
protected void checkParserVersion(Hashtable h)
{
if (null == h)
h = new Hashtable();
try
{
final String XERCES1_VERSION_CLASS = "org.apache.xerces.framework.Version";
Class clazz = classForName(XERCES1_VERSION_CLASS);
// Found Xerces-J 1.x, grab it's version fields
Field f = clazz.getField("fVersion");
String parserVersion = (String) f.get(null);
h.put(VERSION + "xerces1", parserVersion);
}
catch (Exception e)
{
h.put(VERSION + "xerces1", CLASS_NOTPRESENT);
}
// Look for xerces1 and xerces2 parsers separately
try
{
final String XERCES2_VERSION_CLASS = "org.apache.xerces.impl.Version";
Class clazz = classForName(XERCES2_VERSION_CLASS);
// Found Xerces-J 2.x, grab it's version fields
Field f = clazz.getField("fVersion");
String parserVersion = (String) f.get(null);
h.put(VERSION + "xerces2", parserVersion);
}
catch (Exception e)
{
h.put(VERSION + "xerces2", CLASS_NOTPRESENT);
}
try
{
final String CRIMSON_CLASS = "org.apache.crimson.parser.Parser2";
Class clazz = classForName(CRIMSON_CLASS);
//@todo determine specific crimson version
h.put(VERSION + "crimson", CLASS_PRESENT);
}
catch (Exception e)
{
h.put(VERSION + "crimson", CLASS_NOTPRESENT);
}
}
/**
* Report product version information from Ant.
*
* @param h Hashtable to put information in
*/
protected void checkAntVersion(Hashtable h)
{
if (null == h)
h = new Hashtable();
try
{
final String ANT_VERSION_CLASS = "org.apache.tools.ant.Main";
final String ANT_VERSION_METHOD = "getAntVersion"; // noArgs
final Class noArgs[] = new Class[0];
Class clazz = classForName(ANT_VERSION_CLASS);
Method method = clazz.getMethod(ANT_VERSION_METHOD, noArgs);
Object returnValue = method.invoke(null, new Object[0]);
h.put(VERSION + "ant", (String)returnValue);
}
catch (Exception e)
{
h.put(VERSION + "ant", CLASS_NOTPRESENT);
}
}
/**
* Report version info from DOM interfaces.
*
* Currently distinguishes between pre-DOM level 2, the DOM
* level 2 working draft, the DOM level 2 final draft,
* and not found.
*
* @param h Hashtable to put information in
*/
protected void checkDOMVersion(Hashtable h)
{
if (null == h)
h = new Hashtable();
final String DOM_LEVEL2_CLASS = "org.w3c.dom.Document";
final String DOM_LEVEL2_METHOD = "createElementNS"; // String, String
final String DOM_LEVEL2WD_CLASS = "org.w3c.dom.Node";
final String DOM_LEVEL2WD_METHOD = "supported"; // String, String
final String DOM_LEVEL2FD_CLASS = "org.w3c.dom.Node";
final String DOM_LEVEL2FD_METHOD = "isSupported"; // String, String
final Class twoStringArgs[] = { java.lang.String.class,
java.lang.String.class };
try
{
Class clazz = classForName(DOM_LEVEL2_CLASS);
Method method = clazz.getMethod(DOM_LEVEL2_METHOD, twoStringArgs);
// If we succeeded, we have loaded interfaces from a
// level 2 DOM somewhere
h.put(VERSION + "DOM", "2.0");
try
{
// Check for the working draft version, which is
// commonly found, but won't work anymore
clazz = classForName(DOM_LEVEL2WD_CLASS);
method = clazz.getMethod(DOM_LEVEL2WD_METHOD, twoStringArgs);
h.put(ERROR + VERSION + "DOM.draftlevel", "2.0wd");
h.put(ERROR, ERROR_FOUND);
}
catch (Exception e2)
{
try
{
// Check for the final draft version as well
clazz = classForName(DOM_LEVEL2FD_CLASS);
method = clazz.getMethod(DOM_LEVEL2FD_METHOD, twoStringArgs);
h.put(VERSION + "DOM.draftlevel", "2.0fd");
}
catch (Exception e3)
{
h.put(ERROR + VERSION + "DOM.draftlevel", "2.0unknown");
h.put(ERROR, ERROR_FOUND);
}
}
}
catch (Exception e)
{
h.put(ERROR + VERSION + "DOM",
"ERROR attempting to load DOM level 2 class: " + e.toString());
h.put(ERROR, ERROR_FOUND);
}
//@todo load an actual DOM implmementation and query it as well
//@todo load an actual DOM implmementation and check if
// isNamespaceAware() == true, which is needed to parse
// xsl stylesheet files into a DOM
}
/**
* Report version info from SAX interfaces.
*
* Currently distinguishes between SAX 2, SAX 2.0beta2,
* SAX1, and not found.
*
* @param h Hashtable to put information in
*/
protected void checkSAXVersion(Hashtable h)
{
if (null == h)
h = new Hashtable();
final String SAX_VERSION1_CLASS = "org.xml.sax.Parser";
final String SAX_VERSION1_METHOD = "parse"; // String
final String SAX_VERSION2_CLASS = "org.xml.sax.XMLReader";
final String SAX_VERSION2_METHOD = "parse"; // String
final String SAX_VERSION2BETA_CLASSNF = "org.xml.sax.helpers.AttributesImpl";
final String SAX_VERSION2BETA_METHODNF = "setAttributes"; // Attributes
final Class oneStringArg[] = { java.lang.String.class };
// Note this introduces a minor compile dependency on SAX...
final Class attributesArg[] = { org.xml.sax.Attributes.class };
try
{
// This method was only added in the final SAX 2.0 release;
// see changes.html "Changes from SAX 2.0beta2 to SAX 2.0prerelease"
Class clazz = classForName(SAX_VERSION2BETA_CLASSNF);
Method method = clazz.getMethod(SAX_VERSION2BETA_METHODNF, attributesArg);
// If we succeeded, we have loaded interfaces from a
// real, final SAX version 2.0 somewhere
h.put(VERSION + "SAX", "2.0");
}
catch (Exception e)
{
// If we didn't find the SAX 2.0 class, look for a 2.0beta2
h.put(ERROR + VERSION + "SAX",
"ERROR attempting to load SAX version 2 class: " + e.toString());
h.put(ERROR, ERROR_FOUND);
try
{
Class clazz = classForName(SAX_VERSION2_CLASS);
Method method = clazz.getMethod(SAX_VERSION2_METHOD, oneStringArg);
// If we succeeded, we have loaded interfaces from a
// SAX version 2.0beta2 or earlier; these might work but
// you should really have the final SAX 2.0
h.put(VERSION + "SAX-backlevel", "2.0beta2-or-earlier");
}
catch (Exception e2)
{
// If we didn't find the SAX 2.0beta2 class, look for a 1.0 one
h.put(ERROR + VERSION + "SAX",
"ERROR attempting to load SAX version 2 class: " + e.toString());
h.put(ERROR, ERROR_FOUND);
try
{
Class clazz = classForName(SAX_VERSION1_CLASS);
Method method = clazz.getMethod(SAX_VERSION1_METHOD, oneStringArg);
// If we succeeded, we have loaded interfaces from a
// SAX version 1.0 somewhere; which won't work very
// well for JAXP 1.1 or beyond!
h.put(VERSION + "SAX-backlevel", "1.0");
}
catch (Exception e3)
{
// If we didn't find the SAX 2.0 class, look for a 1.0 one
// Note that either 1.0 or no SAX are both errors
h.put(ERROR + VERSION + "SAX-backlevel",
"ERROR attempting to load SAX version 1 class: " + e3.toString());
}
}
}
}
/**
* Worker method to load a class.
* Factor out loading classes for future use and JDK differences.
* Copied from javax.xml.*.FactoryFinder
* @param className name of class to load from
* an appropriate classLoader
* @return the class asked for
*/
protected static Class classForName(String className)
throws ClassNotFoundException
{
ClassLoader classLoader = findClassLoader();
if (classLoader == null)
{
return Class.forName(className);
}
else
{
return classLoader.loadClass(className);
}
}
/**
* Worker method to figure out which ClassLoader to use.
* For JDK 1.2 and later use the context ClassLoader.
* Copied from javax.xml.*.FactoryFinder
* @return the appropriate ClassLoader
*/
protected static ClassLoader findClassLoader()
throws ClassNotFoundException
{
ClassLoader classLoader = null;
Method m = null;
try
{
m = Thread.class.getMethod("getContextClassLoader", null);
}
catch (NoSuchMethodException e)
{
// Assume that we are running JDK 1.1, use the current ClassLoader
return EnvironmentCheck.class.getClassLoader();
}
try
{
return (ClassLoader) m.invoke(Thread.currentThread(), null);
}
catch (Exception e)
{
throw new RuntimeException(e.toString());
}
}
/**
* Manual table of known .jar sizes.
* Only includes shipped versions of certain projects.
* key=jarsize, value=jarname ' from ' distro name
* Note assumption: two jars cannot have the same size!
*
* @see #getApparentVersion(String, long)
*/
protected static Hashtable jarVersions = new Hashtable();
/**
* Static initializer for jarVersions table.
* Doing this just once saves time and space.
*
* @see #getApparentVersion(String, long)
*/
static
{
// Note: hackish Hashtable, this could use improvement
jarVersions.put(new Long(857192), "xalan.jar from xalan-j_1_1");
jarVersions.put(new Long(440237), "xalan.jar from xalan-j_1_2");
jarVersions.put(new Long(436094), "xalan.jar from xalan-j_1_2_1");
jarVersions.put(new Long(426249), "xalan.jar from xalan-j_1_2_2");
jarVersions.put(new Long(702536), "xalan.jar from xalan-j_2_0_0");
jarVersions.put(new Long(720930), "xalan.jar from xalan-j_2_0_1");
jarVersions.put(new Long(732330), "xalan.jar from xalan-j_2_1_0");
jarVersions.put(new Long(872241), "xalan.jar from xalan-j_2_2_D10");
jarVersions.put(new Long(882739), "xalan.jar from xalan-j_2_2_D11");
jarVersions.put(new Long(923866), "xalan.jar from xalan-j_2_2_0");
jarVersions.put(new Long(905872), "xalan.jar from xalan-j_2_3_D1");
jarVersions.put(new Long(906122), "xalan.jar from xalan-j_2_3_0");
// Impossible to get exact number, so don't use this: jarVersions.put(new Long(), "xalan.jar from xalan-j_2_4_D1");
jarVersions.put(new Long(857171), "xalan.jar from lotusxsl-j_1_0_1");
jarVersions.put(new Long(802165), "xalan.jar from lotusxsl-j_2_0_0");
jarVersions.put(new Long(857692), "xalan.jar from lotusxsl-j_2_2");
jarVersions.put(new Long(596540), "xsltc.jar from xalan-j_2_2_0");
jarVersions.put(new Long(590247), "xsltc.jar from xalan-j_2_3_D1");
jarVersions.put(new Long(589914), "xsltc.jar from xalan-j_2_3_0");
jarVersions.put(new Long(1306667), "xsltc.jar from xalan-j_2_4_D1");
jarVersions.put(new Long(1268634), "xsltc.jar-bundled from xalan-j_2_3_0");
jarVersions.put(new Long(100196), "xml-apis.jar from xalan-j_2_2_0 or xalan-j_2_3_D1");
jarVersions.put(new Long(108484), "xml-apis.jar from xalan-j_2_3_0,2_4_D1 from xml-commons-1.0.b2");
// If the below were more common I would update it to report
// errors better; but this is so old hardly anyone has it
jarVersions.put(new Long(424490), "xalan.jar from Xerces Tools releases - ERROR:DO NOT USE!");
jarVersions.put(new Long(1591855), "xerces.jar from xalan-j_1_1 from xerces-1...");
jarVersions.put(new Long(1498679), "xerces.jar from xalan-j_1_2 from xerces-1_2_0.bin");
jarVersions.put(new Long(1484896), "xerces.jar from xalan-j_1_2_1 from xerces-1_2_1.bin");
jarVersions.put(new Long(804460), "xerces.jar from xalan-j_1_2_2 from xerces-1_2_2.bin");
jarVersions.put(new Long(1499244), "xerces.jar from xalan-j_2_0_0 from xerces-1_2_3.bin");
jarVersions.put(new Long(1605266), "xerces.jar from xalan-j_2_0_1 from xerces-1_3_0.bin");
jarVersions.put(new Long(904030), "xerces.jar from xalan-j_2_1_0 from xerces-1_4.bin");
jarVersions.put(new Long(1190776), "xerces.jar from lotusxsl_1_0_1 apparently-from xerces-1_0_3.bin");
jarVersions.put(new Long(1489400), "xerces.jar from lotusxsl-j_2_0_0 from XML4J-3_1_1");
jarVersions.put(new Long(1787796), "xerces.jar from lotusxsl-j_2_2 or xerces-1_4_1.bin");
jarVersions.put(new Long(904030), "xerces.jar from xerces-1_4_0.bin");
jarVersions.put(new Long(1802885), "xerces.jar from xerces-1_4_2.bin");
jarVersions.put(new Long(1734594), "xerces.jar from Xerces-J-bin.2.0.0.beta3");
jarVersions.put(new Long(1808883), "xerces.jar from xalan-j_2_2_D10,D11,D12 or xerces-1_4_3.bin");
jarVersions.put(new Long(1803877), "xerces.jar from XML4J-3_2_1");
jarVersions.put(new Long(1812019), "xerces.jar from xalan-j_2_2_0");
jarVersions.put(new Long(1720292), "xercesImpl.jar from xalan-j_2_3_D1");
jarVersions.put(new Long(1730053), "xercesImpl.jar from xalan-j_2_3_0 from xerces-2_0_0");
jarVersions.put(new Long(1728861), "xercesImpl.jar from xalan-j_2_4_D1 from xerces-2_0_1");
jarVersions.put(new Long(37485), "xalanj1compat.jar from xalan-j_2_0_0");
jarVersions.put(new Long(38100), "xalanj1compat.jar from xalan-j_2_0_1");
jarVersions.put(new Long(18779), "xalanservlet.jar from xalan-j_2_0_0");
jarVersions.put(new Long(21453), "xalanservlet.jar from xalan-j_2_0_1");
// For those who've downloaded JAXP from sun
jarVersions.put(new Long(5618), "jaxp.jar from jaxp1.0.1");
jarVersions.put(new Long(136133), "parser.jar from jaxp1.0.1");
jarVersions.put(new Long(28404), "jaxp.jar from jaxp-1.1");
jarVersions.put(new Long(187162), "crimson.jar from jaxp-1.1");
jarVersions.put(new Long(801714), "xalan.jar from jaxp-1.1");
jarVersions.put(new Long(196399), "crimson.jar from crimson-1.1.1");
jarVersions.put(new Long(33323), "jaxp.jar from crimson-1.1.1 or jakarta-ant-1.4.1b1");
jarVersions.put(new Long(152717), "crimson.jar from crimson-1.1.2beta2");
jarVersions.put(new Long(88143), "xml-apis.jar from crimson-1.1.2beta2");
jarVersions.put(new Long(206384), "crimson.jar from crimson-1.1.3 or jakarta-ant-1.4.1b1");
// jakarta-ant: since many people use ant these days
jarVersions.put(new Long(136198), "parser.jar from jakarta-ant-1.3 or 1.2");
jarVersions.put(new Long(5537), "jaxp.jar from jakarta-ant-1.3 or 1.2");
// Various LotusXSL versions, which are based on Xalan code
// (LotusXSL was donated by Lotus to Apache to become Xalan)
jarVersions.put(new Long(120274), "lotusxsl.jar from lotusxsl-0_16_4");
jarVersions.put(new Long(120293), "lotusxsl.jar from lotusxsl-0_16_5");
jarVersions.put(new Long(283777), "lotusxsl.jar from lotusxsl-0_17_2");
jarVersions.put(new Long(305577), "lotusxsl.jar from lotusxsl-0_17_3");
jarVersions.put(new Long(304500), "lotusxsl.jar from lotusxsl-0_17_4");
jarVersions.put(new Long(714959), "lotusxsl.jar from lotusxsl-0_18_1");
jarVersions.put(new Long(717674), "lotusxsl.jar from lotusxsl-0_18_2");
jarVersions.put(new Long(752343), "lotusxsl.jar from lotusxsl-0_18_3");
jarVersions.put(new Long(907101), "lotusxsl.jar from lotusxsl-0_18_4");
}
/** Simple PrintWriter we send output to; defaults to System.out. */
protected PrintWriter outWriter = new PrintWriter(System.out, true);
/**
* Bottleneck output: calls outWriter.println(s).
* @param s String to print
*/
protected void logMsg(String s)
{
outWriter.println(s);
}
}