001: /*
002: * xtc - The eXTensible Compiler
003: * Copyright (C) 2005-2007 Robert Grimm
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public License
007: * version 2.1 as published by the Free Software Foundation.
008: *
009: * This library is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: * Lesser General Public License for more details.
013: *
014: * You should have received a copy of the GNU Lesser General Public
015: * License along with this library; if not, write to the Free Software
016: * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
017: * USA.
018: */
019: package xtc.util;
020:
021: import java.io.File;
022: import java.io.FileNotFoundException;
023: import java.io.IOException;
024: import java.io.Reader;
025: import java.io.UnsupportedEncodingException;
026:
027: import java.text.DateFormat;
028:
029: import java.util.Date;
030:
031: import xtc.Constants;
032:
033: import xtc.parser.ParseException;
034:
035: import xtc.tree.Printer;
036: import xtc.tree.Node;
037: import xtc.tree.VisitingException;
038:
039: /**
040: * The superclass of all tools.
041: *
042: * @author Robert Grimm
043: * @version $Revision: 1.26 $
044: */
045: public abstract class Tool {
046:
047: /** The runtime. */
048: protected final Runtime runtime;
049:
050: /** Create a new tool. */
051: public Tool() {
052: runtime = new Runtime();
053: }
054:
055: /**
056: * Get this tool's name.
057: *
058: * @return The name.
059: */
060: public abstract String getName();
061:
062: /**
063: * Get this tool's version. The default implementation returns
064: * {@link Constants#VERSION}.
065: *
066: * @return The version.
067: */
068: public String getVersion() {
069: return Constants.VERSION;
070: }
071:
072: /**
073: * Get this tool's copyright. The default implementation returns
074: * {@link Constants#FULL_COPY}.
075: *
076: * @return The copyright.
077: */
078: public String getCopy() {
079: return Constants.FULL_COPY;
080: }
081:
082: /**
083: * Get this tool's explanation. This method should return any text
084: * to print after this tool's description of options. The text is
085: * automatically line-wrapped. The default implementation returns
086: * <code>null</code> to indicate that there is no explanation.
087: *
088: * @return The explanation.
089: */
090: public String getExplanation() {
091: return null;
092: }
093:
094: /**
095: * Initialize this tool. This method declares this tool's command
096: * line options. The default implementation declares<ul>
097: *
098: * <li>a boolean option <code>optionSilent</code> for silent
099: * operation,</li>
100: *
101: * <li>a boolean option <code>optionVerbose</code> for verbose
102: * operation,</li>
103: *
104: * <li>a boolean option <code>optionNoExit</code> for not exiting
105: * the Java virtual machine,</li>
106: *
107: * <li>a multiple directory option {@link Runtime#INPUT_DIRECTORY}
108: * for the file search path,</li>
109: *
110: * <li>a directory option {@link Runtime#OUTPUT_DIRECTORY} for the
111: * output directory,</li>
112: *
113: * <li>a word option {@link Runtime#INPUT_ENCODING} for the
114: * character encoding when reading files,</li>
115: *
116: * <li>a word option {@link Runtime#OUTPUT_ENCODING} for the
117: * character encoding when writing files,<li>
118: *
119: * <li>a boolean option <code>optionDiagnostics</code> for printing
120: * tool diagnostics,</li>
121:
122: * <li>a boolean option <code>optionPerformance</code> for
123: * collecting performance statistics,</li>
124: *
125: * <li>a boolean option <code>optionMeasureParser</code> for
126: * measuring parser performance only.</li>
127: *
128: * <li>a boolean option <code>optionMeasureProcessing</code> for
129: * measuring processing performance only.</li>
130: *
131: * <li>a boolean option <code>optionGC</code> for performing
132: * GC,</li>
133: *
134: * <li>an integer option <code>runsWarmUp</code> with a default of 2
135: * for the number of warm-up runs,</li>
136: *
137: * <li>and an integer option <code>runsTotal</code> with a default
138: * of 12 for the total number of runs.</li>
139: *
140: * </ul>
141: */
142: public void init() {
143: runtime
144: .bool("silent", "optionSilent", false,
145: "Enable silent operation.")
146: .bool("verbose", "optionVerbose", false,
147: "Enable verbose operation.")
148: .bool("no-exit", "optionNoExit", false,
149: "Do not explicitly exit the Java virtual machine.")
150: .dir("in", Runtime.INPUT_DIRECTORY, true,
151: "Add the specified directory to the file search path.")
152: .dir("out", Runtime.OUTPUT_DIRECTORY, false,
153: "Use the specified directory for output.")
154: .word("enc-in", Runtime.INPUT_ENCODING, false,
155: "Use the specified character encoding for input.")
156: .word("enc-out", Runtime.OUTPUT_ENCODING, false,
157: "Use the specified character encoding for output.")
158: .bool("diagnostics", "optionDiagnostics", false,
159: "Print diagnostics for internal tool state.")
160: .bool("performance", "optionPerformance", false,
161: "Collect and print performance statistics.")
162: .bool("measureParser", "optionMeasureParser", false,
163: "Measure parser performance only.")
164: .bool("measureProcess", "optionMeasureProcessing",
165: false, "Measure processing performance only.")
166: .bool("gc", "optionGC", false,
167: "Perform GC before each operation.")
168: .number("warmupRuns", "runsWarmUp", 2,
169: "Perform the specified number of warm-up runs. The default is 2.")
170: .number("totalRuns", "runsTotal", 12,
171: "Perform the specified number of total runs. The default is 12.");
172: }
173:
174: /**
175: * Prepare for processing. This method prepares for actually
176: * processing files, for example, by performing consistency checks
177: * between command line arguments and by initializing all default
178: * values not specified on the command line. The default
179: * implementation invokes {@link Runtime#initDefaultValues()}. It
180: * also checks that the <code>optionSilent</code> and
181: * <code>optionVerbose</code> flags are not both set at the same
182: * time.
183: *
184: * @see #wrapUp()
185: */
186: public void prepare() {
187: runtime.initDefaultValues();
188: if (runtime.test("optionSilent")
189: && runtime.test("optionVerbose")) {
190: runtime
191: .error("can't run in silent and verbose mode at the same time");
192: }
193: if (runtime.test("optionMeasureParser")
194: && runtime.test("optionMeasureProcessing")) {
195: runtime
196: .error("can't measure just parsing and just processing at the "
197: + "same time");
198: }
199: if (runtime.test("optionMeasureParser")
200: && (!runtime.test("optionPerformance"))) {
201: runtime.setValue("optionPerformance", true);
202: }
203: if (runtime.test("optionMeasureProcessing")
204: && (!runtime.test("optionPerformance"))) {
205: runtime.setValue("optionPerformance", true);
206: }
207: }
208:
209: /**
210: * Print tool diagnostics. The default implementation of this
211: * method does nothing.
212: */
213: public void diagnose() {
214: // Nothing to do.
215: }
216:
217: /**
218: * Locate the file with the specified name. The default
219: * implementation simply looks in the current directory, ignoring
220: * any directories in the tool's search path.
221: *
222: * @see Runtime#locate(String)
223: *
224: * @param name The file name.
225: * @return The corresponding file.
226: * @throws IllegalArgumentException Signals an inappropriate file
227: * (e.g., one that is too large).
228: * @throws IOException Signals an I/O error.
229: */
230: public File locate(String name) throws IOException {
231: File file = new File(name);
232: if (!file.exists()) {
233: throw new FileNotFoundException(name + ": not found");
234: }
235: return file;
236: }
237:
238: /**
239: * Parse the specified file.
240: *
241: * @param in The input stream for the file.
242: * @param file The corresponding file.
243: * @return The AST corresponding to the file's contents, or
244: * <code>null</code> if no tree has been generated.
245: * @throws IllegalArgumentException Signals an inappropriate file
246: * (e.g., one that is too large).
247: * @throws IOException Signals an I/O error.
248: * @throws ParseException Signals a parse error.
249: */
250: public abstract Node parse(Reader in, File file)
251: throws IOException, ParseException;
252:
253: /**
254: * Process the specified AST node. This method is only invoked if
255: * {@link #parse(Reader,File)} has completed successfuly, has
256: * returned a node (and not <code>null</code>), and no errors have
257: * been reported through {@link Runtime#error()}, {@link
258: * Runtime#error(String)}, or {@link Runtime#error(String,Node)}
259: * while parsing. The default implementation of this method does
260: * nothing.
261: *
262: * @param node The node.
263: */
264: public void process(Node node) {
265: // Nothing to do.
266: }
267:
268: /**
269: * Recursively process the file with the specified name. This
270: * method {@link #locate(String) locates} the specified file, opens
271: * it, {@link #parse(Reader,File) parses} it, closes it, and then
272: * {@link #process(Node) processes} the resulting AST node.
273: *
274: * @param name The file name.
275: * @throws IllegalArgumentException Signals an inappropriate file
276: * (e.g., one that is too large).
277: * @throws FileNotFoundException Signals that the file was not
278: * found.
279: * @throws IOException Signals an I/O error while accessing the
280: * file.
281: * @throws ParseException Signals a parse error.
282: * @throws VisitingException Signals an error while visiting a node.
283: */
284: public void process(String name) throws IOException, ParseException {
285: // Locate the file.
286: File file = locate(name);
287:
288: // Open the file.
289: Reader in = runtime.getReader(file);
290:
291: // Parse the file.
292: Node root;
293: try {
294: root = parse(in, file);
295: } finally {
296: // Close the file.
297: try {
298: in.close();
299: } catch (IOException x) {
300: // Ignore.
301: }
302: }
303:
304: // Process the AST.
305: process(root);
306: }
307:
308: /**
309: * Print a tool header to the specified printer. This method prints
310: * a header documenting the tool name, version, copyright, and
311: * current time. It also prints a warning not to edit the result.
312: *
313: * @param printer The printer.
314: */
315: public void printHeader(Printer printer) {
316: printer.sep();
317: printer.indent().pln("// This file has been generated by");
318: printer.indent().p("// ").p(getName()).p(", version ").p(
319: getVersion()).pln(',');
320: printer.p("// ").p(getCopy()).pln(',');
321: Date now = new Date();
322: printer.indent().p("// on ")
323: .p(
324: DateFormat.getDateInstance(DateFormat.FULL)
325: .format(now)).p(" at ").p(
326: DateFormat.getTimeInstance(DateFormat.MEDIUM)
327: .format(now)).pln('.');
328: printer.indent().pln("// Edit at your own risk.");
329: printer.sep();
330: printer.pln();
331: }
332:
333: /**
334: * Wrap up this tool. This method is invoked after all files have
335: * been processed. The default implementation does nothing.
336: */
337: public void wrapUp() {
338: // Nothing to do.
339: }
340:
341: /**
342: * Run this tool with the specified command line arguments. This
343: * method works as following:<ol>
344: *
345: * <li>It calls {@link #init()} to initialize this tool.</li>
346: *
347: * <li>It prints the {@link #getName() name}, {@link #getVersion()
348: * version}, and {@link #getCopy() copyright} to the {@link
349: * Runtime#console() console}.</li>
350: *
351: * <li>If this tool has been invoked without arguments, it prints a
352: * description of all command line {@link Runtime#printOptions()
353: * options} and, optionally, an {@link #getExplanation()
354: * explanation}. It then exits.</li>
355: *
356: * <li>It {@link Runtime#process(String[]) processes} the specified
357: * command line arguments and {@link #prepare() prepares} for
358: * processing the files. If any errors have been {@link
359: * Runtime#seenError() reported} during the two method calls, it
360: * exits.</li>
361: *
362: * <li>For each file name specified on the command line, it {@link
363: * #locate(String) locates} the file, {@link #parse(Reader,File)
364: * parses} the contents, and {@link #process(Node) processes} the
365: * resulting AST. If the <code>-performance</code> command line
366: * option has been specified, it repeatedly parses and processes
367: * each file, measuring both latency and heap utilization. It then
368: * exits.</li>
369: *
370: * </ol>
371: *
372: * @param args The command line arguments.
373: */
374: public void run(String[] args) {
375: // Initialize this tool.
376: init();
377:
378: // Print the tool description and exit if there are no arguments.
379: if (0 == args.length) {
380: runtime.console().p(getName()).p(", v. ").p(getVersion())
381: .p(", ").pln(getCopy());
382: runtime.console().pln()
383: .pln("Usage: <option>* <file-name>+").pln().pln(
384: "Options are:");
385: runtime.printOptions();
386:
387: final String explanation = getExplanation();
388: if (null != explanation) {
389: runtime.console().pln().wrap(0, explanation).pln();
390: }
391: runtime.console().pln().flush();
392: if (runtime.hasValue("optionNoExit")
393: && runtime.test("optionNoExit")) {
394: return;
395: } else {
396: runtime.exit();
397: }
398: }
399:
400: // Process the command line arguments and print tool name.
401: int index = runtime.process(args);
402:
403: if ((!runtime.hasValue("optionSilent"))
404: || (!runtime.test("optionSilent"))) {
405: runtime.console().p(getName()).p(", v. ").p(getVersion())
406: .p(", ").pln(getCopy()).flush();
407: }
408:
409: final boolean diagnose = runtime.hasValue("optionDiagnostics")
410: && runtime.test("optionDiagnostics");
411:
412: if (index >= args.length && !diagnose) {
413: runtime.error("no file names specified");
414: }
415:
416: // Prepare for processing the files.
417: prepare();
418:
419: // Print diagnostics.
420: if (diagnose)
421: diagnose();
422:
423: // Stop if there have been errors already.
424: if (runtime.seenError()) {
425: if (runtime.test("optionNoExit")) {
426: return;
427: } else {
428: runtime.exit();
429: }
430: }
431:
432: // Process the files.
433: final boolean silent = runtime.test("optionSilent");
434: final boolean doGC = runtime.test("optionGC");
435: final boolean measure = runtime.test("optionPerformance");
436: final boolean parserOnly = runtime.test("optionMeasureParser");
437: final boolean processOnly = runtime
438: .test("optionMeasureProcessing");
439: final int warmUp = measure ? runtime.getInt("runsWarmUp") : 0;
440: final int total = measure ? runtime.getInt("runsTotal") : 1;
441: final Statistics time = measure ? new Statistics() : null;
442: final Statistics memory = measure ? new Statistics() : null;
443: final Statistics fileSizes = measure ? new Statistics() : null;
444: final Statistics latencies = measure ? new Statistics() : null;
445: final Statistics heapSizes = measure ? new Statistics() : null;
446:
447: // If measuring, we need to print a legend.
448: if (measure) {
449: runtime.console().p(
450: "Legend: file, size, time (ave, med, stdev), ")
451: .pln("memory (ave, med, stdev)").pln().flush();
452: }
453:
454: while (index < args.length) {
455: // If we are neither silent nor measuring, report on activity.
456: if ((!silent) && (!measure)) {
457: runtime.console().p("Processing ").p(args[index]).pln(
458: " ...").flush();
459: }
460:
461: // Locate the file.
462: File file = null;
463:
464: try {
465: file = locate(args[index]);
466:
467: } catch (IllegalArgumentException x) {
468: runtime.error(x.getMessage());
469:
470: } catch (FileNotFoundException x) {
471: runtime.error(x.getMessage());
472:
473: } catch (IOException x) {
474: if (null == x.getMessage()) {
475: runtime.error(args[index] + ": I/O error");
476: } else {
477: runtime.error(args[index] + ": " + x.getMessage());
478: }
479:
480: } catch (Throwable x) {
481: runtime.error();
482: x.printStackTrace();
483: }
484:
485: // Parse and process the file.
486: if (null != file) {
487: if (measure) {
488: time.reset();
489: memory.reset();
490: }
491:
492: for (int i = 0; i < total; i++) {
493: Node ast = null;
494: boolean success = false;
495:
496: // Perform GC if requested.
497: if (doGC) {
498: System.gc();
499: }
500:
501: // Measure performance if requested.
502: long startTime = 0;
503: long startMemory = 0;
504: if (measure && (!processOnly)) {
505: startMemory = java.lang.Runtime.getRuntime()
506: .freeMemory();
507: startTime = System.currentTimeMillis();
508: }
509:
510: // Parse the input.
511: Reader in = null;
512: try {
513: in = runtime.getReader(file);
514: ast = parse(in, file);
515: success = true;
516:
517: } catch (IllegalArgumentException x) {
518: runtime.error(x.getMessage());
519:
520: } catch (FileNotFoundException x) {
521: runtime.error(x.getMessage());
522:
523: } catch (UnsupportedEncodingException x) {
524: runtime.error(x.getMessage());
525:
526: } catch (IOException x) {
527: if (null == x.getMessage()) {
528: runtime.error(args[index] + ": I/O error");
529: } else {
530: runtime.error(args[index] + ": "
531: + x.getMessage());
532: }
533:
534: } catch (ParseException x) {
535: runtime.error();
536: System.err.print(x.getMessage());
537:
538: } catch (Throwable x) {
539: runtime.error();
540: x.printStackTrace();
541:
542: } finally {
543: if (null != in) {
544: try {
545: in.close();
546: } catch (IOException x) {
547: // Nothing to see here. Move on.
548: }
549: }
550: }
551:
552: if (success && (null != ast) && (!parserOnly)) {
553: // Measure processing only if requested.
554: if (measure && processOnly) {
555: startMemory = java.lang.Runtime
556: .getRuntime().freeMemory();
557: startTime = System.currentTimeMillis();
558: }
559:
560: // Process the AST.
561: try {
562: process(ast);
563: } catch (VisitingException x) {
564: runtime.error();
565: x.getCause().printStackTrace();
566: } catch (Throwable x) {
567: runtime.error();
568: x.printStackTrace();
569: }
570: }
571:
572: // Collect performance data for this run if requested.
573: if (measure) {
574: final long endTime = System.currentTimeMillis();
575: final long endMemory = java.lang.Runtime
576: .getRuntime().freeMemory();
577:
578: if (i >= warmUp) {
579: time.add(endTime - startTime);
580: memory.add(startMemory - endMemory);
581: }
582: }
583: }
584:
585: // Collect performance data for all the file's runs if
586: // requested.
587: if (measure) {
588: final long fileSize = file.length();
589: final double latency = time.mean();
590: final double heapSize = memory.mean();
591:
592: fileSizes.add(fileSize / 1024.0);
593: latencies.add(latency);
594: heapSizes.add(heapSize / 1024.0);
595:
596: runtime.console().p(args[index]).p(' ').p(fileSize)
597: .p(' ').p(Statistics.round(latency)).p(' ')
598: .p(time.median()).p(' ').p(
599: Statistics.round(time.stdev())).p(
600: ' ').p(Statistics.round(heapSize))
601: .p(' ').p(memory.median()).p(' ').pln(
602: Statistics.round(memory.stdev()))
603: .flush();
604: }
605: }
606:
607: // Next file.
608: index++;
609: }
610:
611: // Wrap up.
612: wrapUp();
613:
614: // Print overall statistics, if requested.
615: if (measure) {
616: final double totalTime = latencies.sum();
617: final double totalMemory = heapSizes.sum();
618: final double throughput = 1000.0 / Statistics.fitSlope(
619: fileSizes, latencies);
620: final double heapUtil = Statistics.fitSlope(fileSizes,
621: heapSizes);
622:
623: runtime.console().pln().p("Total time : ").p(
624: Statistics.round(totalTime)).pln(" ms").p(
625: "Total memory : ").p(
626: Statistics.round(totalMemory)).pln(" KB").p(
627: "Average throughput : ").p(
628: Statistics.round(throughput)).pln(" KB/s").p(
629: "Average heap utilization : ").p(
630: Statistics.round(heapUtil)).pln(":1").flush();
631: }
632:
633: // Done.
634: if (!runtime.test("optionNoExit"))
635: runtime.exit();
636: }
637:
638: }
|