curcuru 01/01/19 15:35:08
Added: test/java/src/org/apache/qetest/xalanj1 TestThreads.java Log: Simple port of trax TestThreads to Xalan-J 1.x API (or 2.x compat.jar) Revision Changes Path 1.1 xml-xalan/test/java/src/org/apache/qetest/xalanj1/TestThreads.java Index: TestThreads.java =================================================================== /* * The Apache Software License, Version 1.1 * * * Copyright (c) 2001 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 [EMAIL PROTECTED] * * 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) 2000, Lotus * Development Corporation., http://www.lotus.com. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. */ /* * * TestThreads.java * */ package org.apache.qetest.xalanj1; import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.io.Reader; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.OutputStream; import java.util.Properties; import java.util.StringTokenizer; // Needed SAX classes import org.xml.sax.InputSource; import org.xml.sax.SAXException; // For optional URI/URLs instead of string filenames import java.net.URL; import java.net.MalformedURLException; import org.apache.xalan.xslt.StylesheetRoot; import org.apache.xalan.xslt.XSLTProcessor; import org.apache.xalan.xslt.XSLTProcessorFactory; import org.apache.xalan.xslt.XSLTInputSource; import org.apache.xalan.xslt.XSLTResultTarget; import org.apache.xalan.xslt.XSLTProcessorFactory; //------------------------------------------------------------------------- /** * Testing multiple simultaneous processors on different threads with Xalan-J 1.x. * <p>No validation of output files is currently done! You must manually * inspect any logfiles. Most options can be passed in with a Properties file.</p> * <p>Note: Most automated tests extend XSLProcessorTestBase, and * are named *Test.java. Since we are semi-manual, we're * named Test*.java instead.</p> * We assume Features.STREAM. * @author [EMAIL PROTECTED] */ public class TestThreads { /** * Convenience method to print out usage information. * * NEEDSDOC ($objectName$) @return */ public static String usage() { return ("Usage: TestThreads [-load] file.properties :\n" + " where the properties file can set:,\n" + " inputDir=e:\\builds\\xsl-test\n" + " outputDir=e:\\builds\\xsl-test\\results\n" + " logFile=e:\\builds\\xsl-test\\results\\TestThreads.xml\n" + " numRunners=5\n" + " numRunnerCalls=10\n" + " setOneFile=bool01\n" + " setTwoFile=expr01\n" + " setThreeFile=numb01\n" + " paramName=SomeParam\n" + " paramVal=TheValue\n"); } /** NEEDSDOC Field debug */ public boolean debug = true; // for adhoc debugging /** * Number of sets of worker threads to create and loops per runner. * <p>'numRunners=xx', default is 10; 'numRunnerCalls=xx', default is 50.</p> */ protected int numRunners = 10; /** * Number of sets of worker threads to create and loops per runner. * <p>'numRunners=xx', default is 10; 'numRunnerCalls=xx', default is 50.</p> */ protected int numRunnerCalls = 50; /** * Root input filenames that certain runners should use, in the inputDir. * <p>'setOneFile=File'; 'setTwoFile=File'; 'setThreeFile=File' * in .prop file to set; default is TestThreads1, TestThreads2, TestThreads3.</p> * <p>Files are found in 'inputDir=c:\bar\baz' from .prop file.</p> */ protected String inputDir = null; /** NEEDSDOC Field setOneFilenameRoot */ protected String setOneFilenameRoot = "TestThreads1"; /** NEEDSDOC Field setTwoFilenameRoot */ protected String setTwoFilenameRoot = "TestThreads2"; /** NEEDSDOC Field setThreeFilenameRoot */ protected String setThreeFilenameRoot = "TestThreads3"; /** * All output logs and files get put in the outputDir. */ protected String outputDir = null; /** * Sample PARAM name that certain runners should use. * <p>Use 'paramName=xx' in .prop file to set, default is test1.</p> */ protected String paramName = "test1"; /** * Sample PARAM value that certain runners should use. * <p>Use 'paramVal=xx' in .prop file to set, default is bar.</p> */ protected String paramVal = "bar"; /** * liaisonClassName that just the *second* set of runners should use. * <p>Use 'liaison=xx' in .prop file to set, default is null (whatever the processor's default is).</p> */ protected String liaison = null; // TRAX unused // Used to pass info to runners; simpler to update than changing ctors /** RunnerID offset in ctor's array initializer. */ public static final int ID = 0; /** NEEDSDOC Field XMLNAME */ public static final int XMLNAME = 1; /** NEEDSDOC Field XSLNAME */ public static final int XSLNAME = 2; /** NEEDSDOC Field OUTNAME */ public static final int OUTNAME = 3; /** NEEDSDOC Field PARAMNAME */ public static final int PARAMNAME = 4; /** NEEDSDOC Field PARAMVAL */ public static final int PARAMVAL = 5; /** NEEDSDOC Field OPTIONS */ public static final int OPTIONS = 6; /** NEEDSDOC Field LIAISON */ public static final int LIAISON = 7; /** NEEDSDOC Field FUTUREUSE */ public static final int FUTUREUSE = 8; /** * Name of main file's output logging; each runner also has separate output. */ protected String logFileName = "TestThreads.xml"; /** * Construct multiple threads with processors and run them all. * @author Shane Curcuru & Scott Boag * <p>Preprocesses some stylesheets, then creates lots of worker threads.</p> */ public void runTest() { // Prepare a log file and dump out some basic info createLogFile(logFileName); println("<?xml version=\"1.0\"?>"); println("<resultsfile logFile=\"" + logFileName + "\">"); println("<message desc=\"threads=" + (3 * numRunners) + " iterations=" + numRunnerCalls + "\"/>"); println("<message desc=\"oneF=" + setOneFilenameRoot + " twof=" + setTwoFilenameRoot + " threef=" + setThreeFilenameRoot + "\"/>"); println("<message desc=\"param=" + paramName + " val=" + paramVal + " liaison=" + liaison + "\"/>"); // Preprocess some stylesheets for use by the runners String errStr = "Create processor threw: "; // Templates stylesheet1, stylesheet2, stylesheet3; StylesheetRoot stylesheet1, stylesheet2, stylesheet3; try { // String setOneURL = filenameToURI(inputDir + setOneFilenameRoot + ".xsl"); // String setTwoURL = filenameToURI(inputDir + setTwoFilenameRoot + ".xsl"); // String setThreeURL = filenameToURI(inputDir + setThreeFilenameRoot + ".xsl"); String setOneURL = (inputDir + setOneFilenameRoot + ".xsl"); String setTwoURL = (inputDir + setTwoFilenameRoot + ".xsl"); String setThreeURL = (inputDir + setThreeFilenameRoot + ".xsl"); // TransformerFactory factory = TransformerFactory.newInstance(); XSLTProcessor processor = null; if (liaison != null) processor = XSLTProcessorFactory.getProcessorUsingLiaisonName(liaison); else processor = org.apache.xalan.xslt.XSLTProcessorFactory.getProcessor(); errStr = "Processing stylesheet1 threw: "; // stylesheet1 = factory.newTemplates(new StreamSource(setOneURL)); stylesheet1 = processor.processStylesheet(new XSLTInputSource(setOneURL)); errStr = "Processing stylesheet2 threw: "; // stylesheet2 = factory.newTemplates(new StreamSource(setTwoURL)); stylesheet2 = processor.processStylesheet(new XSLTInputSource(setTwoURL)); errStr = "Processing stylesheet3 threw: "; // stylesheet3 = factory.newTemplates(new StreamSource(setThreeURL)); stylesheet3 = processor.processStylesheet(new XSLTInputSource(setThreeURL)); } catch (Exception e) { println("<arbitrary desc=\"" + errStr + e.toString() + "\">"); if (pWriter != null) { e.printStackTrace(pWriter); } e.printStackTrace(); println("</arbitrary>"); return; } errStr = "PreCreating runners threw: "; try { String[] rValues = new String[FUTUREUSE]; // Create a whole bunch of worker threads and run them for (int i = 0; i < numRunners; i++) { TestThreadsRunner r1, r2, r3; Thread t1, t2, t3; // First set of runners reports on memory usage periodically rValues[ID] = "one-" + i; rValues[XMLNAME] = (inputDir + setOneFilenameRoot + ".xml"); // filenameToURI rValues[XSLNAME] = (inputDir + setOneFilenameRoot + ".xsl"); // filenameToURI rValues[OUTNAME] = outputDir + setOneFilenameRoot + "r" + i; rValues[PARAMNAME] = paramName; rValues[PARAMVAL] = paramVal; rValues[OPTIONS] = "memory;param"; errStr = "Creating runnerone-" + i + " threw: "; r1 = new TestThreadsRunner(rValues, stylesheet1, numRunnerCalls); t1 = new Thread(r1); t1.start(); // Second set of runners is polite; uses optional liaison rValues[ID] = "two-" + i; rValues[XMLNAME] = (inputDir + setTwoFilenameRoot + ".xml"); // filenameToURI rValues[XSLNAME] = (inputDir + setTwoFilenameRoot + ".xsl"); // filenameToURI rValues[OUTNAME] = outputDir + setTwoFilenameRoot + "r" + i; rValues[PARAMNAME] = paramName; rValues[PARAMVAL] = paramVal; rValues[OPTIONS] = "polite;param"; if ((liaison != null) &&!(liaison.equals(""))) rValues[LIAISON] = liaison; errStr = "Creating runnertwo-" + i + " threw: "; r2 = new TestThreadsRunner(rValues, stylesheet2, numRunnerCalls); t2 = new Thread(r2); t2.start(); rValues[LIAISON] = null; // Third set of runners will recreate it's processor each time // and report memory usage; but not set the param // Note: this causes lots of calls to System.gc rValues[ID] = "thr-" + i; rValues[XMLNAME] = (inputDir + setThreeFilenameRoot + ".xml"); // filenameToURI rValues[XSLNAME] = (inputDir + setThreeFilenameRoot + ".xsl"); // filenameToURI rValues[OUTNAME] = outputDir + setThreeFilenameRoot + "r" + i; rValues[PARAMNAME] = paramName; rValues[PARAMVAL] = paramVal; rValues[OPTIONS] = "recreate;memory"; errStr = "Creating runnerthree-" + i + " threw: "; r3 = new TestThreadsRunner(rValues, stylesheet3, numRunnerCalls); t3 = new Thread(r3); t3.start(); println("<message desc=\"Created " + i + "th set of runners.\"/>"); } } catch (Exception e) { println("<arbitrary desc=\"" + errStr + e.toString() + "\">"); if (pWriter != null) { e.printStackTrace(pWriter); } e.printStackTrace(); println("</arbitrary>"); } // Clean up our own references, just for completeness stylesheet1 = null; stylesheet2 = null; stylesheet3 = null; errStr = null; println("<message desc=\"Created all our runners!\"/>"); println("<message desc=\"TestThreads main thread now complete\"/>"); println("</resultsfile>"); if (pWriter != null) pWriter.flush(); } /** * Read in properties file and set instance variables. * * @param fName name of .properties file to read * @return false if error occoured */ protected boolean initPropFile(String fName) { Properties p = new Properties(); try { // Load named file into our properties block FileInputStream fIS = new FileInputStream(fName); p.load(fIS); // Parse out any values that match our internal convenience variables outputDir = p.getProperty("outputDir", outputDir); // Validate the outputDir and use it to reset the logFileName File oDir = new File(outputDir); if (!oDir.exists()) { if (!oDir.mkdirs()) { // Error, we can't create the outputDir, default to current dir println("<message desc=\"outputDir(" + outputDir + ") does not exist, defaulting to .\"/>"); outputDir = "."; } } // Verify inputDir as well inputDir = p.getProperty("inputDir", inputDir); File tDir = new File(inputDir); if (!tDir.exists()) { if (!tDir.mkdirs()) { // Error, we can't create the inputDir, abort println("<message desc=\"inputDir(" + inputDir + ") does not exist, terminating test\"/>"); return false; } } // Add on separators inputDir += File.separator; outputDir += File.separator; // Each defaults to variable initializers logFileName = p.getProperty("logFile", logFileName); setOneFilenameRoot = p.getProperty("setOneFile", setOneFilenameRoot); setTwoFilenameRoot = p.getProperty("setTwoFile", setTwoFilenameRoot); setThreeFilenameRoot = p.getProperty("setThreeFile", setThreeFilenameRoot); paramName = p.getProperty("paramName", paramName); paramVal = p.getProperty("paramVal", paramVal); liaison = p.getProperty("liaison", liaison); String numb; numb = p.getProperty("numRunners"); if (numb != null) { try { numRunners = Integer.parseInt(numb); } catch (NumberFormatException numEx) { // no-op, leave set as default println("<message desc=\"numRunners threw: " + numEx.toString() + "\"/>"); } } numb = p.getProperty("numRunnerCalls"); if (numb != null) { try { numRunnerCalls = Integer.parseInt(numb); } catch (NumberFormatException numEx) { // no-op, leave set as default println("<message desc=\"numRunnerCalls threw: " + numEx.toString() + "\"/>"); } } } catch (Exception e) { println("<arbitrary=\"initPropFile: " + fName + " threw: " + e.toString() + "\">"); if (pWriter != null) { e.printStackTrace(pWriter); } e.printStackTrace(); println("</arbitrary>"); return false; } return true; } /** * Bottleneck output; goes to System.out and main's pWriter. * * NEEDSDOC @param s */ protected void println(String s) { System.out.println(s); if (pWriter != null) pWriter.println(s); } /** A simple log output file for the main thread; each runner also has it's own. */ protected PrintWriter pWriter = null; /** * Worker method to setup a simple log output file. * * NEEDSDOC @param n */ protected void createLogFile(String n) { try { pWriter = new PrintWriter(new FileWriter(n, true)); } catch (Exception e) { System.err.println("<message desc=\"createLogFile threw: " + e.toString() + "\"/>"); e.printStackTrace(); } } /** * Startup the test from the command line. * * NEEDSDOC @param args */ public static void main(String[] args) { if (args.length < 1) { System.err.println("ERROR! Must have at least one argument\n" + usage()); return; // Don't System.exit, it's not polite } TestThreads app = new TestThreads(); // semi-HACK: accept and ignore -load as first arg only String propFileName = null; if ("-load".equalsIgnoreCase(args[0])) { propFileName = args[1]; } else { propFileName = args[0]; } if (!app.initPropFile(propFileName)) // Side effect: creates pWriter for logging { System.err.println("ERROR! Could not read properties file: " + propFileName); return; } app.runTest(); } /** * Worker method to translate String to URI. * Note: Xerces and Crimson appear to handle some URI references * differently - this method needs further work once we figure out * exactly what kind of format each parser wants (esp. considering * relative vs. absolute references). * @param String path\filename of test file * @return URL to pass to SystemId */ public static String filenameToURI(String filename) { File f = new File(filename); String tmp = f.getAbsolutePath(); if (File.separatorChar == '\\') { tmp = tmp.replace('\\', '/'); } return "file:///" + tmp; } } // end of class TestThreads /** * Worker class to run a processor on a separate thread. * <p>Currently, no automated validation is done, however most * output files and all error logs are saved to disk allowing for * later manual verification.</p> */ class TestThreadsRunner implements Runnable { /** NEEDSDOC Field xslStylesheet */ // Templates xslStylesheet; StylesheetRoot xslStylesheet; /** NEEDSDOC Field numProcesses */ int numProcesses; /** NEEDSDOC Field runnerID */ String runnerID; /** NEEDSDOC Field xmlName */ String xmlName; /** NEEDSDOC Field xslName */ String xslName; /** NEEDSDOC Field outName */ String outName; /** NEEDSDOC Field paramName */ String paramName; /** NEEDSDOC Field paramVal */ String paramVal; /** NEEDSDOC Field liaison */ String liaison; /** NEEDSDOC Field polite */ boolean polite = false; // if we should yield each loop /** NEEDSDOC Field recreate */ boolean recreate = false; // if we should re-create a new processor each time /** NEEDSDOC Field validate */ boolean validate = false; // if we should attempt to validate output files (FUTUREWORK) /** NEEDSDOC Field reportMem */ boolean reportMem = false; // if we should report memory usage periodically /** NEEDSDOC Field setParam */ boolean setParam = false; // if we should set our parameter or not /** * Constructor TestThreadsRunner * * * NEEDSDOC @param params * NEEDSDOC @param xslStylesheet * NEEDSDOC @param numProcesses */ TestThreadsRunner(String[] params, StylesheetRoot xslStylesheet, int numProcesses) { this.xslStylesheet = xslStylesheet; this.numProcesses = numProcesses; this.runnerID = params[TestThreads.ID]; this.xmlName = params[TestThreads.XMLNAME]; // must already be legal URI this.xslName = params[TestThreads.XSLNAME]; // must already be legal URI this.outName = params[TestThreads.OUTNAME]; // must be local path/filename this.paramName = params[TestThreads.PARAMNAME]; this.paramVal = params[TestThreads.PARAMVAL]; if (params[TestThreads.OPTIONS].indexOf("polite") > 0) polite = true; if (params[TestThreads.OPTIONS].indexOf("recreate") > 0) recreate = true; if (params[TestThreads.OPTIONS].indexOf("validate") > 0) validate = true; // Optimization: only report memory if asked to and we're // in the first iteration of runners created if ((params[TestThreads.OPTIONS].indexOf("memory") > 0) && (this.runnerID.indexOf("0") >= 0)) reportMem = true; if (params[TestThreads.OPTIONS].indexOf("param") > 0) setParam = true; if (params[TestThreads.LIAISON] != null) // TRAX unused liaison = params[TestThreads.LIAISON]; } /** * Bottleneck output; both to System.out and to our private errWriter. * * NEEDSDOC @param s */ protected void println(String s) { System.out.println(s); if (errWriter != null) errWriter.println(s); } /** * Bottleneck output; both to System.out and to our private errWriter. * * NEEDSDOC @param s */ protected void print(String s) { System.out.print(s); if (errWriter != null) errWriter.print(s); } /** NEEDSDOC Field errWriter */ PrintWriter errWriter = null; /** * NEEDSDOC Method createErrWriter * */ protected void createErrWriter() { try { errWriter = new PrintWriter(new FileWriter(outName + ".log"), true); } catch (Exception e) { System.err.println("<message desc=\"" + runnerID + ":threw: " + e.toString() + "\"/>"); } } /** Main entrypoint; loop and perform lots of processes. */ public void run() { int i = 0; // loop counter; used for error reporting createErrWriter(); println("<?xml version=\"1.0\"?>"); println("<testrunner desc=\"" + runnerID + ":started\" fileName=\"" + xslName + "\">"); // TransformerFactory factory = null; XSLTProcessor processor = null; try { // Each runner creates it's own processor for use and it's own error log // factory = TransformerFactory.newInstance(); if (liaison != null) processor = XSLTProcessorFactory.getProcessorUsingLiaisonName(liaison); else processor = org.apache.xalan.xslt.XSLTProcessorFactory.getProcessor(); println("<arbitrary desc=\"" + runnerID + ":processing\">"); } catch (Throwable ex) { // If we got here, just log it and bail, no sense continuing println("<throwable desc=\"" + ex.toString() + "\"><![CDATA["); ex.printStackTrace(errWriter); println("\n</throwable>"); println("<message desc=\"" + runnerID + ":complete-ERROR:after:" + i + "\"/>"); println("</testrunner>"); if (errWriter != null) errWriter.close(); return; } try { // Loop away... for (i = 0; i < numProcesses; i++) { // Run a process using the pre-compiled stylesheet we were construced with { // Transformer transformer1 = xslStylesheet.newTransformer(); FileOutputStream resultStream1 = new FileOutputStream(outName + ".out"); // Result result1 = new StreamResult(resultStream1); //if (setParam) // transformer1.setParameter(paramName, paramVal); processor.setStylesheet(xslStylesheet); if (setParam) processor.setStylesheetParam(paramName, paramVal); print("."); // Note presence of this in logs shows which process threw an exception // transformer1.transform(new StreamSource(xmlName), result1); processor.process(new XSLTInputSource(xmlName), null, new XSLTResultTarget(resultStream1)); processor.reset(); resultStream1.close(); // Temporary vars go out of scope for cleanup here } // Now process something with a newly-processed stylesheet { // Templates templates2 = factory.newTemplates(new StreamSource(xslName)); // Transformer transformer2 = templates2.newTransformer(); FileOutputStream resultStream2 = new FileOutputStream(outName + "_.out"); // Result result2 = new StreamResult(resultStream2); //if (setParam) // transformer2.setParameter(paramName, paramVal); if (setParam) processor.setStylesheetParam(paramName, paramVal); print("*"); // Note presence of this in logs shows which process threw an exception // transformer2.transform(new StreamSource(xmlName), result2); processor.process(new XSLTInputSource(xmlName), new XSLTInputSource(xslName), new XSLTResultTarget(resultStream2)); resultStream2.close(); } // if asked, report memory statistics if (reportMem) { Runtime r = Runtime.getRuntime(); r.gc(); long freeMemory = r.freeMemory(); long totalMemory = r.totalMemory(); println("<statistic desc=\"" + runnerID + ":memory:longval-free:doubleval-total\">"); println("<longval>" + freeMemory + "</longval>"); println("<doubleval>" + totalMemory + "</doubleval>"); println("</statistic>"); } // if we're polite, let others play for a bit if (polite) java.lang.Thread.yield(); } // IF we get here, we worked without exceptions (presumably successfully) println("</arbitrary>"); println("<message desc=\"" + runnerID + ":complete-OK:after:" + numProcesses + "\"/>"); } // Separate messages for each kind of exception catch (SAXException te) { println("\n<SAXException desc=\"" + te.toString() + "\">"); logStackTrace(te, errWriter); logContainedException(te, errWriter); println("</SAXException>"); println("</arbitrary>"); println("<message desc=\"" + runnerID + ":complete-ERROR:after:" + i + "\"/>"); } catch (Throwable ex) { logThrowable(ex, errWriter); println("</arbitrary>"); println("<message desc=\"" + runnerID + ":complete-ERROR:after:" + i + "\"/>"); } finally { // Cleanup our references, etc. println("</testrunner>"); if (errWriter != null) errWriter.close(); runnerID = null; xmlName = null; xslName = null; xslStylesheet = null; outName = null; } } // end of run()... /** * NEEDSDOC Method logContainedException * * * NEEDSDOC @param parent * NEEDSDOC @param p */ private void logContainedException(SAXException parent, PrintWriter p) { Throwable containedException = parent.getException(); if (null != containedException) { println("<containedexception desc=\"" + containedException.toString() + "\">"); logStackTrace(containedException, p); println("</containedexception>"); } } /** * NEEDSDOC Method logThrowable * * * NEEDSDOC @param t * NEEDSDOC @param p */ private void logThrowable(Throwable t, PrintWriter p) { println("\n<throwable desc=\"" + t.toString() + "\">"); logStackTrace(t, p); println("</throwable>"); } /** * NEEDSDOC Method logStackTrace * * * NEEDSDOC @param t * NEEDSDOC @param p */ private void logStackTrace(Throwable t, PrintWriter p) { // Should check if (errWriter == null) println("<stacktrace><![CDATA["); t.printStackTrace(p); // Could also echo to stdout, but not really worth it println("]]></stacktrace>"); } } // end of class TestThreadsRunner... // END OF FILE
