001: /*
002: * Spoon - http://spoon.gforge.inria.fr/
003: * Copyright (C) 2006 INRIA Futurs <renaud.pawlak@inria.fr>
004: *
005: * This software is governed by the CeCILL-C License under French law and
006: * abiding by the rules of distribution of free software. You can use, modify
007: * and/or redistribute the software under the terms of the CeCILL-C license as
008: * circulated by CEA, CNRS and INRIA at http://www.cecill.info.
009: *
010: * This program is distributed in the hope that it will be useful, but WITHOUT
011: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
012: * FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details.
013: *
014: * The fact that you are presently reading this means that you have had
015: * knowledge of the CeCILL-C license and that you accept its terms.
016: */
017:
018: package spoon;
019:
020: import java.io.File;
021: import java.io.FileNotFoundException;
022: import java.io.IOException;
023: import java.io.InputStream;
024: import java.lang.reflect.Method;
025: import java.util.ArrayList;
026: import java.util.List;
027:
028: import org.xml.sax.InputSource;
029: import org.xml.sax.SAXException;
030: import org.xml.sax.XMLReader;
031: import org.xml.sax.helpers.XMLReaderFactory;
032:
033: import spoon.processing.Builder;
034: import spoon.processing.FileGenerator;
035: import spoon.processing.ProcessingManager;
036: import spoon.processing.Severity;
037: import spoon.reflect.Factory;
038: import spoon.support.DefaultCoreFactory;
039: import spoon.support.JavaOutputProcessor;
040: import spoon.support.QueueProcessingManager;
041: import spoon.support.StandardEnvironment;
042: import spoon.support.builder.CtFile;
043: import spoon.support.builder.CtFolder;
044: import spoon.support.builder.CtResource;
045: import spoon.support.builder.FileFactory;
046: import spoon.support.builder.support.CtFolderZip;
047: import spoon.support.processing.SpoonletXmlHandler;
048:
049: import com.martiansoftware.jsap.FlaggedOption;
050: import com.martiansoftware.jsap.JSAP;
051: import com.martiansoftware.jsap.JSAPException;
052: import com.martiansoftware.jsap.JSAPResult;
053: import com.martiansoftware.jsap.Switch;
054: import com.martiansoftware.jsap.UnflaggedOption;
055: import com.martiansoftware.jsap.stringparsers.FileStringParser;
056:
057: /**
058: * This abstract class defines the common tasks and options for launching a
059: * program processing. To be subclassed for concrete launchers.
060: */
061: public abstract class AbstractLauncher {
062:
063: private String[] args = new String[0];
064:
065: private JSAPResult arguments;
066:
067: private Factory factory;
068:
069: private List<CtResource> inputResources = new ArrayList<CtResource>();
070:
071: /**
072: * Contains the arguments accepted by this launcher (available after
073: * construction and accessible by sub-classes).
074: */
075: protected JSAP jsapArgs;
076:
077: private List<String> processors = new ArrayList<String>();
078:
079: private List<CtResource> templateResources = new ArrayList<CtResource>();
080:
081: /**
082: * Constructor with no arguments.
083: */
084: protected AbstractLauncher() throws JSAPException {
085: jsapArgs = defineArgs();
086: }
087:
088: /**
089: * Default constructor takes the command-line arguments.
090: */
091: protected AbstractLauncher(String[] args) throws JSAPException {
092: this ();
093: this .args = args;
094: }
095:
096: /**
097: * Adds an input resource to be processed by Spoon.
098: */
099: public void addInputResource(CtResource resource) {
100: inputResources.add(resource);
101: }
102:
103: /**
104: * Adds a processor.
105: */
106: public void addProcessor(String name) {
107: processors.add(name);
108: }
109:
110: /**
111: * Adds a resource that contains a template (usually a source File).
112: */
113: public void addTemplateResource(CtResource resource) {
114: templateResources.add(resource);
115: }
116:
117: /**
118: * Do the model building.
119: *
120: * @return true if the Java sources was succesfully compiled by the core
121: * Java compiler, false if some errors were encountered
122: */
123: protected boolean build() {
124: // building
125: Builder builder = getFactory().getBuilder();
126:
127: try {
128: for (CtResource f : getInputSources()) {
129: builder.addInputSource(f);
130: }
131: for (CtResource f : getTemplateSources()) {
132: builder.addTemplateSource(f);
133: }
134: } catch (IOException e) {
135: getFactory().getEnvironment().report(null, Severity.ERROR,
136: "Error while loading resource : " + e.getMessage());
137: if (getFactory().getEnvironment().isDebug())
138: e.printStackTrace();
139: }
140: boolean success = false;
141: try {
142: success = builder.build();
143: } catch (Exception e) {
144: getFactory().getEnvironment().report(null, Severity.ERROR,
145: "Error while loading resource : " + e.getMessage());
146: if (getFactory().getEnvironment().isDebug())
147: e.printStackTrace();
148: }
149: return success;
150: }
151:
152: /**
153: * Creates the factory and associated environment for constructing the
154: * model, initialized with the launcher's arguments.
155: */
156: protected Factory createFactory() {
157: StandardEnvironment env = new StandardEnvironment();
158: Factory factory = new Factory(new DefaultCoreFactory(), env);
159:
160: // environment initialization
161: env.setComplianceLevel(getArguments().getInt("compliance"));
162: env.setVerbose(true);
163: env.setXmlRootFolder(getArguments().getFile("properties"));
164:
165: JavaOutputProcessor printer = new JavaOutputProcessor(
166: getArguments().getFile("output"));
167: env.setDefaultFileGenerator(printer);
168:
169: env.setVerbose(getArguments().getBoolean("verbose")
170: || getArguments().getBoolean("debug"));
171: env.setDebug(getArguments().getBoolean("debug"));
172:
173: env.setTabulationSize(getArguments().getInt("tabsize"));
174: env.useTabulations(getArguments().getBoolean("tabs"));
175: env.useSourceCodeFragments(getArguments().getBoolean(
176: "fragments"));
177: return factory;
178: }
179:
180: /**
181: * Defines the common arguments for sub-launchers.
182: *
183: * @return the JSAP arguments
184: * @throws JSAPException
185: * when the creation fails
186: */
187: protected JSAP defineArgs() throws JSAPException {
188: // Verbose output
189: JSAP jsap = new JSAP();
190:
191: // help
192: Switch sw1 = new Switch("help");
193: sw1.setShortFlag('h');
194: sw1.setLongFlag("help");
195: sw1.setDefault("false");
196: jsap.registerParameter(sw1);
197:
198: // Verbose
199: sw1 = new Switch("verbose");
200: sw1.setShortFlag('v');
201: sw1.setLongFlag("verbose");
202: sw1.setDefault("false");
203: sw1.setHelp("Output messages about what the compiler is doing");
204: jsap.registerParameter(sw1);
205:
206: // Tabs
207: sw1 = new Switch("tabs");
208: sw1.setLongFlag("tabs");
209: sw1.setDefault("false");
210: sw1
211: .setHelp("Use tabulations instead of spaces in the generated code (use spaces by default)");
212: jsap.registerParameter(sw1);
213:
214: // Tabs
215: sw1 = new Switch("fragments");
216: sw1.setLongFlag("fragments");
217: sw1.setShortFlag('f');
218: sw1.setDefault("false");
219: sw1
220: .setHelp("Use source code fragments to generate source code (preserve formatting)");
221: jsap.registerParameter(sw1);
222:
223: // Tab size
224: FlaggedOption opt2 = new FlaggedOption("tabsize");
225: opt2.setLongFlag("tabsize");
226: opt2.setStringParser(JSAP.INTEGER_PARSER);
227: opt2.setDefault("4");
228: opt2.setHelp("Define tabulation size");
229: jsap.registerParameter(opt2);
230:
231: // Super Verbose
232: sw1 = new Switch("debug");
233: sw1.setLongFlag("vvv");
234: sw1.setDefault("false");
235: sw1.setHelp("Generate all debugging info");
236: jsap.registerParameter(sw1);
237:
238: // java compliance
239: opt2 = new FlaggedOption("compliance");
240: opt2.setLongFlag("compliance");
241: opt2.setHelp("set java compliance level (1,2,3,4,5 or 6)");
242: opt2.setStringParser(JSAP.INTEGER_PARSER);
243: opt2.setDefault("5");
244: jsap.registerParameter(opt2);
245:
246: // setting Input files & Directory
247: opt2 = new FlaggedOption("spoonlet");
248: opt2.setShortFlag('s');
249: opt2.setLongFlag("spoonlet");
250: opt2.setStringParser(JSAP.STRING_PARSER);
251: opt2.setRequired(false);
252: opt2.setHelp("List of spoonlet files to load");
253: jsap.registerParameter(opt2);
254:
255: // setting Input files & Directory
256: opt2 = new FlaggedOption("input");
257: opt2.setShortFlag('i');
258: opt2.setLongFlag("input");
259: opt2.setStringParser(JSAP.STRING_PARSER);
260: opt2.setRequired(false);
261: opt2.setHelp("List of path to sources files");
262: jsap.registerParameter(opt2);
263:
264: // Processor qualified name
265: opt2 = new FlaggedOption("processors");
266: opt2.setShortFlag('p');
267: opt2.setLongFlag("processors");
268: opt2.setHelp("List of processor's qualified name to be used");
269: opt2.setStringParser(JSAP.STRING_PARSER);
270: opt2.setRequired(false);
271: jsap.registerParameter(opt2);
272:
273: // setting input template
274: opt2 = new FlaggedOption("template");
275: opt2.setShortFlag('t');
276: opt2.setLongFlag("template");
277: opt2.setHelp("list of source templates");
278: opt2.setStringParser(JSAP.STRING_PARSER);
279: opt2.setRequired(false);
280: opt2.setHelp("list of path to templates java files");
281: jsap.registerParameter(opt2);
282:
283: // Spooned output directory
284: opt2 = new FlaggedOption("output");
285: opt2.setShortFlag('o');
286: opt2.setLongFlag("output");
287: opt2.setDefault("spooned");
288: opt2.setHelp("specify where to place generated java files");
289: opt2.setStringParser(FileStringParser.getParser());
290: opt2.setRequired(false);
291: jsap.registerParameter(opt2);
292:
293: // Location of properties files
294: opt2 = new FlaggedOption("properties");
295: opt2.setLongFlag("properties");
296: opt2.setStringParser(FileStringParser.getParser());
297: opt2.setRequired(false);
298: opt2.setHelp("Directory to search for spoon properties files");
299: jsap.registerParameter(opt2);
300:
301: // class to be run
302: UnflaggedOption opt3 = new UnflaggedOption("class");
303: opt3.setStringParser(JSAP.STRING_PARSER);
304: opt3.setRequired(false);
305: opt3
306: .setHelp("class to launch within the Spoon context (Main class)");
307: jsap.registerParameter(opt3);
308:
309: opt3 = new UnflaggedOption("arguments");
310: opt3.setStringParser(JSAP.STRING_PARSER);
311: opt3.setRequired(false);
312: opt3.setGreedy(true);
313: opt3.setHelp("parameters to be passed to the main method");
314: jsap.registerParameter(opt3);
315:
316: return jsap;
317: }
318:
319: /**
320: * Returns the command-line given launching arguments in JSAP format.
321: */
322: protected final JSAPResult getArguments() {
323: if (arguments == null) {
324: try {
325: arguments = parseArgs(args);
326: } catch (JSAPException e) {
327: throw new RuntimeException(e);
328: }
329: }
330: return arguments;
331: }
332:
333: /**
334: * Gets the factory which contains the built model.
335: */
336: public final Factory getFactory() {
337: if (factory == null) {
338: factory = createFactory();
339: }
340: return factory;
341: }
342:
343: /**
344: * Processes the arguments.
345: */
346: protected void processArguments() {
347:
348: if (getArguments().getString("input") != null) {
349: for (String s : getArguments().getString("input").split(
350: "[" + File.pathSeparatorChar + "]")) {
351: try {
352: inputResources.add(FileFactory
353: .createResource(new File(s)));
354: } catch (FileNotFoundException e) {
355: getFactory().getEnvironment().report(
356: null,
357: Severity.ERROR,
358: "Unable to add source file : "
359: + e.getMessage());
360: if (getFactory().getEnvironment().isDebug())
361: e.printStackTrace();
362: }
363: }
364: }
365:
366: if (getArguments().getString("spoonlet") != null) {
367: for (String s : getArguments().getString("spoonlet").split(
368: "[" + File.pathSeparatorChar + "]")) {
369: loadSpoonlet(new File(s));
370: }
371: }
372:
373: // Adding template from command-line
374: if (getArguments().getString("template") != null) {
375: for (String s : getArguments().getString("template").split(
376: "[" + File.pathSeparatorChar + "]")) {
377: try {
378: addTemplateResource(FileFactory
379: .createResource(new File(s)));
380: } catch (FileNotFoundException e) {
381: getFactory().getEnvironment().report(
382: null,
383: Severity.ERROR,
384: "Unable to add template file: "
385: + e.getMessage());
386: if (getFactory().getEnvironment().isDebug())
387: e.printStackTrace();
388: }
389: }
390: }
391:
392: if (getArguments().getString("processors") != null) {
393: for (String processorName : getArguments().getString(
394: "processors").split(File.pathSeparator)) {
395: addProcessor(processorName);
396: }
397: }
398: }
399:
400: /**
401: * Gets the list of input sources as files. This method can be overriden to
402: * customize this list.
403: */
404: protected java.util.List<CtResource> getInputSources() {
405: return inputResources;
406: }
407:
408: /**
409: * Gets the list of processor types to be initially applied during the
410: * processing (-p option).
411: */
412: protected java.util.List<String> getProcessorTypes() {
413: return processors;
414: }
415:
416: /**
417: * Gets the list of template sources as files.
418: */
419: protected List<CtResource> getTemplateSources() {
420: return templateResources;
421: }
422:
423: /**
424: * Load content of spoonlet file (template and processor list).
425: */
426: protected void loadSpoonlet(File spoonletFile) {
427: CtFolder folder;
428: try {
429: folder = new CtFolderZip(spoonletFile);
430: } catch (IOException e) {
431: getFactory().getEnvironment().report(null, Severity.ERROR,
432: "Unable to load spoonlet: " + e.getMessage());
433: if (getFactory().getEnvironment().isDebug())
434: e.printStackTrace();
435: return;
436: }
437: List<CtResource> spoonletIndex = new ArrayList<CtResource>();
438: CtFile configFile = null;
439: for (CtFile file : folder.getAllFiles()) {
440: if (file.isJava())
441: spoonletIndex.add(file);
442: else if (file.getName().endsWith("spoon.xml")) {
443: // Loading spoonlet properties
444: configFile = file;
445: }
446: }
447:
448: if (configFile == null) {
449: getFactory().getEnvironment().report(
450: null,
451: Severity.ERROR,
452: "No configuration file in spoonlet "
453: + spoonletFile.getName());
454: } else {
455: try {
456: XMLReader xr = XMLReaderFactory.createXMLReader();
457: SpoonletXmlHandler loader = new SpoonletXmlHandler(
458: this , spoonletIndex);
459: xr.setContentHandler(loader);
460: InputStream stream = configFile.getContent();
461: xr.parse(new InputSource(stream));
462: stream.close();
463: } catch (SAXException e) {
464: e.printStackTrace();
465: } catch (IOException e) {
466: e.printStackTrace();
467: }
468: }
469:
470: }
471:
472: /**
473: * Parses the arguments given by the command line.
474: *
475: * @param args
476: * the command-line arguments as a string array
477: * @return the JSAP-presented arguments
478: * @throws JSAPException
479: * when an error occurs in the argument parsing
480: */
481: protected JSAPResult parseArgs(String[] args) throws JSAPException {
482: JSAPResult arguments = jsapArgs.parse(args);
483: if (!arguments.success()) {
484: // print out specific error messages describing the problems
485: for (java.util.Iterator<?> errs = arguments
486: .getErrorMessageIterator(); errs.hasNext();) {
487: System.err.println("Error: " + errs.next());
488: }
489: }
490: if (!arguments.success() || arguments.getBoolean("help")) {
491: System.err.println();
492: System.err
493: .println("Usage: java <launcher name> [option(s)]");
494: System.err.println();
495: System.err.println("Options : ");
496: System.err.println();
497: System.err.println(jsapArgs.getHelp());
498: System.exit(-1);
499: }
500:
501: return arguments;
502: }
503:
504: /**
505: * Prints out the built model into files.
506: */
507: protected void print() {
508: if (getFactory().getEnvironment().getDefaultFileGenerator() != null) {
509: ProcessingManager processing = new QueueProcessingManager(
510: getFactory());
511: processing.addProcessor(getFactory().getEnvironment()
512: .getDefaultFileGenerator());
513: processing.process();
514: }
515: }
516:
517: /**
518: * Processes the built model with the processors.
519: */
520: protected void process() {
521: // processing (consume all the processors)
522: ProcessingManager processing = new QueueProcessingManager(
523: getFactory());
524: for (String processorName : getProcessorTypes()) {
525: processing.addProcessor(processorName);
526: getFactory().getEnvironment().debugMessage(
527: "Loaded processor " + processorName + ".");
528: }
529:
530: processing.process();
531: }
532:
533: /**
534: * Starts the Spoon processing.
535: */
536: public void run() throws Exception {
537:
538: getFactory().getEnvironment().reportProgressMessage(
539: "Spoon version 1.4");
540:
541: getFactory().getEnvironment().debugMessage(
542: "loading command-line arguments...");
543: processArguments();
544:
545: if (arguments.getBoolean("fragments")) {
546: getFactory()
547: .getEnvironment()
548: .reportProgressMessage(
549: "running in 'fragments' mode: AST changes will be ignored");
550: }
551: getFactory().getEnvironment().reportProgressMessage(
552: "start processing...");
553:
554: long t = System.currentTimeMillis();
555: long tstart = t;
556: build();
557: getFactory().getEnvironment().debugMessage(
558: "model built in " + (System.currentTimeMillis() - t)
559: + " ms");
560: t = System.currentTimeMillis();
561: process();
562: getFactory().getEnvironment().debugMessage(
563: "model processed in "
564: + (System.currentTimeMillis() - t) + " ms");
565: t = System.currentTimeMillis();
566: print();
567: FileGenerator<?> fg = getFactory().getEnvironment()
568: .getDefaultFileGenerator();
569: if (fg != null) {
570: if (arguments.getBoolean("compile")) {
571: getFactory().getEnvironment().debugMessage(
572: "generated bytecode in "
573: + (System.currentTimeMillis() - t)
574: + " ms");
575: } else {
576: getFactory().getEnvironment().debugMessage(
577: "generated source in "
578: + (System.currentTimeMillis() - t)
579: + " ms");
580: }
581: getFactory().getEnvironment().debugMessage(
582: "output directory: " + fg.getOutputDirectory());
583: }
584: t = System.currentTimeMillis();
585:
586: getFactory().getEnvironment().debugMessage(
587: "program spooning done in " + (t - tstart) + " ms");
588: getFactory().getEnvironment().reportEnd();
589:
590: // Gets main class
591: String progClass = getArguments().getString("class");
592: String progArgs[] = getArguments().getStringArray("arguments");
593:
594: if (progClass != null) {
595: // Launch main class using reflection
596: getFactory().getEnvironment().debugMessage(
597: "running class: '" + progClass + "'...");
598: Class<?> clas = getClass().getClassLoader().loadClass(
599: progClass);
600: Class<?> mainArgType[] = { (new String[0]).getClass() };
601: Method main = clas.getMethod("main", mainArgType);
602: Object argsArray[] = { progArgs };
603: main.invoke(null, argsArray);
604: }
605:
606: }
607: }
|