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.compiler;
054:
055: import java.io.*;
056: import java.util.*;
057: import java.lang.reflect.*;
058: import com.go.trove.io.SourceReader;
059: import com.go.tea.parsetree.*;
060:
061: /******************************************************************************
062: * The Tea compiler. This class is abstract, and a few concrete
063: * implementations can be found in the com.go.tea.util package.
064: *
065: * <p>A Compiler instance should be used for only one "build" because
066: * some information is cached internally like parse trees and error count.
067: *
068: * @author Brian S O'Neill
069: * @version
070: * <!--$$Revision:--> 58 <!-- $-->, <!--$$JustDate:--> 7/26/01 <!-- $-->
071: * @see com.go.tea.util.FileCompiler
072: * @see com.go.tea.util.ResourceCompiler
073: */
074: public abstract class Compiler {
075: // Maps qualified names to ParseTrees.
076: final Map mParseTreeMap;
077:
078: // Maps qualified names to CompilationUnits.
079: private final Map mCompilationUnitMap = new HashMap();
080:
081: // Set of names for CompilationUnits that have already been compiled.
082: private final Set mCompiled = new HashSet();
083:
084: private Set mPreserveTree;
085:
086: private Class mContextClass = com.go.tea.runtime.UtilityContext.class;
087: private Method[] mRuntimeMethods;
088: private Method[] mStringConverters;
089:
090: private ErrorListener mErrorListener;
091:
092: private Vector mErrorListeners = new Vector(4);
093: private int mErrorCount = 0;
094:
095: private Vector mStatusListeners = new Vector();
096:
097: private boolean mGenerateCode = true;
098: private boolean mExceptionGuardian = false;
099:
100: private ClassLoader mClassLoader;
101:
102: private MessageFormatter mFormatter;
103:
104: public Compiler() {
105: this (Collections.synchronizedMap(new HashMap()));
106: }
107:
108: /**
109: * This constructor allows template signatures to be shared among compiler
110: * instances. This is useful in interactive environments, where compilation
111: * is occurring on a regular basis, but most called templates are not
112: * being modified. The Compiler will map qualified template names to
113: * ParseTree objects that have their code removed. Removing a template
114: * entry from the map will force the compiler to re-parse the template if
115: * it is called. Any template passed into the compile method will always be
116: * re-parsed, even if its parse tree is already present in the map.
117: *
118: * @param parseTreeMap map should be thread-safe
119: */
120: public Compiler(Map parseTreeMap) {
121: mParseTreeMap = parseTreeMap;
122: mErrorListener = new ErrorListener() {
123: public void compileError(ErrorEvent e) {
124: dispatchCompileError(e);
125: }
126: };
127: mFormatter = MessageFormatter.lookup(this );
128: }
129:
130: /**
131: * Add an ErrorListener in order receive events of compile-time errors.
132: * @see com.go.tea.util.ConsoleErrorReporter
133: */
134: public void addErrorListener(ErrorListener listener) {
135: mErrorListeners.addElement(listener);
136: }
137:
138: public void removeErrorListener(ErrorListener listener) {
139: mErrorListeners.removeElement(listener);
140: }
141:
142: private void dispatchCompileError(ErrorEvent e) {
143: mErrorCount++;
144:
145: synchronized (mErrorListeners) {
146: for (int i = 0; i < mErrorListeners.size(); i++) {
147: ((ErrorListener) mErrorListeners.elementAt(i))
148: .compileError(e);
149: }
150: }
151: }
152:
153: /**
154: * Add a StatusListener in order to receive events of compilation progress.
155: */
156: public void addStatusListener(StatusListener listener) {
157: mStatusListeners.addElement(listener);
158: }
159:
160: public void removeStatusListener(StatusListener listener) {
161: mStatusListeners.removeElement(listener);
162: }
163:
164: private void dispatchCompileStatus(StatusEvent e) {
165: synchronized (mStatusListeners) {
166: for (int i = 0; i < mStatusListeners.size(); i++) {
167: ((StatusListener) mStatusListeners.elementAt(i))
168: .statusUpdate(e);
169: }
170: }
171: }
172:
173: private void uncaughtException(Exception e) {
174: Thread t = Thread.currentThread();
175: t.getThreadGroup().uncaughtException(t, e);
176: }
177:
178: /**
179: * By default, code generation is enabled. Passing false disables the
180: * code generation phase of the compiler.
181: */
182: public void setCodeGenerationEnabled(boolean flag) {
183: mGenerateCode = flag;
184: }
185:
186: /**
187: * Returns true if code generation is enabled. The default setting is true.
188: */
189: public boolean isCodeGenerationEnabled() {
190: return mGenerateCode;
191: }
192:
193: public void setExceptionGuardianEnabled(boolean flag) {
194: mExceptionGuardian = flag;
195: }
196:
197: /**
198: * Returns true if the exception guardian is enabled. The default setting
199: * is false.
200: */
201: public boolean isExceptionGuardianEnabled() {
202: return mExceptionGuardian;
203: }
204:
205: /**
206: * Sets the ClassLoader to use to load classes with. If set to null,
207: * then classes are loaded using Class.forName.
208: */
209: public void setClassLoader(ClassLoader loader) {
210: mClassLoader = loader;
211: }
212:
213: /**
214: * Returns the ClassLoader used by the Compiler, or null if none set.
215: */
216: public ClassLoader getClassLoader() {
217: return mClassLoader;
218: }
219:
220: /**
221: * Loads and returns a class by the fully qualified name given. If a
222: * ClassLoader is specified, it is used to load the class. Otherwise,
223: * the class is loaded via Class.forName.
224: *
225: * @see #setClassLoader(ClassLoader)
226: */
227: public Class loadClass(String name) throws ClassNotFoundException {
228: while (true) {
229: try {
230: if (mClassLoader == null) {
231: return Class.forName(name);
232: } else {
233: return mClassLoader.loadClass(name);
234: }
235: } catch (ClassNotFoundException e) {
236: int index = name.lastIndexOf('.');
237: if (index < 0) {
238: throw e;
239: }
240:
241: // Search for inner class.
242: name = name.substring(0, index) + '$'
243: + name.substring(index + 1);
244: }
245: }
246: }
247:
248: /**
249: * After a template is compiled, all but the root node of its parse tree
250: * is clipped, in order to save memory. Applications that wish to traverse
251: * CompilationUnit parse trees should call this method to preserve them.
252: * This method must be called prior to compilation and prior to requesting
253: * a parse tree from a CompilationUnit.
254: *
255: * @param name fully qualified name of template whose parse tree is to be
256: * preserved.
257: */
258: public void preserveParseTree(String name) {
259: if (mPreserveTree == null) {
260: mPreserveTree = new HashSet();
261: }
262: mPreserveTree.add(name);
263: }
264:
265: /**
266: * Compile a single compilation unit. This method can be called multiple
267: * times, but it will not compile compilation units that have already been
268: * compiled.
269: *
270: * @param name the fully qualified template name
271: *
272: * @return The names of all the sources compiled by this compiler
273: * @exception IOException
274: */
275: public String[] compile(String name) throws IOException {
276: return compile(new String[] { name });
277: }
278:
279: /**
280: * Compile a list of compilation units. This method can be called multiple
281: * times, but it will not compile compilation units that have already been
282: * compiled.
283: *
284: * @param names an array of fully qualified template names
285: *
286: * @return The names of all the sources compiled by this compiler
287: * @exception IOException
288: */
289: public String[] compile(String[] names) throws IOException {
290: synchronized (mParseTreeMap) {
291: for (int i = 0; i < names.length; i++) {
292: if (Thread.interrupted()) {
293: break;
294: }
295: dispatchCompileStatus(new StatusEvent(this , i,
296: names.length, names[i]));
297: CompilationUnit unit = getCompilationUnit(names[i],
298: null);
299: if (unit == null) {
300: String msg = mFormatter.format("not.found",
301: names[i]);
302: dispatchCompileError(new ErrorEvent(this , msg,
303: (SourceInfo) null, null));
304: } else if (!mCompiled.contains(names[i])
305: && unit.shouldCompile()) {
306: mParseTreeMap.remove(names[i]);
307: getParseTree(unit);
308: }
309: }
310: }
311:
312: names = new String[mCompiled.size()];
313: Iterator it = mCompiled.iterator();
314: int i = 0;
315: while (it.hasNext()) {
316: names[i++] = (String) it.next();
317: }
318:
319: return names;
320: }
321:
322: public int getErrorCount() {
323: return mErrorCount;
324: }
325:
326: /**
327: * Returns a compilation unit associated with the given name, or null if
328: * not found.
329: *
330: * @param name the requested name
331: * @param from optional CompilationUnit is passed because requested name
332: * should be found relative to it.
333: */
334: public CompilationUnit getCompilationUnit(String name,
335: CompilationUnit from) {
336: name = determineQualifiedName(name, from);
337:
338: if (name == null) {
339: return null;
340: }
341:
342: CompilationUnit unit = (CompilationUnit) mCompilationUnitMap
343: .get(name);
344: if (unit == null) {
345: unit = createCompilationUnit(name);
346: if (unit != null) {
347: mCompilationUnitMap.put(name, unit);
348: }
349: }
350:
351: return unit;
352: }
353:
354: /**
355: * Returns the list of imported packages that all templates have. This
356: * always returns "java.lang" and "java.util". Template parameters can
357: * abbreviate the names of all classes in java.lang and java.util.
358: */
359: public static final String[] getImportedPackages() {
360: return new String[] { "java.lang", "java.util" };
361: }
362:
363: /**
364: * Return a class that defines a template's runtime context. The runtime
365: * context contains methods that are callable by templates. A template
366: * is compiled such that the first parameter of its execute method must
367: * be an instance of the runtime context.
368: *
369: * <p>Default implementation returns
370: * com.go.tea.runtime.UtilityContext.
371: *
372: * @see com.go.tea.runtime.UtilityContext
373: */
374: public Class getRuntimeContext() {
375: return mContextClass;
376: }
377:
378: /**
379: * Call to override the default runtime context class that a template is
380: * compiled to use.
381: *
382: * @see com.go.tea.runtime.Context
383: */
384: public void setRuntimeContext(Class contextClass) {
385: mContextClass = contextClass;
386: mRuntimeMethods = null;
387: mStringConverters = null;
388: }
389:
390: /**
391: * Returns all the methods available in the runtime context.
392: */
393: public final Method[] getRuntimeContextMethods() {
394: if (mRuntimeMethods == null) {
395: mRuntimeMethods = getRuntimeContext().getMethods();
396: }
397:
398: return (Method[]) mRuntimeMethods.clone();
399: }
400:
401: /**
402: * Return the name of a method in the runtime context to bind to for
403: * receiving objects emitted by templates. The compiler will bind to the
404: * closest matching public method based on the type of its single
405: * parameter.
406: *
407: * <p>Default implementation returns "print".
408: */
409: public String getRuntimeReceiver() {
410: return "print";
411: }
412:
413: /**
414: * Return the name of a method in the runtime context to bind to for
415: * converting objects and primitives to strings. The compiler will bind to
416: * the closest matching public method based on the type of its single
417: * parameter.
418: *
419: * <p>Default implementation returns "toString". Returning null indicates
420: * that a static String.valueOf method should be invoked.
421: */
422: public String getRuntimeStringConverter() {
423: return "toString";
424: }
425:
426: /**
427: * Returns the set of methods that are used to perform conversion to
428: * strings. The compiler will bind to the closest matching method based
429: * on its parameter type.
430: */
431: public final Method[] getStringConverterMethods() {
432: if (mStringConverters == null) {
433: String name = getRuntimeStringConverter();
434:
435: Vector methods = new Vector();
436:
437: if (name != null) {
438: Method[] contextMethods = getRuntimeContextMethods();
439: for (int i = 0; i < contextMethods.length; i++) {
440: Method m = contextMethods[i];
441: if (m.getName().equals(name)
442: && m.getReturnType() == String.class
443: && m.getParameterTypes().length == 1) {
444:
445: methods.addElement(m);
446: }
447: }
448: }
449:
450: int customSize = methods.size();
451:
452: Method[] stringMethods = String.class.getMethods();
453: for (int i = 0; i < stringMethods.length; i++) {
454: Method m = stringMethods[i];
455: if (m.getName().equals("valueOf")
456: && m.getReturnType() == String.class
457: && m.getParameterTypes().length == 1
458: && Modifier.isStatic(m.getModifiers())) {
459:
460: // Don't add to list if a custom converter already handles
461: // this method's parameter type.
462: Class type = m.getParameterTypes()[0];
463: int j;
464: for (j = 0; j < customSize; j++) {
465: Method cm = (Method) methods.elementAt(j);
466: if (cm.getParameterTypes()[0] == type) {
467: break;
468: }
469: }
470:
471: if (j == customSize) {
472: methods.addElement(m);
473: }
474: }
475: }
476:
477: mStringConverters = new Method[methods.size()];
478: methods.copyInto(mStringConverters);
479: }
480:
481: return (Method[]) mStringConverters.clone();
482: }
483:
484: /**
485: * Given a name, as requested by the given CompilationUnit, return a
486: * fully qualified name or null if the name could not be found.
487: *
488: * @param name requested name
489: * @param from optional CompilationUnit
490: */
491: private String determineQualifiedName(String name,
492: CompilationUnit from) {
493: if (from != null) {
494: // Determine qualified name as being relative to "from"
495:
496: String fromName = from.getName();
497: int index = fromName.lastIndexOf('.');
498: if (index >= 0) {
499: String qual = fromName.substring(0, index + 1) + name;
500: if (sourceExists(qual)) {
501: return qual;
502: }
503: }
504: }
505:
506: if (sourceExists(name)) {
507: return name;
508: }
509:
510: return null;
511: }
512:
513: /**
514: * @return true if source exists for the given qualified name
515: */
516: public abstract boolean sourceExists(String name);
517:
518: protected abstract CompilationUnit createCompilationUnit(String name);
519:
520: /**
521: * Default implementation returns a SourceReader that uses "<%" and "%>"
522: * as code delimiters.
523: */
524: protected SourceReader createSourceReader(CompilationUnit unit)
525: throws IOException {
526:
527: Reader r = new BufferedReader(unit.getReader());
528: return new SourceReader(r, "<%", "%>");
529: }
530:
531: protected Scanner createScanner(SourceReader reader,
532: CompilationUnit unit) throws IOException {
533:
534: return new Scanner(reader, unit);
535: }
536:
537: protected Parser createParser(Scanner scanner, CompilationUnit unit)
538: throws IOException {
539:
540: return new Parser(scanner, unit);
541: }
542:
543: protected TypeChecker createTypeChecker(CompilationUnit unit) {
544: TypeChecker tc = new TypeChecker(unit);
545: tc.setClassLoader(getClassLoader());
546: tc.setExceptionGuardianEnabled(isExceptionGuardianEnabled());
547: return tc;
548: }
549:
550: /**
551: * Default implementation returns a new JavaClassGenerator.
552: *
553: * @see JavaClassGenerator
554: */
555: protected CodeGenerator createCodeGenerator(CompilationUnit unit)
556: throws IOException {
557:
558: return new JavaClassGenerator(unit);
559: }
560:
561: /**
562: * Called by the Compiler or by a CompilationUnit when its parse tree is
563: * requested. Requesting a parse tree may cause template code to be
564: * generated.
565: */
566: Template getParseTree(CompilationUnit unit) {
567: synchronized (mParseTreeMap) {
568: return getParseTree0(unit);
569: }
570: }
571:
572: private Template getParseTree0(CompilationUnit unit) {
573: String name = unit.getName();
574: Template tree = (Template) mParseTreeMap.get(name);
575: if (tree != null) {
576: return tree;
577: }
578:
579: try {
580: // Parse and type check the parse tree.
581:
582: // Direct all compile errors into the CompilationUnit.
583: // Remove the unit as an ErrorListener in the finally block
584: // at the end of this method.
585: addErrorListener(unit);
586:
587: try {
588: Scanner s = createScanner(createSourceReader(unit),
589: unit);
590: s.addErrorListener(mErrorListener);
591: Parser p = createParser(s, unit);
592: p.addErrorListener(mErrorListener);
593: tree = p.parse();
594: mParseTreeMap.put(name, tree);
595: s.close();
596: } catch (IOException e) {
597: uncaughtException(e);
598: String msg = mFormatter.format("read.error", e
599: .toString());
600: dispatchCompileError(new ErrorEvent(this , msg,
601: (SourceInfo) null, unit));
602: return tree;
603: }
604:
605: TypeChecker tc = createTypeChecker(unit);
606: tc.setClassLoader(getClassLoader());
607: tc.addErrorListener(mErrorListener);
608: tc.typeCheck();
609:
610: if (mCompiled.contains(name) || !unit.shouldCompile()) {
611: return tree;
612: } else {
613: mCompiled.add(name);
614: }
615:
616: // Code generate the CompilationUnit only if no errors and
617: // the code generate option is enabled.
618:
619: if (unit.getErrorCount() == 0 && mGenerateCode) {
620: try {
621: OutputStream out = unit.getOutputStream();
622:
623: if (out != null) {
624: tree = (Template) new BasicOptimizer(tree)
625: .optimize();
626: mParseTreeMap.put(name, tree);
627:
628: CodeGenerator codegen = createCodeGenerator(unit);
629: codegen.writeTo(out);
630: out.flush();
631: out.close();
632: }
633: } catch (IOException e) {
634: uncaughtException(e);
635: String msg = mFormatter.format("write.error", e
636: .toString());
637: dispatchCompileError(new ErrorEvent(this , msg,
638: (SourceInfo) null, unit));
639: return tree;
640: }
641: }
642: } catch (Exception e) {
643: uncaughtException(e);
644: String msg = mFormatter.format("internal.error", e
645: .toString());
646: dispatchCompileError(new ErrorEvent(this , msg,
647: (SourceInfo) null, unit));
648: } finally {
649: removeErrorListener(unit);
650: // Conserve memory by removing the bulk of the parse tree after
651: // compilation. This preserves the signature for templates that
652: // may need to call this one.
653: if (tree != null && mPreserveTree != null
654: && !mPreserveTree.contains(name)) {
655: tree.setStatement(null);
656: }
657: }
658:
659: return tree;
660: }
661: }
|