/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package thread; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.io.StringReader; import org.apache.xerces.dom.CoreDocumentImpl; import org.apache.xerces.parsers.DOMParser; import org.apache.xerces.parsers.SAXParser; import org.w3c.dom.DOMConfiguration; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.xml.sax.AttributeList; import org.xml.sax.HandlerBase; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; /** * This program is a straight port of xerces/c/tests/ThreadTest.cpp * No particular effort has been made to make it more java-like (who cares * besides a few biggots? ;-) * * @author Andy Heninger, IBM (C++ version) * @author Arnaud Le Hors, IBM * * @version $Id$ */ public class Test { //------------------------------------------------------------------------------ // // struct InFileInfo One of these structs will be set up for each file listed // on the command line. Once set, the data is unchanging // and can safely be referenced by the test threads without // use of synchronization. // //------------------------------------------------------------------------------ class InFileInfo { public String fileName; public String fileContent; // If doing an in-memory parse, this field points // to an allocated string containing the entire file // contents. Otherwise it's 0. int checkSum; // The XML checksum. Set up by the main thread for // each file before the worker threads are started. } //------------------------------------------------------------------------------ // // struct runInfo Holds the info extracted from the command line. // There is only one of these, and it is static, and // unchanging once the command line has been parsed. // During the test, the threads will access this info without // any synchronization. // //------------------------------------------------------------------------------ final int MAXINFILES = 25; class RunInfo { boolean quiet; boolean verbose; int numThreads; boolean validating; boolean dom; boolean reuseParser; boolean inMemory; boolean dumpOnErr; int totalTime; int numInputFiles; InFileInfo files[] = new InFileInfo[MAXINFILES]; } //------------------------------------------------------------------------------ // // struct threadInfo Holds information specific to an individual thread. // One of these is set up for each thread in the test. // The main program monitors the threads by looking // at the status stored in these structs. // //------------------------------------------------------------------------------ class ThreadInfo { boolean fHeartBeat; // Set true by the thread each time it finishes // parsing a file. int fParses; // Number of parses completed. int fThreadNum; // Identifying number for this thread. ThreadInfo() { fHeartBeat = false; fParses = 0; fThreadNum = -1; } } // //------------------------------------------------------------------------------ // // Global Data // //------------------------------------------------------------------------------ RunInfo gRunInfo = new RunInfo(); ThreadInfo gThreadInfo[]; //------------------------------------------------------------------------------ // // class ThreadParser Bundles together a SAX parser and the SAX handlers // and contains the API that the rest of this test // program uses for creating parsers and doing parsing. // // Multiple instances of this class can operate concurrently // in different threads. // //------------------------------------------------------------------------------- class ThreadParser extends HandlerBase { private int fCheckSum; private SAXParser fSAXParser; private DOMParser fDOMParser; // Not really public, // These are the SAX call-back functions // that this class implements. public void characters(char chars[], int start, int length) { addToCheckSum(chars, start, length);} public void ignorableWhitespace(char chars[], int start, int length) { addToCheckSum(chars, start, length);} public void warning(SAXParseException ex) { System.err.print("*** Warning "+ ex.getMessage());} public void error(SAXParseException ex) { System.err.print("*** Error "+ ex.getMessage());} public void fatalError(SAXParseException ex) { System.err.print("***** Fatal error "+ ex.getMessage());} // // ThreadParser constructor. Invoked by the threads of the test program // to create parsers. // ThreadParser() { if (gRunInfo.dom) { // Set up to use a DOM parser fDOMParser = new org.apache.xerces.parsers.DOMParser(); try { fDOMParser.setFeature( "http://xml.org/sax/features/validation", gRunInfo.validating); } catch (Exception e) {} fDOMParser.setErrorHandler(this); } else { // Set up to use a SAX parser. fSAXParser = new org.apache.xerces.parsers.SAXParser(); try { fSAXParser.setFeature( "http://xml.org/sax/features/validation", gRunInfo.validating); } catch (Exception e) {} fSAXParser.setDocumentHandler(this); fSAXParser.setErrorHandler(this); } } //------------------------------------------------------------------------ // // parse - This is the method that is invoked by the rest of // the test program to actually parse an XML file. // // @param fileNum is an index into the gRunInfo.files array. // @return the XML checksum, or 0 if a parse error occured. // //------------------------------------------------------------------------ int parse(int fileNum) { InputSource mbis = null; InFileInfo fInfo = gRunInfo.files[fileNum]; fCheckSum = 0; if (gRunInfo.inMemory) { mbis = new InputSource(new StringReader(fInfo.fileContent)); } try { if (gRunInfo.dom) { // Do a DOM parse if (gRunInfo.inMemory) fDOMParser.parse(mbis); else fDOMParser.parse(fInfo.fileName); Document doc = fDOMParser.getDocument(); domCheckSum(doc); CoreDocumentImpl core = (CoreDocumentImpl) doc; DOMConfiguration config = core.getDomConfig(); config.setParameter("validate", Boolean.TRUE); config.setParameter("schema-type", "http://www.w3.org/2001/XMLSchema"); core.normalizeDocument(); } else { // Do a SAX parse if (gRunInfo.inMemory) fSAXParser.parse(mbis); else fSAXParser.parse(fInfo.fileName); } } catch (SAXException e) { String exceptionMessage = e.getMessage(); System.err.println(" during parsing: " + fInfo.fileName + " Exception message is: " + exceptionMessage); } catch (IOException e) { String exceptionMessage = e.getMessage(); System.err.println(" during parsing: " + fInfo.fileName + " Exception message is: " + exceptionMessage); } return fCheckSum; } // // addToCheckSum - private function, used within ThreadParser in // computing the checksum of the XML file. // private void addToCheckSum(char chars[], int start, int len) { // String with character count. int i; for (i=start; i"); for (child=node.getFirstChild(); child!=null; child=child.getNextSibling()) domPrint(child); System.out.print(""); break; } case Node.ATTRIBUTE_NODE: { System.out.print(" "); System.out.print(node.getNodeName()); // The attribute name System.out.print("= \""); System.out.print(node.getNodeValue()); // The attribute value System.out.print("\""); break; } case Node.TEXT_NODE: case Node.CDATA_SECTION_NODE: { System.out.print(node.getNodeValue()); break; } case Node.ENTITY_REFERENCE_NODE: case Node.DOCUMENT_NODE: { // For entity references and the document, nothing is dirctly // printed, but we do want to process the chidren nodes. // for (child=node.getFirstChild(); child!=null; child=child.getNextSibling()) domPrint(child); break; } } } } // class ThreadParser //---------------------------------------------------------------------- // // parseCommandLine Read through the command line, and save all // of the options in the gRunInfo struct. // // Display the usage message if the command line // is no good. // // Probably ought to be a member function of RunInfo. // //---------------------------------------------------------------------- void parseCommandLine(String argv[]) { gRunInfo.quiet = false; // Set up defaults for run. gRunInfo.verbose = false; gRunInfo.numThreads = 2; gRunInfo.validating = false; gRunInfo.dom = false; gRunInfo.reuseParser = false; gRunInfo.inMemory = false; gRunInfo.dumpOnErr = false; gRunInfo.totalTime = 0; gRunInfo.numInputFiles = 0; try // Use exceptions for command line syntax errors. { int argnum = 0; int argc = argv.length; while (argnum < argc) { if (argv[argnum].equals("-quiet")) gRunInfo.quiet = true; else if (argv[argnum].equals("-verbose")) gRunInfo.verbose = true; else if (argv[argnum].equals("-v")) gRunInfo.validating = true; else if (argv[argnum].equals("-dom")) gRunInfo.dom = true; else if (argv[argnum].equals("-reuse")) gRunInfo.reuseParser = true; else if (argv[argnum].equals("-dump")) gRunInfo.dumpOnErr = true; else if (argv[argnum].equals("-mem")) gRunInfo.inMemory = true; else if (argv[argnum].equals("-threads")) { ++argnum; if (argnum >= argc) throw new Exception(); try { gRunInfo.numThreads = Integer.parseInt(argv[argnum]); } catch (NumberFormatException e) { throw new Exception(); } if (gRunInfo.numThreads < 0) throw new Exception(); } else if (argv[argnum].equals("-time")) { ++argnum; if (argnum >= argc) throw new Exception(); try { gRunInfo.totalTime = Integer.parseInt(argv[argnum]); } catch (NumberFormatException e) { throw new Exception(); } if (gRunInfo.totalTime < 1) throw new Exception(); } else if (argv[argnum].charAt(0) == '-') { System.err.println("Unrecognized command line option. Scanning" + " \"" + argv[argnum] + "\""); throw new Exception(); } else { gRunInfo.numInputFiles++; if (gRunInfo.numInputFiles >= MAXINFILES) { System.err.println("Too many input files. Limit is " + MAXINFILES); throw new Exception(); } gRunInfo.files[gRunInfo.numInputFiles-1] = new InFileInfo(); gRunInfo.files[gRunInfo.numInputFiles-1].fileName = argv[argnum]; } argnum++; } // We've made it through the command line. // Verify that at least one input file to be parsed was specified. if (gRunInfo.numInputFiles == 0) { System.err.println("No input XML file specified on command line."); throw new Exception(); }; } catch (Exception e) { System.err.print("usage: java thread.Test [-v] [-threads nnn] [-time nnn] [-quiet] [-verbose] xmlfile...\n" + " -v Use validating parser. Non-validating is default. \n" + " -dom Use a DOM parser. Default is SAX. \n" + " -quiet Suppress periodic status display. \n" + " -verbose Display extra messages. \n" + " -reuse Retain and reuse parser. Default creates new for each parse.\n" + " -threads nnn Number of threads. Default is 2. \n" + " -time nnn Total time to run, in seconds. Default is forever.\n" + " -dump Dump DOM tree on error.\n" + " -mem Read files into memory once only, and parse them from there.\n" ); System.exit(1); } } //--------------------------------------------------------------------------- // // ReadFilesIntoMemory For use when parsing from memory rather than // reading the files each time, here is the code that // reads the files into local memory buffers. // // This function is only called once, from the main // thread, before all of the worker threads are started. // //--------------------------------------------------------------------------- void ReadFilesIntoMemory() { int fileNum; InputStreamReader fileF; char chars[] = new char[1024]; StringBuffer buf = new StringBuffer(); if (gRunInfo.inMemory) { for (fileNum = 0; fileNum 0) { buf.append(chars, 0, len); } fInfo.fileContent = buf.toString(); fileF.close(); } catch (FileNotFoundException e) { System.err.print("File not found: \"" + fInfo.fileName + "\"."); System.exit(-1); } catch (IOException e) { System.err.println("Error reading file \"" + fInfo.fileName + "\"."); System.exit(-1); } } } } //---------------------------------------------------------------------- // // threadMain The main function for each of the swarm of test threads. // Run in an infinite loop, parsing each of the documents // given on the command line in turn. // // There is no return from this fuction, and no graceful // thread termination. Threads are stuck running here // until the OS shuts them down as a consequence of the // main thread of the process (which never calls this // function) exiting. // //---------------------------------------------------------------------- class thread extends Thread { ThreadInfo thInfo; thread (ThreadInfo param) { thInfo = param; } public void run() { ThreadParser thParser = null; if (gRunInfo.verbose) System.out.println("Thread " + thInfo.fThreadNum + ": starting"); int docNum = gRunInfo.numInputFiles; // // Each time through this loop, one file will be parsed and its checksum // computed and compared with the precomputed value for that file. // while (true) { if (thParser == null) thParser = new ThreadParser(); docNum++; if (docNum >= gRunInfo.numInputFiles) docNum = 0; InFileInfo fInfo = gRunInfo.files[docNum]; if (gRunInfo.verbose ) System.out.println("Thread " + thInfo.fThreadNum + ": starting file " + fInfo.fileName); int checkSum = 0; checkSum = thParser.parse(docNum); if (checkSum != gRunInfo.files[docNum].checkSum) { System.err.println("\nThread " + thInfo.fThreadNum + ": Parse Check sum error on file \"" + fInfo.fileName + "\". Expected " + fInfo.checkSum + ", got " + checkSum); // Revisit - let the loop continue to run? int secondTryCheckSum = thParser.reCheck(); System.err.println(" Retry checksum is " + secondTryCheckSum); if (gRunInfo.dumpOnErr) thParser.domPrint(); System.out.flush(); System.exit(-1); } if (gRunInfo.reuseParser == false) { thParser = null; } thInfo.fHeartBeat = true; thInfo.fParses++; } } // run():void } // class thread //---------------------------------------------------------------------- // // main // //---------------------------------------------------------------------- void run(String argv[]) { parseCommandLine(argv); // // If we will be parsing from memory, read each of the input files // into memory now. // ReadFilesIntoMemory(); // // While we are still single threaded, parse each of the documents // once, to check for errors, and to note the checksum. // Blow off the rest of the test if there are errors. // ThreadParser mainParser = new ThreadParser(); int n; boolean errors = false; int cksum; for (n = 0; n < gRunInfo.numInputFiles; n++) { String fileName = gRunInfo.files[n].fileName; if (gRunInfo.verbose) System.out.print(fileName + " checksum is "); cksum = mainParser.parse(n); if (cksum == 0) { System.err.println("An error occured while initially parsing" + fileName); errors = true; } gRunInfo.files[n].checkSum = cksum; if (gRunInfo.verbose ) System.out.println(cksum); if (gRunInfo.dumpOnErr && errors) mainParser.domPrint(); } if (errors) System.exit(1); // // Fire off the requested number of parallel threads // if (gRunInfo.numThreads == 0) return; gThreadInfo = new ThreadInfo[gRunInfo.numThreads]; int threadNum; for (threadNum=0; threadNum < gRunInfo.numThreads; threadNum++) { gThreadInfo[threadNum] = new ThreadInfo(); gThreadInfo[threadNum].fThreadNum = threadNum; thread t = new thread(gThreadInfo[threadNum]); t.start(); } // // Loop, watching the heartbeat of the worker threads. // Each second, display "+" when all threads have completed a parse // display "." if some thread hasn't since previous "+" // long startTime = System.currentTimeMillis(); long elapsedSeconds = 0; while (gRunInfo.totalTime == 0 || gRunInfo.totalTime > elapsedSeconds) { try { Thread.sleep(1000); } catch (InterruptedException e) { // nobody would dare!! :-) } if (gRunInfo.quiet == false && gRunInfo.verbose == false) { char c = '+'; for (threadNum=0; threadNum < gRunInfo.numThreads; threadNum++) { if (gThreadInfo[threadNum].fHeartBeat == false) { c = '.'; break; } } System.out.print(c); System.out.flush(); if (c == '+') for (threadNum=0; threadNum < gRunInfo.numThreads; threadNum++) gThreadInfo[threadNum].fHeartBeat = false; } elapsedSeconds = (System.currentTimeMillis() - startTime) / 1000; }; // // Time's up, we are done. (We only get here if this was a timed run) // Tally up the total number of parses completed by each of the threads. // To Do: Run the main thread at higher priority, so that the worker threads // won't make much progress while we are adding up the results. // double totalParsesCompleted = 0; for (threadNum=0; threadNum < gRunInfo.numThreads; threadNum++) { totalParsesCompleted += gThreadInfo[threadNum].fParses; // printf("%f ", totalParsesCompleted); } double parsesPerMinute = totalParsesCompleted / (((double)gRunInfo.totalTime) / ((double)60)); System.out.println("\n" + parsesPerMinute + " parses per minute."); // The threads are still running; we just return // and leave it to the operating sytem to kill them. // System.exit(0); } static public void main(String argv[]) { Test test = new Test(); test.run(argv); } } // class Test