001: /* ====================================================================
002: * Tea - Copyright (c) 1997-2000 Walt Disney Internet Group
003: * ====================================================================
004: * The Tea Software License, Version 1.1
005: *
006: * Copyright (c) 2000 Walt Disney Internet Group. All rights reserved.
007: *
008: * Redistribution and use in source and binary forms, with or without
009: * modification, are permitted provided that the following conditions
010: * are met:
011: *
012: * 1. Redistributions of source code must retain the above copyright
013: * notice, this list of conditions and the following disclaimer.
014: *
015: * 2. Redistributions in binary form must reproduce the above copyright
016: * notice, this list of conditions and the following disclaimer in
017: * the documentation and/or other materials provided with the
018: * distribution.
019: *
020: * 3. The end-user documentation included with the redistribution,
021: * if any, must include the following acknowledgment:
022: * "This product includes software developed by the
023: * Walt Disney Internet Group (http://opensource.go.com/)."
024: * Alternately, this acknowledgment may appear in the software itself,
025: * if and wherever such third-party acknowledgments normally appear.
026: *
027: * 4. The names "Tea", "TeaServlet", "Kettle", "Trove" and "BeanDoc" must
028: * not be used to endorse or promote products derived from this
029: * software without prior written permission. For written
030: * permission, please contact opensource@dig.com.
031: *
032: * 5. Products derived from this software may not be called "Tea",
033: * "TeaServlet", "Kettle" or "Trove", nor may "Tea", "TeaServlet",
034: * "Kettle", "Trove" or "BeanDoc" appear in their name, without prior
035: * written permission of the Walt Disney Internet Group.
036: *
037: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
038: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
039: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
040: * DISCLAIMED. IN NO EVENT SHALL THE WALT DISNEY INTERNET GROUP OR ITS
041: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
042: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
043: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
044: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
045: * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
046: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
047: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
048: * ====================================================================
049: *
050: * For more information about Tea, please see http://opensource.go.com/.
051: */
052:
053: package com.go.tea.util;
054:
055: import java.io.IOException;
056: import java.io.InputStream;
057: import java.io.File;
058: import java.io.FileInputStream;
059: import java.io.Reader;
060: import java.io.InputStreamReader;
061: import java.io.OutputStream;
062: import java.io.BufferedOutputStream;
063: import java.io.FileOutputStream;
064: import java.util.Collection;
065: import java.util.ArrayList;
066: import java.util.TreeSet;
067: import java.util.Map;
068: import java.util.HashMap;
069: import java.util.Collections;
070: import com.go.trove.util.ClassInjector;
071: import com.go.trove.io.DualOutput;
072: import com.go.tea.compiler.Compiler;
073: import com.go.tea.compiler.CompilationUnit;
074:
075: /******************************************************************************
076: * FileCompiler compiles tea source files by reading them from a file or a
077: * directory. The compiled code can be written as class files to a given
078: * destination directory, they can be passed to a ClassInjector, or they
079: * can be sent to both.
080: *
081: * <p>When given a directory, FileCompiler compiles all files with the
082: * extension ".tea". If a destination directory is used, tea files that have a
083: * matching class file that is more up-to-date will not be compiled, unless
084: * they are forced to be re-compiled.
085: *
086: * @author Brian S O'Neill
087: * @version
088: * <!--$$Revision:--> 42 <!-- $--> 21 <!-- $$JustDate:--> 01/02/06 <!-- $-->
089: * @see ClassInjector
090: */
091: public class FileCompiler extends AbstractFileCompiler {
092: /**
093: * Entry point for a command-line tool suitable for compiling Tea
094: * templates to be bundled with a product. Templates are read from files
095: * that must have the extension ".tea", and any compilation error messages
096: * are sent to standard out.
097: *
098: * <pre>
099: * Usage: java com.go.tea.util.FileCompiler {options}
100: * <template root directory> {templates}
101: *
102: * where {options} includes:
103: * -context <class> Specify a runtime context class to compile against.
104: * -dest <directory> Specify where to place generated class files.
105: * -force Compile all templates, even if up-to-date.
106: * -package <package> Root package to compile templates into.
107: * -encoding <encoding> Specify character encoding used by source files.
108: * -guardian Enable the exception guardian.
109: * </pre>
110: */
111: public static void main(String[] args) throws Exception {
112: if (args == null || args.length == 0) {
113: usage();
114: return;
115: }
116:
117: Class context = null;
118: File destDir = null;
119: boolean force = false;
120: String rootPackage = null;
121: String encoding = null;
122: boolean guardian = false;
123: File rootDir = null;
124: Collection templates = new ArrayList(args.length);
125:
126: try {
127: boolean parsingOptions = true;
128: for (int i = 0; i < args.length;) {
129: String arg = args[i++];
130: if (arg.startsWith("-") && parsingOptions) {
131: if (arg.equals("-context") && context == null) {
132: context = Class.forName(args[i++]);
133: continue;
134: } else if (arg.equals("-dest") && destDir == null) {
135: destDir = new File(args[i++]);
136: continue;
137: } else if (arg.equals("-force") && !force) {
138: force = true;
139: continue;
140: } else if (arg.equals("-package")
141: && rootPackage == null) {
142: rootPackage = args[i++];
143: continue;
144: } else if (arg.equals("-encoding")
145: && encoding == null) {
146: encoding = args[i++];
147: continue;
148: } else if (arg.equals("-guardian") && !guardian) {
149: guardian = true;
150: continue;
151: }
152: } else {
153: if (parsingOptions) {
154: parsingOptions = false;
155: rootDir = new File(arg);
156: continue;
157: }
158:
159: arg = arg.replace('/', '.');
160: arg = arg.replace(File.separatorChar, '.');
161: while (arg.startsWith(".")) {
162: arg = arg.substring(1);
163: }
164: while (arg.endsWith(".")) {
165: arg = arg.substring(0, arg.length() - 1);
166: }
167: templates.add(arg);
168: continue;
169: }
170:
171: usage();
172: return;
173: }
174: } catch (ArrayIndexOutOfBoundsException e) {
175: usage();
176: return;
177: }
178:
179: if (rootDir == null) {
180: usage();
181: return;
182: }
183:
184: if (context == null) {
185: context = com.go.tea.runtime.UtilityContext.class;
186: }
187:
188: if (destDir == null) {
189: destDir = rootDir;
190: }
191:
192: FileCompiler compiler = new FileCompiler(rootDir, rootPackage,
193: destDir, null, encoding);
194:
195: compiler.setRuntimeContext(context);
196: compiler.setForceCompile(force);
197: compiler.addErrorListener(new ConsoleErrorReporter(System.out));
198: compiler.setExceptionGuardianEnabled(guardian);
199:
200: String[] names;
201: if (templates.size() == 0) {
202: names = compiler.compileAll(true);
203: } else {
204: names = (String[]) templates.toArray(new String[templates
205: .size()]);
206: names = compiler.compile(names);
207: }
208:
209: int errorCount = compiler.getErrorCount();
210:
211: if (errorCount > 0) {
212: String msg = String.valueOf(errorCount) + " error";
213: if (errorCount != 1) {
214: msg += 's';
215: }
216: System.out.println(msg);
217: System.exit(1);
218: }
219: }
220:
221: private static void usage() {
222: String usageDetail = " -context <class> Specify a runtime context class to compile against.\n"
223: + " -dest <directory> Specify where to place generated class files.\n"
224: + " -force Compile all templates, even if up-to-date.\n"
225: + " -package <package> Root package to compile templates into.\n"
226: + " -encoding <encoding> Specify character encoding used by source files.\n"
227: + " -guardian Enable the exception guardian.";
228:
229: System.out.print("\nUsage: ");
230: System.out.print("java ");
231: System.out.print(FileCompiler.class.getName());
232: System.out
233: .println(" {options} <template root directory> {templates}");
234: System.out.println();
235: System.out.println("where {options} includes:");
236: System.out.println(usageDetail);
237: }
238:
239: private File[] mRootSourceDirs;
240: private String mRootPackage;
241: private File mRootDestDir;
242: private ClassInjector mInjector;
243: private String mEncoding;
244: private boolean mForce = false;
245:
246: /**
247: * @param rootSourceDir Required root source directory
248: * @param rootPackage Optional root package to compile source to
249: * @param rootDestDir Optional directory to place generated class files
250: * @param injector Optional ClassInjector to feed generated classes into
251: */
252: public FileCompiler(File rootSourceDir, String rootPackage,
253: File rootDestDir, ClassInjector injector) {
254: this (new File[] { rootSourceDir }, rootPackage, rootDestDir,
255: injector, null);
256: }
257:
258: /**
259: * @param rootSourceDir Required root source directory
260: * @param rootPackage Optional root package to compile source to
261: * @param rootDestDir Optional directory to place generated class files
262: * @param injector Optional ClassInjector to feed generated classes into
263: * @param encoding Optional character encoding used by source files
264: */
265: public FileCompiler(File rootSourceDir, String rootPackage,
266: File rootDestDir, ClassInjector injector, String encoding) {
267: this (new File[] { rootSourceDir }, rootPackage, rootDestDir,
268: injector, encoding);
269: }
270:
271: /**
272: * @param rootSourceDirs Required root source directories
273: * @param rootPackage Optional root package to compile source to
274: * @param rootDestDir Optional directory to place generated class files
275: * @param injector Optional ClassInjector to feed generated classes into
276: */
277: public FileCompiler(File[] rootSourceDirs, String rootPackage,
278: File rootDestDir, ClassInjector injector) {
279: this (rootSourceDirs, rootPackage, rootDestDir, injector, null);
280: }
281:
282: /**
283: * @param rootSourceDirs Required root source directories
284: * @param rootPackage Optional root package to compile source to
285: * @param rootDestDir Optional directory to place generated class files
286: * @param injector Optional ClassInjector to feed generated classes into
287: * @param encoding Optional character encoding used by source files
288: */
289: public FileCompiler(File[] rootSourceDirs, String rootPackage,
290: File rootDestDir, ClassInjector injector, String encoding) {
291: super ();
292: init(rootSourceDirs, rootPackage, rootDestDir, injector,
293: encoding);
294: }
295:
296: /**
297: * @param rootSourceDirs Required root source directories
298: * @param rootPackage Optional root package to compile source to
299: * @param rootDestDir Optional directory to place generated class files
300: * @param injector Optional ClassInjector to feed generated classes into
301: * @param encoding Optional character encoding used by source files
302: * @param parseTreeMap Optional map should be thread-safe. See
303: * {@link Compiler} for details.
304: */
305: public FileCompiler(File[] rootSourceDirs, String rootPackage,
306: File rootDestDir, ClassInjector injector, String encoding,
307: Map parseTreeMap) {
308: super ((parseTreeMap == null) ? Collections
309: .synchronizedMap(new HashMap()) : parseTreeMap);
310: init(rootSourceDirs, rootPackage, rootDestDir, injector,
311: encoding);
312: }
313:
314: private void init(File[] rootSourceDirs, String rootPackage,
315: File rootDestDir, ClassInjector injector, String encoding) {
316: mRootSourceDirs = (File[]) rootSourceDirs.clone();
317: mRootPackage = rootPackage;
318: mRootDestDir = rootDestDir;
319: mInjector = injector;
320: mEncoding = encoding;
321:
322: for (int i = 0; i < rootSourceDirs.length; i++) {
323: if (!rootSourceDirs[i].isDirectory()) {
324: throw new IllegalArgumentException(
325: "Source location is not a directory: "
326: + rootSourceDirs[i]);
327: }
328: }
329:
330: if (rootDestDir != null && !rootDestDir.isDirectory()) {
331: throw new IllegalArgumentException(
332: "Destination is not a directory: " + rootDestDir);
333: }
334: }
335:
336: /**
337: * @param force When true, compile all source, even if up-to-date
338: */
339: public void setForceCompile(boolean force) {
340: mForce = force;
341: }
342:
343: /**
344: * Compiles all files in the source directory.
345: *
346: * @param recurse When true, recursively compiles all files and directories
347: *
348: * @return The names of all the compiled sources
349: */
350: public String[] compileAll(boolean recurse) throws IOException {
351: return compile(getAllTemplateNames(recurse));
352: }
353:
354: public String[] getAllTemplateNames() throws IOException {
355: return getAllTemplateNames(true);
356: }
357:
358: private String[] getAllTemplateNames(boolean recurse)
359: throws IOException {
360: // Using a Set to prevent duplicate template names.
361: Collection sources = new TreeSet();
362:
363: for (int i = 0; i < mRootSourceDirs.length; i++) {
364: gatherSources(sources, mRootSourceDirs[i], recurse);
365: }
366:
367: return (String[]) sources.toArray(new String[sources.size()]);
368: }
369:
370: public boolean sourceExists(String name) {
371: return findRootSourceDir(name) != null;
372: }
373:
374: /**
375: * Gathers all sources (template names) in the source directory.
376: *
377: * @param templateNames Collection of Strings. The gatherSources method
378: * will add the template names to this Collection.
379: * @param sourceDir the root source directory
380: * @param recurse When true, recursively gathers all sources in
381: * sub-directories.
382: */
383: private void gatherSources(Collection templateNames,
384: File sourceDir, boolean recurse) throws IOException {
385: gatherSources(templateNames, sourceDir, null, recurse);
386: }
387:
388: private void gatherSources(Collection toCompile, File sourceDir,
389: String parentName, boolean recurse) throws IOException {
390: String[] list = sourceDir.list();
391: if (list != null) {
392: for (int i = 0; i < list.length; i++) {
393: File file = new File(sourceDir, list[i]);
394: if (file.isDirectory()) {
395: if (recurse) {
396: String name = file.getName();
397:
398: if (parentName != null) {
399: name = parentName + '.' + name;
400: }
401:
402: gatherSources(toCompile, file, name, recurse);
403: }
404: } else if (file.getName().endsWith(".tea")) {
405: String name = file.getName();
406: int index = name.lastIndexOf('.');
407: name = name.substring(0, index);
408:
409: if (parentName != null) {
410: name = parentName + '.' + name;
411: }
412:
413: toCompile.add(name);
414: }
415: }
416: }
417:
418: return;
419: }
420:
421: /**
422: * Always returns an instance of FileCompiler.Unit. Any errors reported
423: * by the compiler that have a reference to a CompilationUnit will have
424: * been created by this factory method. Casting this to FileCompiler.Unit
425: * allows error reporters to access the source file via the getSourceFile
426: * method.
427: *
428: * @see FileCompiler.Unit#getSourceFile
429: */
430: protected CompilationUnit createCompilationUnit(String name) {
431: return new Unit(name, this );
432: }
433:
434: private File findRootSourceDir(String name) {
435: String fileName = name.replace('.', File.separatorChar)
436: + ".tea";
437:
438: for (int i = 0; i < mRootSourceDirs.length; i++) {
439: File file = new File(mRootSourceDirs[i], fileName);
440: if (file.exists()) {
441: return mRootSourceDirs[i];
442: }
443: }
444:
445: return null;
446: }
447:
448: public class Unit extends CompilationUnit {
449: private final String mSourceFileName;
450: private final File mSourceFile;
451: private final File mDestFile;
452:
453: Unit(String name, Compiler compiler) {
454: super (name, compiler);
455:
456: File rootSourceDir = findRootSourceDir(name);
457: if (rootSourceDir == null) {
458: // File isn't found, but set to a valid directory so that error
459: // is produced later when attempting to get a Reader.
460: rootSourceDir = mRootSourceDirs[0];
461: }
462:
463: String fname = name.replace('.', '/');
464:
465: mSourceFileName = fname + ".tea";
466: mSourceFile = new File(rootSourceDir, mSourceFileName);
467:
468: if (mRootDestDir == null) {
469: mDestFile = null;
470: } else {
471: mDestFile = new File(mRootDestDir, fname + ".class");
472: }
473: }
474:
475: public String getTargetPackage() {
476: return mRootPackage;
477: }
478:
479: public String getSourceFileName() {
480: return mSourceFileName;
481: }
482:
483: public File getSourceFile() {
484: return mSourceFile;
485: }
486:
487: public Reader getReader() throws IOException {
488: InputStream in = new FileInputStream(mSourceFile);
489: if (mEncoding == null) {
490: return new InputStreamReader(in);
491: } else {
492: return new InputStreamReader(in, mEncoding);
493: }
494: }
495:
496: public boolean shouldCompile() {
497: if (!mForce
498: && mDestFile != null
499: && mDestFile.exists()
500: && mDestFile.lastModified() >= mSourceFile
501: .lastModified()) {
502:
503: return false;
504: }
505:
506: return true;
507: }
508:
509: /**
510: * @return the file that gets written by the compiler.
511: */
512: public File getDestinationFile() {
513: return mDestFile;
514: }
515:
516: public OutputStream getOutputStream() throws IOException {
517: OutputStream out1 = null;
518: OutputStream out2 = null;
519:
520: if (mDestFile != null) {
521: File dir = mDestFile.getParentFile();
522: if (!dir.exists()) {
523: dir.mkdirs();
524: }
525: out1 = new FileOutputStream(mDestFile);
526: }
527:
528: if (mInjector != null) {
529: String className = getName();
530: String pack = getTargetPackage();
531: if (pack != null && pack.length() > 0) {
532: className = pack + '.' + className;
533: }
534: out2 = mInjector.getStream(className);
535: }
536:
537: OutputStream out;
538:
539: if (out1 != null) {
540: if (out2 != null) {
541: out = new DualOutput(out1, out2);
542: } else {
543: out = out1;
544: }
545: } else if (out2 != null) {
546: out = out2;
547: } else {
548: out = new OutputStream() {
549: public void write(int b) {
550: }
551:
552: public void write(byte[] b, int off, int len) {
553: }
554: };
555: }
556:
557: return new BufferedOutputStream(out);
558: }
559: }
560: }
|