0001: /*
0002: * Copyright 2006 Ethan Nicholas. All rights reserved.
0003: * Use is subject to license terms.
0004: */
0005: package jaxx.compiler;
0006:
0007: import java.io.*;
0008: import java.lang.reflect.*;
0009: import java.net.*;
0010: import java.util.*;
0011: import java.util.List;
0012: import java.util.regex.*;
0013: import java.util.zip.*;
0014: import javax.xml.parsers.*;
0015: import javax.xml.transform.*;
0016: import javax.xml.transform.dom.*;
0017: import javax.xml.transform.sax.*;
0018: import org.w3c.dom.*;
0019: import org.xml.sax.*;
0020: import org.xml.sax.helpers.*;
0021:
0022: import jaxx.*;
0023: import jaxx.css.*;
0024: import jaxx.parser.ParseException;
0025: import jaxx.reflect.*;
0026: import jaxx.runtime.*;
0027: import jaxx.runtime.swing.*;
0028: import jaxx.spi.*;
0029: import jaxx.tags.*;
0030: import jaxx.types.*;
0031:
0032: /** Compiles JAXX files into Java classes. */
0033: public class JAXXCompiler {
0034: /** True to throw exceptions when we encounter unresolvable classes, false to ignore.
0035: * This is currently set to false until JAXX has full support for inner classes
0036: * (including enumerations), because currently they don't always resolve (but will
0037: * generally compile without error anyway).
0038: */
0039: public static final boolean STRICT_CHECKS = false;
0040:
0041: public static final String JAXX_NAMESPACE = "http://www.jaxxframework.org/";
0042: public static final String JAXX_INTERNAL_NAMESPACE = "http://www.jaxxframework.org/internal";
0043:
0044: /** Maximum length of an inlinable creation method. */
0045: private static final int INLINE_THRESHOLD = 300;
0046:
0047: /** Contains import declarations (of the form "javax.swing.") which are always imported in all compiler instances. */
0048: private static List/*<String>*/staticImports = new ArrayList/*<String>*/();
0049:
0050: static {
0051: staticImports.add("java.awt.*");
0052: staticImports.add("java.awt.event.*");
0053: staticImports.add("java.beans.*");
0054: staticImports.add("java.io.*");
0055: staticImports.add("java.lang.*");
0056: staticImports.add("java.util.*");
0057: staticImports.add("javax.swing.*");
0058: staticImports.add("javax.swing.border.*");
0059: staticImports.add("javax.swing.event.*");
0060: staticImports.add("jaxx.runtime.swing.JAXXButtonGroup");
0061: staticImports.add("jaxx.runtime.swing.HBox");
0062: staticImports.add("jaxx.runtime.swing.VBox");
0063: staticImports.add("jaxx.runtime.swing.Table");
0064: }
0065:
0066: private static DefaultObjectHandler firstPassClassTagHandler = new DefaultObjectHandler(
0067: ClassDescriptorLoader.getClassDescriptor(Object.class));
0068:
0069: /** A list of Runnables which will be run after the first compilation pass. This is primarily used
0070: * to trigger the creation of CompiledObjects, which cannot be created during the first pass and must be
0071: * created in document order.
0072: */
0073: private List/*<Runnable>*/initializers = new ArrayList/*<Runnable>*/();
0074:
0075: /** Files being compiled during the compilation session, may be modified as compilation progresses and additional dependencies are found. */
0076: private static List/*<File>*/jaxxFiles = new ArrayList/*<File>*/();
0077:
0078: /** Class names corresponding to the files in the jaxxFiles list. */
0079: private static List/*<String>*/jaxxFileClassNames = new ArrayList/*<String>*/();
0080:
0081: /** Maps the names of classes being compiled to the compiler instance handling the compilation. */
0082: private static Map/*<String, JAXXCompiler>*/compilers = new HashMap/*<String, JAXXCompiler>*/();
0083:
0084: /** Maps the names of classes being compiled to their symbol tables (created after the first compiler pass). */
0085: private static Map/*<String, SymbolTable>*/symbolTables = new HashMap/*<String, SymbolTable>*/();
0086:
0087: private CompilerOptions options;
0088:
0089: /** Used for error reporting purposes, so we can report the right line number. */
0090: private Stack/*<Element>*/tagsBeingCompiled = new Stack/*<Element>*/();
0091:
0092: /** Used for error reporting purposes, so we can report the right source file. */
0093: private Stack/*<File>*/sourceFiles = new Stack/*<File>*/();
0094:
0095: /** Maps object ID strings to the objects themselves. These are created during the second compilation pass. */
0096: private Map/*<String, CompiledObject>*/objects = new LinkedHashMap/*<String, CompiledObject>*/();
0097:
0098: /** Maps objects to their ID strings. These are created during the second compilation pass. */
0099: private Map/*<CompiledObject, String>*/ids = new LinkedHashMap/*<CompiledObject, String>*/();
0100:
0101: private static int errorCount;
0102: private static int warningCount;
0103:
0104: private boolean failed;
0105:
0106: /** Object corresponding to the root tag in the document. */
0107: private CompiledObject root;
0108:
0109: /** Contains strings of the form "javax.swing." */
0110: private Set/*<String>*/importedPackages = new HashSet/*<String>*/();
0111:
0112: /** Contains strings of the form "javax.swing.Timer" */
0113: private Set/*<String>*/importedClasses = new HashSet/*<String>*/();
0114:
0115: /** Keeps track of open components (components still having children added). */
0116: private Stack/*<CompiledObject>*/openComponents = new Stack/*<CompiledObject>*/();
0117:
0118: /** Sequence number used to create automatic variable names. */
0119: private int autogenID = 0;
0120:
0121: private List/*<DataBinding>*/dataBindings = new ArrayList/*<DataBinding>*/();
0122:
0123: private JavaFile javaFile = new JavaFile();
0124:
0125: // true if a main() method has been declared in a script
0126: boolean mainDeclared;
0127:
0128: private SymbolTable symbolTable = new SymbolTable();
0129:
0130: // TODO: replace these public StringBuffers with something a little less stupid
0131:
0132: /** Extra code to be added to the instance initializer. */
0133: public StringBuffer initializer = new StringBuffer();
0134:
0135: /** Extra code to be added at the end of the instance initializer. */
0136: public StringBuffer lateInitializer = new StringBuffer();
0137:
0138: /** Extra code to be added to the class body. */
0139: public StringBuffer bodyCode = new StringBuffer();
0140:
0141: /** Code to initialize data bindings. */
0142: public StringBuffer initDataBindings = new StringBuffer();
0143:
0144: /** Body of the applyDataBinding method. */
0145: public StringBuffer applyDataBinding = new StringBuffer();
0146:
0147: /** Body of the removeDataBinding method. */
0148: public StringBuffer removeDataBinding = new StringBuffer();
0149:
0150: /** Body of the processDataBinding method. */
0151: public StringBuffer processDataBinding = new StringBuffer();
0152:
0153: /** Base directory used for path resolution (normally the directory in which the .jaxx file resides). */
0154: private File baseDir;
0155:
0156: /** .jaxx file being compiled. */
0157: private File src;
0158:
0159: /** Generated .java file. */
0160: private File dest;
0161:
0162: /** Parsed XML of src file. */
0163: private Document document;
0164:
0165: /** Name of class being compiled. */
0166: private String outputClassName;
0167:
0168: private ScriptManager scriptManager = new ScriptManager(this );
0169:
0170: /** Combination of all stylesheets registered using {@link #registerStylesheet}. */
0171: private Stylesheet stylesheet;
0172:
0173: /** Contains all attributes defined inline on class tags. */
0174: private List/*<Rule>*/inlineStyles = new ArrayList/*<Rule>*/();
0175:
0176: /** Maps objects (expressed in Java code) to event listener classes (e.g. MouseListener) to Lists of EventHandlers. The final list
0177: * contains all event handlers of a particular type attached to a particular object (again, as represented by a Java expression). */
0178: private Map/*<String, Map<ClassDescriptor, List<EventHandler>>>*/eventHandlers = new HashMap/*<CString, Map<ClassDescriptor, List<EventHandler>>>*/();
0179:
0180: private Map/*<Object, String>*/uniqueIds = new HashMap/*<Object, String>*/();
0181:
0182: private Map/*<EventHandler, String>*/eventHandlerMethodNames = new HashMap/*<EventHandler, String>*/();
0183:
0184: /** ClassLoader which searches the user-specified class path in addition to the normal class path */
0185: private ClassLoader classLoader;
0186:
0187: private static final int PASS_1 = 0;
0188: private static final int PASS_2 = 1;
0189: private static int currentPass;
0190:
0191: static {
0192: try {
0193: loadLibraries();
0194: } catch (Exception e) {
0195: throw new RuntimeException(e);
0196: }
0197: }
0198:
0199: public static void init() {
0200: // forces static initializer to run if it hasn't yet
0201: }
0202:
0203: public static void loadLibraries() throws IOException,
0204: ClassNotFoundException, InstantiationException,
0205: IllegalAccessException {
0206: Enumeration/*<URL>*/e = JAXXCompiler.class.getClassLoader()
0207: .getResources("jaxx.properties");
0208: while (e.hasMoreElements()) {
0209: Properties p = new Properties();
0210: InputStream in = ((URL) e.nextElement()).openConnection()
0211: .getInputStream();
0212: p.load(in);
0213: in.close();
0214:
0215: String initializer = p.getProperty("jaxx.initializer");
0216: if (initializer != null)
0217: ((Initializer) Class.forName(initializer).newInstance())
0218: .initialize();
0219: }
0220: }
0221:
0222: private JAXXCompiler(ClassLoader classLoader) {
0223: this .options = new CompilerOptions();
0224: this .classLoader = classLoader;
0225: addImport("java.lang.*");
0226: }
0227:
0228: /** Creates a new JAXXCompiler.
0229: */
0230: protected JAXXCompiler(File baseDir, File src,
0231: String outputClassName, CompilerOptions options) {
0232: this .baseDir = baseDir;
0233: this .src = src;
0234: sourceFiles.push(src);
0235: this .outputClassName = outputClassName;
0236: this .options = options;
0237: addImport(outputClassName.substring(0, outputClassName
0238: .lastIndexOf(".") + 1)
0239: + "*");
0240: Iterator/*<String>*/i = staticImports.iterator();
0241: while (i.hasNext())
0242: addImport((String) i.next());
0243: }
0244:
0245: /** Creates a dummy JAXXCompiler for use in unit testing. */
0246: public static JAXXCompiler createDummyCompiler() {
0247: return createDummyCompiler(JAXXCompiler.class.getClassLoader());
0248: }
0249:
0250: /** Creates a dummy JAXXCompiler for use in unit testing. */
0251: public static JAXXCompiler createDummyCompiler(
0252: ClassLoader classLoader) {
0253: return new JAXXCompiler(classLoader);
0254: }
0255:
0256: public CompilerOptions getOptions() {
0257: return options;
0258: }
0259:
0260: public JavaFile getJavaFile() {
0261: return javaFile;
0262: }
0263:
0264: private void compileFirstPass() throws IOException {
0265: try {
0266: InputStream in = new FileInputStream(src);
0267: document = parseDocument(in);
0268: in.close();
0269: compileFirstPass(document.getDocumentElement());
0270: } catch (SAXParseException e) {
0271: reportError(e.getLineNumber(), "Invalid XML: "
0272: + e.getMessage());
0273: } catch (SAXException e) {
0274: reportError(null, "Error parsing XML document: " + e);
0275: }
0276: }
0277:
0278: private void runInitializers() {
0279: Iterator/*<Runnable>*/i = initializers.iterator();
0280: while (i.hasNext()) {
0281: ((Runnable) i.next()).run();
0282: i.remove();
0283: }
0284: }
0285:
0286: /** Registers a <code>Runnable</code> which will be executed after the first
0287: * compilation pass is complete.
0288: */
0289: public void registerInitializer(Runnable r) {
0290: initializers.add(r);
0291: }
0292:
0293: private void compileSecondPass() throws IOException {
0294: if (!tagsBeingCompiled.isEmpty())
0295: throw new RuntimeException(
0296: "Internal error: starting pass two, but tagsBeingCompiled is not empty: "
0297: + tagsBeingCompiled);
0298:
0299: compileSecondPass(document.getDocumentElement());
0300: }
0301:
0302: private void applyStylesheets() {
0303: Iterator/*<CompiledObject>*/i = new ArrayList/*<CompiledObject>*/(
0304: objects.values()).iterator();
0305: while (i.hasNext()) {
0306: CompiledObject object = (CompiledObject) i.next();
0307: TagManager.getTagHandler(object.getObjectClass())
0308: .applyStylesheets(object, this );
0309: }
0310: }
0311:
0312: private void generateCode() throws IOException {
0313: if (options.getTargetDirectory() != null)
0314: dest = new File(options.getTargetDirectory(),
0315: outputClassName.replace('.', File.separatorChar)
0316: + ".java");
0317: else
0318: dest = new File(baseDir, outputClassName
0319: .substring(outputClassName.lastIndexOf(".") + 1)
0320: + ".java");
0321: PrintWriter out = new PrintWriter(new FileWriter(dest));
0322: createJavaSource(out);
0323: out.close();
0324: }
0325:
0326: private void runJavac() {
0327: try {
0328: URL jaxxURL = getClass().getResource(
0329: "/jaxx/compiler/JAXXCompiler.class");
0330: if (jaxxURL == null)
0331: throw new InternalError(
0332: "Can't-happen error: could not find /jaxx/compiler/JAXXCompiler.class on class path");
0333: String classpath = jaxxURL.toString();
0334: if (classpath.startsWith("jar:")) {
0335: classpath = classpath.substring("jar:".length());
0336: classpath = classpath.substring(0, classpath
0337: .indexOf("!"));
0338: classpath = URLtoFile(classpath).getPath();
0339: }
0340: URL runtimeURL = getClass().getResource(
0341: "/jaxx/runtime/JAXXObject.class");
0342: if (runtimeURL == null)
0343: throw new InternalError(
0344: "Can't-happen error: could not find /jaxx/runtime/JAXXObject.class on class path");
0345: String runtime = runtimeURL.toString();
0346: if (runtime.startsWith("jar:")) {
0347: runtime = runtime.substring("jar:".length());
0348: runtime = runtime.substring(0, runtime.indexOf("!"));
0349: runtime = URLtoFile(runtime).getPath();
0350: }
0351: classpath += File.pathSeparator + runtime
0352: + File.pathSeparator + options.getClassPath()
0353: + File.pathSeparator + ".";
0354: Class javac = Class.forName("com.sun.tools.javac.Main");
0355: Method main = javac.getMethod("compile",
0356: new Class[] { String[].class });
0357: final PrintStream oldErr = System.err;
0358: System.setErr(new PrintStream(
0359: new FilterOutputStream(oldErr) {
0360: public void write(byte[] b, int off, int len)
0361: throws IOException {
0362: String stringValue = new String(b, off, len)
0363: .trim();
0364: if (stringValue.startsWith("Error:")
0365: || stringValue.startsWith("Usage:")
0366: || stringValue.endsWith("error")
0367: || stringValue.endsWith("errors"))
0368: failed = true;
0369: if (stringValue
0370: .endsWith("uses unchecked or unsafe operations.")
0371: || stringValue
0372: .endsWith("with -Xlint:unchecked for details."))
0373: return;
0374: super .write(b, off, len);
0375: }
0376: }));
0377: List/*<String>*/javacOpts = new ArrayList();
0378: if (options.getJavacOpts() != null)
0379: javacOpts.addAll(Arrays.asList(options.getJavacOpts()
0380: .split("\\s+")));
0381: if (options.getTargetDirectory() != null) {
0382: String destRoot = options.getTargetDirectory()
0383: .getPath();
0384: javacOpts.add("-d");
0385: javacOpts.add(destRoot);
0386: classpath += File.pathSeparator + destRoot;
0387: }
0388: javacOpts.add("-classpath");
0389: javacOpts.add(classpath);
0390: javacOpts.add(dest.getPath());
0391: main.invoke(null, new Object[] { javacOpts
0392: .toArray(new String[javacOpts.size()]) });
0393: System.setErr(oldErr);
0394: } catch (ClassNotFoundException e) {
0395: System.err
0396: .println("Unable to find javac (com.sun.tools.javac.Main) on class path.");
0397: System.err
0398: .println("jaxxc launch script is responsible for adding javac (typically");
0399: System.err
0400: .println("located in tools.jar) to the class path; it either added the");
0401: System.err
0402: .println("wrong path or tools.jar does not exist.");
0403: System.err.println();
0404: System.err
0405: .println("Check to make sure that JAVA_HOME points to a valid JDK");
0406: System.err.println("installation.");
0407: failed = true;
0408: } catch (Exception e) {
0409: System.err
0410: .println("An error occurred while invoking javac:");
0411: e.printStackTrace();
0412: failed = true;
0413: }
0414:
0415: if (!options.getKeepJavaFiles())
0416: dest.delete();
0417: }
0418:
0419: private void createJavaSource(PrintWriter out) throws IOException {
0420: int dotPos = outputClassName.lastIndexOf(".");
0421: String packageName = dotPos != -1 ? outputClassName.substring(
0422: 0, dotPos) : null;
0423: String simpleClassName = outputClassName.substring(dotPos + 1);
0424: outputClass(packageName, simpleClassName, out);
0425: }
0426:
0427: public String getOutputClassName() {
0428: return outputClassName;
0429: }
0430:
0431: public static SAXParser getSAXParser() {
0432: try {
0433: SAXParserFactory factory = SAXParserFactory.newInstance();
0434: factory.setNamespaceAware(true);
0435: SAXParser parser = factory.newSAXParser();
0436: return parser;
0437: } catch (SAXException e) {
0438: throw new RuntimeException(e);
0439: } catch (ParserConfigurationException e) {
0440: throw new RuntimeException(e);
0441: }
0442: }
0443:
0444: public static Document parseDocument(InputStream in)
0445: throws IOException, SAXException {
0446: try {
0447: TransformerFactory factory = TransformerFactory
0448: .newInstance();
0449: Transformer transformer = factory.newTransformer();
0450: transformer.setErrorListener(new ErrorListener() {
0451: public void warning(TransformerException ex)
0452: throws TransformerException {
0453: throw ex;
0454: }
0455:
0456: public void error(TransformerException ex)
0457: throws TransformerException {
0458: throw ex;
0459: }
0460:
0461: public void fatalError(TransformerException ex)
0462: throws TransformerException {
0463: throw ex;
0464: }
0465: });
0466:
0467: DOMResult result = new DOMResult();
0468: transformer.transform(new SAXSource(new XMLFilterImpl(
0469: getSAXParser().getXMLReader()) {
0470: Locator locator;
0471:
0472: public void setDocumentLocator(Locator locator) {
0473: this .locator = locator;
0474: }
0475:
0476: public void startElement(String uri, String localName,
0477: String qName, Attributes atts)
0478: throws SAXException {
0479: AttributesImpl resultAtts = new AttributesImpl(atts);
0480: resultAtts.addAttribute(JAXX_INTERNAL_NAMESPACE,
0481: "line", "internal:line", "CDATA", String
0482: .valueOf(locator.getLineNumber()));
0483: getContentHandler().startElement(uri, localName,
0484: qName, resultAtts);
0485: }
0486: }, new InputSource(in)), result);
0487: return (Document) result.getNode();
0488: } catch (TransformerConfigurationException e) {
0489: throw new RuntimeException(e);
0490: } catch (TransformerException e) {
0491: Throwable ex = e;
0492: while (ex.getCause() != null)
0493: ex = ex.getCause();
0494: if (ex instanceof IOException)
0495: throw (IOException) ex;
0496: if (ex instanceof SAXException)
0497: throw (SAXException) ex;
0498: if (ex instanceof RuntimeException)
0499: throw (RuntimeException) ex;
0500: throw new RuntimeException(ex);
0501: }
0502: }
0503:
0504: public File getBaseDir() {
0505: return baseDir;
0506: }
0507:
0508: public Set/*<String>*/getImportedClasses() {
0509: return importedClasses;
0510: }
0511:
0512: public Set/*<String>*/getImportedPackages() {
0513: return importedPackages;
0514: }
0515:
0516: private boolean inlineCreation(CompiledObject object) {
0517: return object.getId().startsWith("$")
0518: && object.getInitializationCode(this ).length() < INLINE_THRESHOLD;
0519: }
0520:
0521: public void checkOverride(CompiledObject object)
0522: throws CompilerException {
0523: if (object.getId().startsWith("$"))
0524: return;
0525: ClassDescriptor ancestor = root.getObjectClass();
0526: if (ancestor == object.getObjectClass())
0527: return;
0528: while (ancestor != null) {
0529: try {
0530: FieldDescriptor f = ancestor
0531: .getDeclaredFieldDescriptor(object.getId());
0532: if (!f.getType().isAssignableFrom(
0533: object.getObjectClass()))
0534: reportError("attempting to redefine superclass member '"
0535: + object.getId()
0536: + "' as incompatible type (was "
0537: + f.getType()
0538: + ", redefined as "
0539: + object.getObjectClass() + ")");
0540: object.setOverride(true);
0541: break;
0542: } catch (NoSuchFieldException e) {
0543: ancestor = ancestor.getSuperclass();
0544: }
0545: }
0546: }
0547:
0548: private Iterator/*<CompiledObject>*/getObjectCreationOrder() {
0549: return objects.values().iterator();
0550: }
0551:
0552: protected JavaMethod createConstructor(String className)
0553: throws CompilerException {
0554: StringBuffer code = new StringBuffer();
0555: String constructorParams = root.getConstructorParams();
0556: if (constructorParams != null) {
0557: code.append(" super(" + constructorParams + ");");
0558: code.append(getLineSeparator());
0559: }
0560: code.append("$initialize();");
0561: code.append(getLineSeparator());
0562: return new JavaMethod(Modifier.PUBLIC, null, className, null,
0563: null, code.toString());
0564: }
0565:
0566: protected JavaMethod createInitializer(String className)
0567: throws CompilerException {
0568: StringBuffer code = new StringBuffer();
0569: code.append("$objectMap.put("
0570: + TypeManager.getJavaCode(root.getId()) + ", this);");
0571: code.append(getLineSeparator());
0572:
0573: Iterator/*<CompiledObject>*/i = getObjectCreationOrder();
0574: boolean lastWasMethodCall = false;
0575: while (i.hasNext()) {
0576: CompiledObject object = (CompiledObject) i.next();
0577: if (object != root && !object.isOverride()) {
0578: if (inlineCreation(object)) {
0579: if (lastWasMethodCall) {
0580: lastWasMethodCall = false;
0581: code.append(getLineSeparator());
0582: }
0583: code.append(getCreationCode(object));
0584: code.append(getLineSeparator());
0585: } else {
0586: code.append(object.getCreationMethodName() + "();");
0587: code.append(getLineSeparator());
0588: lastWasMethodCall = true;
0589: }
0590: }
0591: }
0592: String rootCode = root.getInitializationCode(this );
0593: if (rootCode != null && rootCode.length() > 0) {
0594: code.append(rootCode);
0595: code.append(getLineSeparator());
0596: }
0597: code.append(getLineSeparator());
0598: if (initializer.length() > 0) {
0599: code.append(initializer);
0600: code.append(getLineSeparator());
0601: }
0602: code.append("$completeSetup();");
0603: code.append(getLineSeparator());
0604: return new JavaMethod(Modifier.PRIVATE, "void", "$initialize",
0605: null, null, code.toString());
0606: }
0607:
0608: protected JavaMethod createCompleteSetupMethod() {
0609: StringBuffer code = new StringBuffer();
0610: code.append("allComponentsCreated = true;");
0611: code.append(getLineSeparator());
0612: Iterator/*<CompiledObject>*/i = objects.values().iterator();
0613: while (i.hasNext()) {
0614: CompiledObject object = (CompiledObject) i.next();
0615: if (object.getId().startsWith("$"))
0616: code.append(object.getAdditionCode());
0617: else {
0618: code.append(object.getAdditionMethodName() + "();"
0619: + getLineSeparator());
0620: String additionCode = object.getAdditionCode();
0621: if (additionCode.length() > 0)
0622: additionCode = "if (allComponentsCreated) {"
0623: + getLineSeparator() + additionCode + "}";
0624: javaFile.addMethod(new JavaMethod(Modifier.PROTECTED,
0625: "void", object.getAdditionMethodName(), null,
0626: null, additionCode));
0627: }
0628: code.append(getLineSeparator());
0629: }
0630:
0631: code.append(initDataBindings);
0632:
0633: if (lateInitializer.length() > 0) {
0634: code.append(lateInitializer);
0635: code.append(getLineSeparator());
0636: }
0637: return new JavaMethod(Modifier.PRIVATE, "void",
0638: "$completeSetup", null, null, code.toString());
0639: }
0640:
0641: protected JavaMethod createProcessDataBindingMethod() {
0642: StringBuffer code = new StringBuffer();
0643: boolean super classIsJAXXObject = ClassDescriptorLoader
0644: .getClassDescriptor(JAXXObject.class).isAssignableFrom(
0645: root.getObjectClass());
0646: // the force parameter forces the update to happen even if it is already in activeBindings. This
0647: // is used on superclass invocations b/c by the time the call gets to the superclass, it is already
0648: // marked active and would otherwise be skipped
0649: if (processDataBinding.length() > 0) {
0650: code
0651: .append(" if (!$force && $activeBindings.contains($dest)) return;");
0652: code.append(getLineSeparator());
0653: code.append(" $activeBindings.add($dest);");
0654: code.append(getLineSeparator());
0655: code.append(" try {");
0656: code.append(getLineSeparator());
0657: if (processDataBinding.length() > 0) {
0658: code.append(processDataBinding);
0659: code.append(getLineSeparator());
0660: }
0661: if (super classIsJAXXObject) {
0662: code.append(" else");
0663: code.append(getLineSeparator());
0664: code
0665: .append(" super.processDataBinding($dest, true);");
0666: code.append(getLineSeparator());
0667: }
0668: code.append(" }");
0669: code.append(getLineSeparator());
0670: code.append(" finally {");
0671: code.append(getLineSeparator());
0672: code.append(" $activeBindings.remove($dest);");
0673: code.append(getLineSeparator());
0674: code.append(" }");
0675: code.append(getLineSeparator());
0676: } else if (super classIsJAXXObject) {
0677: code
0678: .append(" super.processDataBinding($dest, true);");
0679: code.append(getLineSeparator());
0680: }
0681: return new JavaMethod(Modifier.PUBLIC, "void",
0682: "processDataBinding", new JavaArgument[] {
0683: new JavaArgument("String", "$dest"),
0684: new JavaArgument("boolean", "$force") }, null,
0685: code.toString());
0686: }
0687:
0688: protected void createJavaFile(String packageName, String className)
0689: throws CompilerException {
0690: String fullClassName = packageName != null ? packageName + "."
0691: + className : className;
0692: if (root == null)
0693: throw new CompilerException("root tag must be a class tag");
0694: ClassDescriptor super class = root.getObjectClass();
0695: boolean super classIsJAXXObject = ClassDescriptorLoader
0696: .getClassDescriptor(JAXXObject.class).isAssignableFrom(
0697: super class);
0698: javaFile.setModifiers(Modifier.PUBLIC);
0699: javaFile.setClassName(fullClassName);
0700: javaFile.setSuperClass(getCanonicalName(super class));
0701: javaFile
0702: .setInterfaces(new String[] { getCanonicalName(JAXXObject.class) });
0703:
0704: Iterator/*<CompiledObject>*/i = objects.values().iterator();
0705: while (i.hasNext()) {
0706: CompiledObject object = (CompiledObject) i.next();
0707: if (!object.isOverride()
0708: && !(object instanceof ScriptInitializer)) {
0709: int access = object.getId().startsWith("$") ? Modifier.PRIVATE
0710: : Modifier.PROTECTED;
0711: if (object == root)
0712: javaFile.addField(new JavaField(access,
0713: fullClassName, object.getId(), "this"));
0714: else
0715: javaFile.addField(new JavaField(access,
0716: getCanonicalName(object.getObjectClass()),
0717: object.getId()));
0718: }
0719: }
0720:
0721: if (!super classIsJAXXObject) {
0722: javaFile.addField(new JavaField(Modifier.PROTECTED,
0723: "java.util.List", "$activeBindings",
0724: "new ArrayList()"));
0725: javaFile
0726: .addField(new JavaField(Modifier.PROTECTED,
0727: "java.util.Map", "$bindingSources",
0728: "new HashMap()"));
0729: }
0730:
0731: if (stylesheet != null)
0732: javaFile.addField(new JavaField(0, "java.util.Map",
0733: "$previousValues", "new java.util.HashMap()"));
0734:
0735: javaFile.addMethod(createConstructor(className));
0736: javaFile.addMethod(createInitializer(className));
0737:
0738: for (int j = 0; j < dataBindings.size(); j++) {
0739: DataBinding dataBinding = (DataBinding) dataBindings.get(j);
0740: if (dataBinding.compile(true))
0741: initDataBindings.append("applyDataBinding("
0742: + TypeManager.getJavaCode(dataBinding.getId())
0743: + ");" + JAXXCompiler.getLineSeparator());
0744: }
0745:
0746: javaFile.addBodyCode(bodyCode.toString());
0747:
0748: i = objects.values().iterator();
0749: while (i.hasNext()) {
0750: CompiledObject object = (CompiledObject) i.next();
0751: if (!inlineCreation(object)) {
0752: if (object != root)
0753: javaFile.addMethod(new JavaMethod(
0754: Modifier.PROTECTED, "void", object
0755: .getCreationMethodName(), null,
0756: null, getCreationCode(object)));
0757: }
0758: }
0759:
0760: javaFile.addField(new JavaField(Modifier.PRIVATE, "boolean",
0761: "allComponentsCreated"));
0762:
0763: javaFile.addMethod(createCompleteSetupMethod());
0764:
0765: javaFile.addMethod(new JavaMethod(Modifier.PUBLIC, "void",
0766: "applyDataBinding",
0767: new JavaArgument[] { new JavaArgument("String",
0768: "$binding") }, null, applyDataBinding
0769: .toString()
0770: + getLineSeparator()
0771: + " processDataBinding($binding);"));
0772:
0773: javaFile.addMethod(new JavaMethod(Modifier.PUBLIC, "void",
0774: "removeDataBinding",
0775: new JavaArgument[] { new JavaArgument("String",
0776: "$binding") }, null, removeDataBinding
0777: .toString()));
0778:
0779: javaFile
0780: .addMethod(new JavaMethod(Modifier.PUBLIC, "void",
0781: "processDataBinding",
0782: new JavaArgument[] { new JavaArgument("String",
0783: "dest") }, null,
0784: "processDataBinding(dest, false);"));
0785:
0786: javaFile.addMethod(createProcessDataBindingMethod());
0787:
0788: if (!super classIsJAXXObject) {
0789: javaFile.addField(createObjectMap());
0790: javaFile.addMethod(createGetObjectByIdMethod());
0791: }
0792:
0793: javaFile.addField(createJAXXObjectDescriptorField());
0794: javaFile.addMethod(createGetJAXXObjectDescriptorMethod());
0795:
0796: ClassDescriptor currentClass = root.getObjectClass();
0797: MethodDescriptor firePropertyChange = null;
0798: while (firePropertyChange == null && currentClass != null) {
0799: try {
0800: firePropertyChange = currentClass
0801: .getDeclaredMethodDescriptor(
0802: "firePropertyChange",
0803: new ClassDescriptor[] {
0804: ClassDescriptorLoader
0805: .getClassDescriptor(String.class),
0806: ClassDescriptorLoader
0807: .getClassDescriptor(Object.class),
0808: ClassDescriptorLoader
0809: .getClassDescriptor(Object.class) });
0810:
0811: } catch (NoSuchMethodException e) {
0812: currentClass = currentClass.getSuperclass();
0813: }
0814: }
0815:
0816: int modifiers = firePropertyChange != null ? firePropertyChange
0817: .getModifiers() : 0;
0818: if (Modifier.isPublic(modifiers)) {
0819: // we have all the support we need
0820: }
0821: if (Modifier.isProtected(modifiers)) {
0822: // there is property change support but the firePropertyChange method is protected
0823: javaFile
0824: .addMethod(new JavaMethod(Modifier.PUBLIC, "void",
0825: "firePropertyChange", new JavaArgument[] {
0826: new JavaArgument(
0827: "java.lang.String",
0828: "propertyName"),
0829: new JavaArgument(
0830: "java.lang.Object",
0831: "oldValue"),
0832: new JavaArgument(
0833: "java.lang.Object",
0834: "newValue") }, null,
0835: "super.firePropertyChange(propertyName, oldValue, newValue);"));
0836: } else {
0837: // either no support at all or firePropertyChange isn't accessible
0838: addPropertyChangeSupport(javaFile);
0839: }
0840:
0841: addEventHandlers(javaFile);
0842:
0843: if (ClassDescriptorLoader.getClassDescriptor(Application.class)
0844: .isAssignableFrom(root.getObjectClass())
0845: && !mainDeclared) {
0846: // TODO: check for existing main method first
0847: javaFile
0848: .addMethod(new JavaMethod(Modifier.PUBLIC
0849: | Modifier.STATIC, "void", "main",
0850: new JavaArgument[] { new JavaArgument(
0851: "String[]", "arg") }, null,
0852: "SwingUtilities.invokeLater(new Runnable() { public void run() { new "
0853: + className
0854: + "().setVisible(true); } });"));
0855: }
0856: }
0857:
0858: protected void outputClass(String packageName, String className,
0859: PrintWriter out) throws CompilerException {
0860: createJavaFile(packageName, className);
0861: out.println(javaFile.toString());
0862: }
0863:
0864: public void addImport(String text) {
0865: if (text.endsWith("*"))
0866: importedPackages.add(text.substring(0, text.length() - 1));
0867: else
0868: importedClasses.add(text);
0869:
0870: if (!text.equals("*"))
0871: getJavaFile().addImport(text);
0872: }
0873:
0874: private JavaField createObjectMap() {
0875: return new JavaField(Modifier.PROTECTED, "Map", "$objectMap",
0876: "new HashMap()");
0877: }
0878:
0879: protected JavaMethod createGetObjectByIdMethod() {
0880: return new JavaMethod(Modifier.PUBLIC, "java.lang.Object",
0881: "getObjectById", new JavaArgument[] { new JavaArgument(
0882: "String", "id") }, null,
0883: "return $objectMap.get(id);");
0884: }
0885:
0886: public JAXXObjectDescriptor getJAXXObjectDescriptor() {
0887: runInitializers();
0888: CompiledObject[] components = (CompiledObject[]) new ArrayList/*<CompiledObject>*/(
0889: objects.values()).toArray(new CompiledObject[objects
0890: .size()]);
0891: assert initializers.isEmpty() : "there are pending initializers remaining";
0892: assert root != null : "root object has not been defined";
0893: assert Arrays.asList(components).contains(root) : "root object is not registered";
0894: ComponentDescriptor[] descriptors = new ComponentDescriptor[components.length];
0895: // as we print, sort the array so that component's parents are always before the components themselves
0896: for (int i = 0; i < components.length; i++) {
0897: CompiledObject parent = components[i].getParent();
0898: while (parent != null) {
0899: boolean found = false;
0900: for (int j = i + 1; j < components.length; j++) { // found parent after component, swap them
0901: if (components[j] == parent) {
0902: components[j] = components[i];
0903: components[i] = parent;
0904: found = true;
0905: break;
0906: }
0907: }
0908: if (!found)
0909: break;
0910: parent = components[i].getParent();
0911: }
0912: int parentIndex = -1;
0913: if (parent != null) {
0914: for (int j = 0; j < i; j++) {
0915: if (components[j] == parent) {
0916: parentIndex = j;
0917: break;
0918: }
0919: }
0920: }
0921: descriptors[i] = new ComponentDescriptor(components[i]
0922: .getId(), components[i] == root ? outputClassName
0923: : components[i].getObjectClass().getName(),
0924: components[i].getStyleClass(),
0925: parentIndex != -1 ? descriptors[parentIndex] : null);
0926: }
0927:
0928: Stylesheet stylesheet = getStylesheet();
0929: if (stylesheet == null)
0930: stylesheet = new Stylesheet();
0931:
0932: return new JAXXObjectDescriptor(descriptors, stylesheet);
0933: }
0934:
0935: protected JavaField createJAXXObjectDescriptorField() {
0936: try {
0937: JAXXObjectDescriptor descriptor = getJAXXObjectDescriptor();
0938: ByteArrayOutputStream buffer = new ByteArrayOutputStream();
0939: ObjectOutputStream out = new ObjectOutputStream(
0940: new GZIPOutputStream(buffer));
0941: out.writeObject(descriptor);
0942: out.close();
0943: // the use of the weird deprecated constructor is deliberate -- we need to store the data as a String
0944: // in the compiled class file, since byte array initialization is horribly inefficient compared to
0945: // String initialization. So we store the bytes in the String, and we quite explicitly want a 1:1
0946: // mapping between bytes and chars, with the high byte of the char set to zero. We can then safely
0947: // reconstitute the original byte[] at a later date. This is unquestionably an abuse of the String
0948: // type, but if we could efficiently store a byte[] we wouldn't have to do this.
0949: String data = new String(buffer.toByteArray(), 0);
0950:
0951: int sizeLimit = 65000; // constant strings are limited to 64K, and I'm not brave enough to push right up to the limit
0952: if (data.length() < sizeLimit)
0953: return new JavaField(
0954: Modifier.PRIVATE | Modifier.STATIC,
0955: "java.lang.String", "$jaxxObjectDescriptor",
0956: TypeManager.getJavaCode(data));
0957: else {
0958: StringBuffer initializer = new StringBuffer();
0959: for (int i = 0; i < data.length(); i += sizeLimit) {
0960: String name = "$jaxxObjectDescriptor" + i;
0961: javaFile.addField(new JavaField(Modifier.PRIVATE
0962: | Modifier.STATIC, "java.lang.String",
0963: name, TypeManager
0964: .getJavaCode(data.substring(i, Math
0965: .min(i + sizeLimit, data
0966: .length())))));
0967: if (initializer.length() > 0)
0968: initializer.append(" + ");
0969: initializer.append("String.valueOf(" + name + ")");
0970: }
0971: return new JavaField(
0972: Modifier.PRIVATE | Modifier.STATIC,
0973: "java.lang.String", "$jaxxObjectDescriptor",
0974: initializer.toString());
0975: }
0976: } catch (IOException e) {
0977: throw new RuntimeException(
0978: "Internal error: can't-happen error", e);
0979: }
0980: }
0981:
0982: protected JavaMethod createGetJAXXObjectDescriptorMethod() {
0983: return new JavaMethod(
0984: Modifier.PUBLIC | Modifier.STATIC,
0985: "jaxx.runtime.JAXXObjectDescriptor",
0986: "$getJAXXObjectDescriptor",
0987: null,
0988: null,
0989: "return jaxx.runtime.Util.decodeCompressedJAXXObjectDescriptor($jaxxObjectDescriptor);");
0990: }
0991:
0992: public String getEventHandlerMethodName(EventHandler handler) {
0993: String result = (String) eventHandlerMethodNames.get(handler);
0994: if (result == null) {
0995: result = "$ev" + eventHandlerMethodNames.size();
0996: eventHandlerMethodNames.put(handler, result);
0997: }
0998: return result;
0999: }
1000:
1001: protected void addEventHandlers(JavaFile javaFile) {
1002: Iterator/*Map.Entry<String, Map<ClassDescriptor, List<EventHandler>>>*/i = eventHandlers
1003: .entrySet().iterator();
1004: while (i.hasNext()) { // outer loop is iterating over different objects (well, technically, different Java expressions)
1005: Map.Entry/*<String, Map<ClassDescriptor, List<EventHandler>>*/e1 = (Map.Entry) i
1006: .next();
1007: String expression = (String) e1.getKey();
1008: Iterator/*Map.Entry<ClassDescriptor, List<EventHandler>>*/j = ((Map) e1
1009: .getValue()).entrySet().iterator();
1010: while (j.hasNext()) { // iterate over different types of listeners for this particular object (MouseListener, ComponentListener, etc.)
1011: Map.Entry/*<ClassDescriptor, List<EventHandler>>*/e2 = (Map.Entry) j
1012: .next();
1013: ClassDescriptor listenerClass = (ClassDescriptor) e2
1014: .getKey();
1015: Iterator/*<EventHandler>*/k = ((List) e2.getValue())
1016: .iterator();
1017: while (k.hasNext()) { // iterate over individual event handlers of a single type
1018: EventHandler handler = (EventHandler) k.next();
1019: String methodName = getEventHandlerMethodName(handler);
1020: MethodDescriptor listenerMethod = handler
1021: .getListenerMethod();
1022: if (listenerMethod.getParameterTypes().length != 1)
1023: throw new CompilerException(
1024: "Expected event handler "
1025: + listenerMethod.getName()
1026: + " of class "
1027: + handler.getListenerClass()
1028: + " to have exactly one argument");
1029: javaFile.addMethod(new JavaMethod(Modifier.PUBLIC,
1030: "void", methodName,
1031: new JavaArgument[] { new JavaArgument(
1032: getCanonicalName(listenerMethod
1033: .getParameterTypes()[0]),
1034: "event") }, null, handler
1035: .getJavaCode()));
1036:
1037: }
1038: }
1039: }
1040: }
1041:
1042: protected String getCreationCode(CompiledObject object)
1043: throws CompilerException {
1044: if (object instanceof ScriptInitializer)
1045: return object.getInitializationCode(this );
1046: else {
1047: StringBuffer result = new StringBuffer();
1048: result.append(object.getId());
1049: result.append(" = ");
1050: String constructorParams = object.getConstructorParams();
1051: if (constructorParams != null)
1052: result.append("("
1053: + getCanonicalName(object.getObjectClass())
1054: + ") new "
1055: + getCanonicalName(object.getObjectClass())
1056: + "(" + constructorParams + ");");
1057: else
1058: result.append("new "
1059: + getCanonicalName(object.getObjectClass())
1060: + "();");
1061: result.append(getLineSeparator());
1062: String initCode = object.getInitializationCode(this );
1063: if (initCode != null && initCode.length() > 0)
1064: result.append(initCode);
1065: result.append("$objectMap.put("
1066: + TypeManager.getJavaCode(object.getId()) + ", "
1067: + object.getId() + ");");
1068:
1069: return result.toString();
1070: }
1071: }
1072:
1073: protected void addPropertyChangeSupport(JavaFile javaFile)
1074: throws CompilerException {
1075: javaFile.addField(new JavaField(0,
1076: "java.beans.PropertyChangeSupport",
1077: "$propertyChangeSupport"));
1078:
1079: javaFile
1080: .addMethod(new JavaMethod(
1081: 0,
1082: "java.beans.PropertyChangeSupport",
1083: "$getPropertyChangeSupport",
1084: null,
1085: null,
1086: "if ($propertyChangeSupport == null)\n"
1087: + " $propertyChangeSupport = new PropertyChangeSupport(this);\n"
1088: + "return $propertyChangeSupport;"));
1089:
1090: javaFile
1091: .addMethod(new JavaMethod(Modifier.PUBLIC, "void",
1092: "addPropertyChangeListener",
1093: new JavaArgument[] { new JavaArgument(
1094: "java.beans.PropertyChangeListener",
1095: "listener") }, null,
1096: "$getPropertyChangeSupport().addPropertyChangeListener(listener);"));
1097:
1098: javaFile
1099: .addMethod(new JavaMethod(
1100: Modifier.PUBLIC,
1101: "void",
1102: "addPropertyChangeListener",
1103: new JavaArgument[] {
1104: new JavaArgument("java.lang.String",
1105: "property"),
1106: new JavaArgument(
1107: "java.beans.PropertyChangeListener",
1108: "listener") }, null,
1109: "$getPropertyChangeSupport().addPropertyChangeListener(property, listener);"));
1110:
1111: javaFile
1112: .addMethod(new JavaMethod(Modifier.PUBLIC, "void",
1113: "removePropertyChangeListener",
1114: new JavaArgument[] { new JavaArgument(
1115: "java.beans.PropertyChangeListener",
1116: "listener") }, null,
1117: "$getPropertyChangeSupport().removePropertyChangeListener(listener);"));
1118:
1119: javaFile
1120: .addMethod(new JavaMethod(
1121: Modifier.PUBLIC,
1122: "void",
1123: "removePropertyChangeListener",
1124: new JavaArgument[] {
1125: new JavaArgument("java.lang.String",
1126: "property"),
1127: new JavaArgument(
1128: "java.beans.PropertyChangeListener",
1129: "listener") }, null,
1130: "$getPropertyChangeSupport().removePropertyChangeListener(property, listener);"));
1131:
1132: javaFile
1133: .addMethod(new JavaMethod(
1134: Modifier.PUBLIC,
1135: "void",
1136: "firePropertyChange",
1137: new JavaArgument[] {
1138: new JavaArgument("java.lang.String",
1139: "propertyName"),
1140: new JavaArgument("java.lang.Object",
1141: "oldValue"),
1142: new JavaArgument("java.lang.Object",
1143: "newValue") },
1144: null,
1145: "$getPropertyChangeSupport().firePropertyChange(propertyName, oldValue, newValue);"));
1146: }
1147:
1148: public void compileFirstPass(final Element tag) throws IOException {
1149: tagsBeingCompiled.push(tag);
1150:
1151: String namespace = tag.getNamespaceURI();
1152: String fullClassName = null;
1153: String localName = tag.getLocalName();
1154: boolean namespacePrefix = tag.getPrefix() != null;
1155: // resolve class tags into fully-qualified class name
1156: if (namespace != null && namespace.endsWith("*")) {
1157: String packageName = namespace.substring(0, namespace
1158: .length() - 1);
1159: if (localName.startsWith(packageName)) // class name is fully-qualified already
1160: fullClassName = TagManager.resolveClassName(localName,
1161: this );
1162: else { // namespace not included in class name, probably need the namespace to resolve
1163: fullClassName = TagManager.resolveClassName(packageName
1164: + localName, this );
1165: if (fullClassName == null && !namespacePrefix) // it was just a default namespace, try again without using the namespace
1166: fullClassName = TagManager.resolveClassName(
1167: localName, this );
1168: }
1169: } else
1170: fullClassName = TagManager
1171: .resolveClassName(localName, this );
1172:
1173: if (fullClassName != null) { // we are definitely dealing with a class tag
1174: addDependencyClass(fullClassName);
1175: namespace = fullClassName.substring(0, fullClassName
1176: .lastIndexOf(".") + 1)
1177: + "*";
1178: if (symbolTable.getSuperclassName() == null)
1179: symbolTable.setSuperclassName(fullClassName);
1180: String id = tag.getAttribute("id");
1181: if (id.length() > 0)
1182: symbolTable.getClassTagIds().put(id, fullClassName);
1183: }
1184: // during the first pass, we can't create ClassDescriptors for JAXX files because they may not have been processed yet
1185: // (and we can't wait until they have been processed because of circular dependencies). So we don't do any processing
1186: // during the first pass which requires having a ClassDescriptor; here we determine whether we have a class tag or not
1187: // (class tag namespaces end in "*") and use a generic handler if so. The real handler is used during the second pass.
1188: TagHandler handler = (namespace != null && namespace
1189: .endsWith("*")) ? firstPassClassTagHandler : TagManager
1190: .getTagHandler(tag.getNamespaceURI(), localName,
1191: namespacePrefix, this );
1192: if (handler != firstPassClassTagHandler
1193: && handler instanceof DefaultObjectHandler) {
1194: fullClassName = ((DefaultObjectHandler) handler)
1195: .getBeanClass().getName();
1196: namespace = fullClassName.substring(0, fullClassName
1197: .lastIndexOf(".") + 1)
1198: + "*";
1199: handler = firstPassClassTagHandler;
1200: }
1201: if (handler == firstPassClassTagHandler) {
1202: final String finalClassName = fullClassName;
1203: registerInitializer(new Runnable() { // register an initializer which will create the CompiledObject after pass 1
1204: public void run() {
1205: DefaultObjectHandler handler = (DefaultObjectHandler) TagManager
1206: .getTagHandler(null, finalClassName,
1207: JAXXCompiler.this );
1208: if (handler == null)
1209: throw new CompilerException(
1210: "Internal error: missing TagHandler for '"
1211: + finalClassName + "'");
1212: handler.registerCompiledObject(tag,
1213: JAXXCompiler.this );
1214: }
1215: });
1216: }
1217: if (handler != null) {
1218: try {
1219: handler.compileFirstPass(tag, this );
1220: } catch (CompilerException e) {
1221: reportError(e);
1222: }
1223: } else {
1224: reportError("Could not find a Java class corresponding to: <"
1225: + tag.getTagName() + ">");
1226: failed = true;
1227: }
1228:
1229: Element finished = (Element) tagsBeingCompiled.pop();
1230: if (finished != tag)
1231: throw new RuntimeException(
1232: "internal error: just finished compiling "
1233: + tag
1234: + ", but top of tagsBeingCompiled stack is "
1235: + finished);
1236: }
1237:
1238: public void compileSecondPass(Element tag) throws IOException {
1239: tagsBeingCompiled.push(tag);
1240:
1241: TagHandler handler = TagManager.getTagHandler(tag
1242: .getNamespaceURI(), tag.getLocalName(),
1243: tag.getPrefix() != null, this );
1244: if (handler != null)
1245: handler.compileSecondPass(tag, this );
1246: else {
1247: reportError("Could not find a Java class corresponding to: <"
1248: + tag.getTagName() + ">");
1249: assert false : "can't-happen error: error should have been reported during the fast pass and caused an abort";
1250: failed = true;
1251: }
1252:
1253: Element finished = (Element) tagsBeingCompiled.pop();
1254: if (finished != tag)
1255: throw new RuntimeException(
1256: "internal error: just finished compiling "
1257: + tag
1258: + ", but top of tagsBeingCompiled stack is "
1259: + finished);
1260:
1261: }
1262:
1263: // 1.5 adds getCanonicalName; unfortunately we can't depend on 1.5 features yet
1264: public static String getCanonicalName(Class clazz) {
1265: if (clazz.isArray()) {
1266: String canonicalName = getCanonicalName(clazz
1267: .getComponentType());
1268: if (canonicalName != null)
1269: return canonicalName + "[]";
1270: else
1271: return null;
1272: } else
1273: return clazz.getName().replace('$', '.');
1274: }
1275:
1276: public static String getCanonicalName(ClassDescriptor clazz) {
1277: if (clazz.isArray()) {
1278: String canonicalName = getCanonicalName(clazz
1279: .getComponentType());
1280: if (canonicalName != null)
1281: return canonicalName + "[]";
1282: else
1283: return null;
1284: } else
1285: return clazz.getName().replace('$', '.');
1286: }
1287:
1288: public static String capitalize(String s) {
1289: if (s.length() == 0)
1290: return s;
1291: return Character.toUpperCase(s.charAt(0)) + s.substring(1);
1292: }
1293:
1294: public String[] parseParameterList(String parameters)
1295: throws CompilerException {
1296: List/*<String>*/result = new ArrayList/*<String>*/();
1297: StringBuffer current = new StringBuffer();
1298: int state = 0; // normal
1299: for (int i = 0; i < parameters.length(); i++) {
1300: char c = parameters.charAt(i);
1301: switch (state) {
1302: case 0: // normal
1303: switch (c) {
1304: case '"':
1305: current.append(c);
1306: state = 1;
1307: break; // in quoted string
1308: case '\\':
1309: current.append(c);
1310: state = 2;
1311: break; // immediately after backslash
1312: case ',':
1313: if (current.length() > 0) {
1314: result.add(current.toString());
1315: current.setLength(0);
1316: break;
1317: } else
1318: reportError("error parsing parameter list: "
1319: + parameters);
1320: default:
1321: current.append(c);
1322: }
1323: break;
1324: case 1: // in quoted string
1325: switch (c) {
1326: case '"':
1327: current.append(c);
1328: state = 0;
1329: break; // normal
1330: case '\\':
1331: current.append(c);
1332: state = 3;
1333: break; // immediate after backslash in quoted string
1334: default:
1335: current.append(c);
1336: }
1337: break;
1338: case 2: // immediately after backslash
1339: current.append(c);
1340: state = 0; // normal
1341: break;
1342: case 3: // immediately after backslash in quoted string
1343: current.append(c);
1344: state = 1; // in quoted string
1345: break;
1346: }
1347: }
1348: if (current.length() > 0)
1349: result.add(current.toString());
1350: return (String[]) result.toArray(new String[result.size()]);
1351: }
1352:
1353: public void openComponent(CompiledObject component)
1354: throws CompilerException {
1355: openComponent(component, null);
1356: }
1357:
1358: public void openComponent(CompiledObject component,
1359: String constraints) throws CompilerException {
1360: CompiledObject parent = getOpenComponent();
1361: openInvisibleComponent(component);
1362: if (parent != null && !component.isOverride())
1363: parent.addChild(component, constraints, this );
1364: }
1365:
1366: public void openInvisibleComponent(CompiledObject component) {
1367: if (!ids.containsKey(component))
1368: registerCompiledObject(component);
1369: openComponents.push(component);
1370: }
1371:
1372: public CompiledObject getOpenComponent() {
1373: if (openComponents.isEmpty())
1374: return null;
1375: else
1376: return (CompiledObject) openComponents.peek();
1377: }
1378:
1379: public void closeComponent(CompiledObject component) {
1380: if (openComponents.pop() != component)
1381: throw new IllegalArgumentException(
1382: "can only close the topmost open object");
1383: }
1384:
1385: public CompiledObject getRootObject() {
1386: return root;
1387: }
1388:
1389: public void registerCompiledObject(CompiledObject object) {
1390: assert symbolTables.values().contains(symbolTable) : "attempting to register CompiledObject before pass 1 is complete";
1391: if (root == null)
1392: root = object;
1393:
1394: String id = object.getId();
1395: if (ids.containsKey(object))
1396: reportError("object '" + object
1397: + "' is already registered with id '"
1398: + ids.get(object) + "', cannot re-register as '"
1399: + id + "'");
1400: if (objects.containsKey(id)
1401: && !(objects.get(id) instanceof Element))
1402: reportError("id '" + id
1403: + "' is already registered to component "
1404: + objects.get(id));
1405: objects.put(id, object);
1406: ids.put(object, id);
1407: }
1408:
1409: public String getAutoId(ClassDescriptor objectClass) {
1410: if (options.getOptimize()) {
1411: return "$" + Integer.toString(autogenID++, 36);
1412: } else {
1413: String name = objectClass.getName();
1414: name = name.substring(name.lastIndexOf(".") + 1);
1415: return "$" + name + autogenID++;
1416: }
1417: }
1418:
1419: public String getUniqueId(Object object) {
1420: String result = (String) uniqueIds.get(object);
1421: if (result == null) {
1422: result = "$u" + uniqueIds.size();
1423: uniqueIds.put(object, result);
1424: }
1425: return result;
1426: }
1427:
1428: public SymbolTable getSymbolTable() {
1429: return symbolTable;
1430: }
1431:
1432: public CompiledObject getCompiledObject(String id) {
1433: runInitializers();
1434: assert symbolTables.values().contains(symbolTable) : "attempting to retrieve CompiledObject before pass 1 is complete";
1435: return (CompiledObject) objects.get(id);
1436: }
1437:
1438: private Matcher leftBraceMatcher = Pattern.compile(
1439: "^(\\{)|[^\\\\](\\{)").matcher("");
1440:
1441: private int getNextLeftBrace(String string, int pos) {
1442: leftBraceMatcher.reset(string);
1443: return leftBraceMatcher.find(pos) ? Math.max(leftBraceMatcher
1444: .start(1), leftBraceMatcher.start(2)) : -1;
1445: }
1446:
1447: private Matcher rightBraceMatcher = Pattern.compile(
1448: "^(\\})|[^\\\\](\\})").matcher("");
1449:
1450: private int getNextRightBrace(String string, int pos) {
1451: leftBraceMatcher.reset(string);
1452: rightBraceMatcher.reset(string);
1453: int openCount = 1;
1454: int rightPos = -1;
1455: while (openCount > 0) {
1456: pos++;
1457: int leftPos = leftBraceMatcher.find(pos) ? Math.max(
1458: leftBraceMatcher.start(1), leftBraceMatcher
1459: .start(2)) : -1;
1460: rightPos = rightBraceMatcher.find(pos) ? Math.max(
1461: rightBraceMatcher.start(1), rightBraceMatcher
1462: .start(2)) : -1;
1463: assert leftPos == -1 || leftPos >= pos;
1464: assert rightPos == -1 || rightPos >= pos;
1465: if (leftPos != -1 && leftPos < rightPos) {
1466: pos = leftPos;
1467: openCount++;
1468: } else if (rightPos != -1) {
1469: pos = rightPos;
1470: openCount--;
1471: } else
1472: openCount = 0;
1473: }
1474: return pos;
1475: }
1476:
1477: /** Examine an attribute value for data binding expressions. Returns a 'cooked' expression which
1478: * can be used to determine the resulting value. It is expected that this expression will be used
1479: * as the source expression in a call to {@link #registerDataBinding}.
1480: * If the attribute value does not invoke data binding, this method returns <code>null</code>
1481: *
1482: *@param stringValue the string value of the property from the XML
1483: *@param type the type of the property, from the <code>JAXXPropertyDescriptor</code>
1484: *@return a processed version of the expression
1485: */
1486: public String processDataBindings(String stringValue,
1487: ClassDescriptor type) throws CompilerException {
1488: int pos = getNextLeftBrace(stringValue, 0);
1489: if (pos != -1) {
1490: StringBuffer expression = new StringBuffer();
1491: int lastPos = 0;
1492: while (pos != -1 && pos < stringValue.length()) {
1493: if (pos > lastPos) {
1494: if (expression.length() > 0)
1495: expression.append(" + ");
1496: expression.append('"');
1497: expression.append(JAXXCompiler
1498: .escapeJavaString(stringValue.substring(
1499: lastPos, pos)));
1500: expression.append('"');
1501: }
1502:
1503: if (expression.length() > 0)
1504: expression.append(" + ");
1505: expression.append('(');
1506: int pos2 = getNextRightBrace(stringValue, pos + 1);
1507: if (pos2 == -1) {
1508: reportError("unmatched '{' in expression: "
1509: + stringValue);
1510: return "";
1511: }
1512: expression.append(stringValue.substring(pos + 1, pos2));
1513: expression.append(')');
1514: pos2++;
1515: if (pos2 < stringValue.length()) {
1516: pos = getNextLeftBrace(stringValue, pos2);
1517: lastPos = pos2;
1518: } else {
1519: pos = stringValue.length();
1520: lastPos = pos;
1521: }
1522: }
1523: if (lastPos < stringValue.length()) {
1524: if (expression.length() > 0)
1525: expression.append(" + ");
1526: expression.append('"');
1527: expression.append(JAXXCompiler
1528: .escapeJavaString(stringValue
1529: .substring(lastPos)));
1530: expression.append('"');
1531: }
1532: return type == ClassDescriptorLoader
1533: .getClassDescriptor(String.class) ? "String.valueOf("
1534: + expression + ")"
1535: : expression.toString();
1536: }
1537: return null;
1538: }
1539:
1540: public void registerDataBinding(String src, String dest,
1541: String assignment) {
1542: try {
1543: src = checkJavaCode(src);
1544: dataBindings.add(new DataBinding(src, dest, assignment,
1545: this ));
1546: } catch (CompilerException e) {
1547: reportError("While parsing data binding for '"
1548: + dest.substring(dest.lastIndexOf(".") + 1) + "': "
1549: + e.getMessage());
1550: }
1551: }
1552:
1553: public ScriptManager getScriptManager() {
1554: return scriptManager;
1555: }
1556:
1557: /** Verifies that a snippet of Java code parses correctly. A warning is generated if the string has enclosing
1558: * curly braces. Returns a "cooked" version of the string which has enclosing curly braces removed.
1559: *
1560: *@param javaCode the Java code snippet to test
1561: *@throws CompilerException if the code cannot be parsed
1562: */
1563: public String checkJavaCode(String javaCode) {
1564: javaCode = scriptManager.trimScript(javaCode);
1565: scriptManager.checkParse(javaCode);
1566: return javaCode;
1567: }
1568:
1569: public void registerEventHandler(EventHandler handler) {
1570: String objectCode = handler.getObjectCode();
1571: Map/*<ClassDescriptor, List<EventHandler>>*/listeners = (Map) eventHandlers
1572: .get(objectCode);
1573: if (listeners == null) {
1574: listeners = new HashMap/*<ClassDescriptor, List<EventHandler>>*/();
1575: eventHandlers.put(objectCode, listeners);
1576: }
1577: ClassDescriptor listenerClass = handler.getListenerClass();
1578: List/*<EventHandler>*/handlerList = (List) listeners
1579: .get(listenerClass);
1580: if (handlerList == null) {
1581: handlerList = new ArrayList/*<EventHandler>*/();
1582: listeners.put(listenerClass, handlerList);
1583: }
1584: handlerList.add(handler);
1585: }
1586:
1587: public FieldDescriptor[] getScriptFields() {
1588: List/*<FieldDescriptor>*/scriptFields = symbolTable
1589: .getScriptFields();
1590: return (FieldDescriptor[]) scriptFields
1591: .toArray(new FieldDescriptor[scriptFields.size()]);
1592: }
1593:
1594: public void addScriptField(FieldDescriptor field) {
1595: symbolTable.getScriptFields().add(field);
1596: }
1597:
1598: public MethodDescriptor[] getScriptMethods() {
1599: List/*<MethodDescriptor>*/scriptMethods = symbolTable
1600: .getScriptMethods();
1601: return (MethodDescriptor[]) scriptMethods
1602: .toArray(new MethodDescriptor[scriptMethods.size()]);
1603: }
1604:
1605: public void addScriptMethod(MethodDescriptor method) {
1606: if (method.getName().equals("main")
1607: && method.getParameterTypes().length == 1
1608: && method.getParameterTypes()[0].getName().equals(
1609: "[Ljava.lang.String;"))
1610: mainDeclared = true;
1611: symbolTable.getScriptMethods().add(method);
1612: }
1613:
1614: public void registerScript(String script) throws CompilerException {
1615: registerScript(script, null);
1616: }
1617:
1618: public void registerScript(String script, File sourceFile)
1619: throws CompilerException {
1620: if (sourceFile != null)
1621: sourceFiles.push(sourceFile);
1622: scriptManager.registerScript(script);
1623:
1624: if (sourceFile != null) {
1625: File pop = (File) sourceFiles.pop();
1626: if (pop != sourceFile)
1627: throw new RuntimeException(
1628: "leaving registerScript(), but "
1629: + sourceFile
1630: + " was not the top entry on the stack (found "
1631: + pop + " instead)");
1632: }
1633: }
1634:
1635: public String preprocessScript(String script)
1636: throws CompilerException {
1637: return scriptManager.preprocessScript(script);
1638: }
1639:
1640: public void registerStylesheet(Stylesheet stylesheet) {
1641: if (this .stylesheet == null)
1642: this .stylesheet = stylesheet;
1643: else
1644: this .stylesheet.add(stylesheet.getRules());
1645: }
1646:
1647: public Stylesheet getStylesheet() {
1648: Stylesheet merged = new Stylesheet();
1649: if (stylesheet != null)
1650: merged.add(stylesheet.getRules());
1651: merged.add((Rule[]) inlineStyles.toArray(new Rule[inlineStyles
1652: .size()]));
1653: return merged;
1654: }
1655:
1656: public Stack/*<File>*/getSourceFiles() {
1657: return sourceFiles;
1658: }
1659:
1660: public void addInlineStyle(CompiledObject object,
1661: String propertyName, boolean dataBinding) {
1662: inlineStyles.add(Rule.inlineAttribute(object, propertyName,
1663: dataBinding));
1664: }
1665:
1666: public void reportWarning(String warning) {
1667: Element currentTag = null;
1668: if (!tagsBeingCompiled.isEmpty())
1669: currentTag = (Element) tagsBeingCompiled.peek();
1670: reportWarning(currentTag, warning, 0);
1671: }
1672:
1673: public void reportWarning(Element tag, String warning,
1674: int lineOffset) {
1675: String lineNumber = null;
1676: if (tag != null) {
1677: String lineAttr = tag.getAttributeNS(
1678: JAXX_INTERNAL_NAMESPACE, "line");
1679: if (lineAttr.length() > 0)
1680: lineNumber = lineAttr;
1681: }
1682: File src = (File) sourceFiles.peek();
1683: try {
1684: src = src.getCanonicalFile();
1685: } catch (IOException e) {
1686: }
1687:
1688: System.err.print(src);
1689: if (lineNumber != null)
1690: System.err.print(":"
1691: + ((sourceFiles.size() == 1) ? Integer
1692: .parseInt(lineNumber)
1693: + lineOffset : lineOffset + 1));
1694: System.err.println(": Warning: " + warning);
1695: warningCount++;
1696: }
1697:
1698: public void reportError(String error) {
1699: Element currentTag = null;
1700: if (!tagsBeingCompiled.isEmpty())
1701: currentTag = (Element) tagsBeingCompiled.peek();
1702: reportError(currentTag, error);
1703: }
1704:
1705: public void reportError(CompilerException ex) {
1706: reportError(null, ex);
1707: }
1708:
1709: public void reportError(String extraMessage, CompilerException ex) {
1710: String message = ex.getMessage();
1711: if (ex.getClass() == UnsupportedAttributeException.class
1712: || ex.getClass() == UnsupportedTagException.class)
1713: message = ex.getClass().getName().substring(
1714: ex.getClass().getName().lastIndexOf(".") + 1)
1715: + ": " + message;
1716: int lineOffset;
1717: if (ex instanceof ParseException)
1718: lineOffset = Math.max(0,
1719: ((ParseException) ex).getLine() - 1);
1720: else
1721: lineOffset = 0;
1722: Element currentTag = null;
1723: if (!tagsBeingCompiled.isEmpty())
1724: currentTag = (Element) tagsBeingCompiled.peek();
1725: reportError(currentTag, extraMessage != null ? extraMessage
1726: + message : message, lineOffset);
1727: }
1728:
1729: public void reportError(Element tag, String error) {
1730: reportError(tag, error, 0);
1731: }
1732:
1733: public void reportError(Element tag, String error, int lineOffset) {
1734: int lineNumber = 0;
1735: if (tag != null) {
1736: String lineAttr = tag.getAttributeNS(
1737: JAXX_INTERNAL_NAMESPACE, "line");
1738: if (lineAttr.length() > 0)
1739: lineNumber = Integer.parseInt(lineAttr);
1740: }
1741: lineNumber = Math.max(lineNumber, 1) + lineOffset;
1742: reportError(lineNumber, error);
1743: }
1744:
1745: public void reportError(int lineNumber, String error) {
1746: File src = sourceFiles.isEmpty() ? null : (File) sourceFiles
1747: .peek();
1748: try {
1749: if (src != null)
1750: src = src.getCanonicalFile();
1751: } catch (IOException e) {
1752: }
1753:
1754: System.err.print(src != null ? src.getPath()
1755: : "<unknown source>");
1756: if (lineNumber > 0)
1757: System.err.print(":" + lineNumber);
1758: System.err.println(": " + error);
1759: errorCount++;
1760: failed = true;
1761: }
1762:
1763: /** Escapes a string using standard Java escape sequences, generally in preparation to including it in a string literal
1764: * in a compiled Java file.
1765: *
1766: *@param raw the raw string to be escape
1767: *@return a string in which all 'dangerous' characters have been replaced by equivalent Java escape sequences
1768: **/
1769: public static String escapeJavaString(String raw) {
1770: StringBuffer out = new StringBuffer(raw);
1771: for (int i = 0; i < out.length(); i++) {
1772: char c = out.charAt(i);
1773: if (c == '\\' || c == '"') {
1774: out.insert(i, '\\');
1775: i++;
1776: } else if (c == '\n') {
1777: out.replace(i, i + 1, "\\n");
1778: i++;
1779: } else if (c == '\r') {
1780: out.replace(i, i + 1, "\\r");
1781: i++;
1782: } else if (c < 32 || c > 127) {
1783: String value = Integer.toString((int) c, 16);
1784: while (value.length() < 4)
1785: value = "0" + value;
1786: out.replace(i, i + 1, "\\u" + value);
1787: i += 5;
1788: }
1789: }
1790: return out.toString();
1791: }
1792:
1793: /** Returns the system line separator string.
1794: *
1795: *@return the string used to separate lines
1796: */
1797: public static String getLineSeparator() {
1798: return System.getProperty("line.separator", "\n");
1799: }
1800:
1801: /** Returns a <code>ClassLoader</code> which searches the user-specified class path in addition
1802: * to the normal system class path.
1803: *
1804: *@return <code>ClassLoader</code> to use while resolving class references
1805: */
1806: public ClassLoader getClassLoader() {
1807: if (classLoader == null) {
1808: String classPath = options.getClassPath();
1809: if (classPath == null)
1810: classPath = ".";
1811: String[] paths = classPath.split(File.pathSeparator);
1812: URL[] urls = new URL[paths.length];
1813: for (int i = 0; i < paths.length; i++) {
1814: try {
1815: urls[i] = new File(paths[i]).toURL();
1816: } catch (MalformedURLException e) {
1817: throw new RuntimeException(e);
1818: }
1819: }
1820: classLoader = new URLClassLoader(urls, getClass()
1821: .getClassLoader());
1822: }
1823:
1824: return classLoader;
1825: }
1826:
1827: /** Returns the compiler instance which is processing the specified JAXX class. Each class is compiled by a
1828: * different compiler instance.
1829: */
1830: public static JAXXCompiler getJAXXCompiler(String className) {
1831: return compilers != null ? (JAXXCompiler) compilers
1832: .get(className) : null;
1833: }
1834:
1835: /** Returns the symbol table for the specified JAXX class. Must be called during the second compiler pass.
1836: * Returns <code>null</code> if no such symbol table could be found.
1837: */
1838: public static SymbolTable getSymbolTable(String className) {
1839: JAXXCompiler compiler = getJAXXCompiler(className);
1840: if (compiler == null)
1841: return null;
1842: return compiler.getSymbolTable();
1843: }
1844:
1845: public static File URLtoFile(URL url) {
1846: return URLtoFile(url.toString());
1847: }
1848:
1849: public static File URLtoFile(String urlString) {
1850: if (!urlString.startsWith("file:"))
1851: throw new IllegalArgumentException(
1852: "url must start with 'file:'");
1853: urlString = urlString.substring("file:".length());
1854: if (urlString.startsWith("/")
1855: && System.getProperty("os.name").startsWith("Windows"))
1856: urlString = urlString.substring(1);
1857: try {
1858: return new File(URLDecoder.decode(urlString.replace('/',
1859: File.separatorChar), "utf-8"));
1860: } catch (UnsupportedEncodingException e) {
1861: throw new RuntimeException(e);
1862: }
1863: }
1864:
1865: public void addDependencyClass(String className) {
1866: if (!jaxxFileClassNames.contains(className)) {
1867: URL jaxxURL = getClassLoader().getResource(
1868: className.replace('.', '/') + ".jaxx");
1869: URL classURL = getClassLoader().getResource(
1870: className.replace('.', '/') + ".class");
1871: if (jaxxURL != null && classURL != null) {
1872: try {
1873: File jaxxFile = URLtoFile(jaxxURL);
1874: File classFile = URLtoFile(classURL);
1875: if (classFile.lastModified() > jaxxFile
1876: .lastModified())
1877: return; // class file is newer, no need to recompile
1878: } catch (Exception e) {
1879: // do nothing
1880: }
1881: }
1882:
1883: if (jaxxURL != null
1884: && jaxxURL.toString().startsWith("file:")) {
1885: File jaxxFile = URLtoFile(jaxxURL);
1886: try {
1887: jaxxFile = jaxxFile.getCanonicalFile();
1888: } catch (IOException ex) {
1889: }
1890: assert jaxxFile.getName()
1891: .equalsIgnoreCase(
1892: className.substring(className
1893: .lastIndexOf(".") + 1)
1894: + ".jaxx") : "expecting file name to match "
1895: + className
1896: + ", but found "
1897: + jaxxFile.getName();
1898: if (jaxxFile.getName()
1899: .equals(
1900: className.substring(className
1901: .lastIndexOf(".") + 1)
1902: + ".jaxx")) { // check case match
1903: if (currentPass == PASS_2)
1904: throw new AssertionError(
1905: "Internal error: adding dependency class "
1906: + className
1907: + " during second compilation pass");
1908: jaxxFileClassNames.add(className);
1909: jaxxFiles.add(jaxxFile);
1910: } else
1911: return; // case mismatch, ignore
1912: }
1913: }
1914: }
1915:
1916: /** Compiled a set of files, expressed as paths relative to a base directory. The class names of the compiled files are derived
1917: * from the relative path strings (e.g. "example/Foo.jaxx" compiles into a class named "example.Foo"). Returns <code>true</code>
1918: * if compilation succeeds, <code>false</code> if it fails. Warning and error messages are sent to <code>System.err</code>.
1919: *
1920: *@param base the directory against which to resolve relative paths
1921: *@param relativePaths a list of relative paths to .jaxx files being compiled
1922: *@param options the compiler options to use
1923: *@return <code>true</code> if compilation succeeds, <code>false</code> otherwise
1924: */
1925: public static synchronized boolean compile(File base,
1926: String[] relativePaths, CompilerOptions options) {
1927: File[] files = new File[relativePaths.length];
1928: String[] classNames = new String[relativePaths.length];
1929: for (int i = 0; i < files.length; i++) {
1930: files[i] = new File(base, relativePaths[i]);
1931: classNames[i] = relativePaths[i].substring(0,
1932: relativePaths[i].lastIndexOf("."));
1933: classNames[i] = classNames[i].replace(File.separatorChar,
1934: '.');
1935: classNames[i] = classNames[i].replace('/', '.');
1936: classNames[i] = classNames[i].replace('\\', '.');
1937: classNames[i] = classNames[i].replace(':', '.');
1938: }
1939: return compile(files, classNames, options);
1940: }
1941:
1942: /** Resets all state in preparation for a new compilation session. */
1943: private static void reset() {
1944: errorCount = 0;
1945: warningCount = 0;
1946: jaxxFiles.clear();
1947: jaxxFileClassNames.clear();
1948: symbolTables.clear();
1949: compilers.clear();
1950: }
1951:
1952: /** Compiled a set of files, with the class names specified explicitly. The class compiled from files[i] will be named classNames[i].
1953: * Returns <code>true</code> if compilation succeeds, <code>false</code> if it fails. Warning and error messages are sent to
1954: * <code>System.err</code>.
1955: *
1956: *@param files the .jaxx files to compile
1957: *@param classNames the names of the classes being compiled
1958: *@param options the compiler options to use
1959: *@return <code>true</code> if compilation succeeds, <code>false</code> otherwise
1960: */
1961: public static synchronized boolean compile(File[] files,
1962: String[] classNames, CompilerOptions options) {
1963: reset(); // just to be safe...
1964: jaxxFiles.addAll(Arrays.asList(files));
1965: jaxxFileClassNames.addAll(Arrays.asList(classNames));
1966: try {
1967: boolean success = true;
1968:
1969: // pass 1
1970: currentPass = PASS_1;
1971: boolean compiled;
1972: do {
1973: compiled = false;
1974: assert jaxxFiles.size() == jaxxFileClassNames.size();
1975: Iterator/*<String>*/filesIterator = new ArrayList/*<File>*/(
1976: jaxxFiles).iterator(); // clone it so it can safely be modified while we're iterating
1977: Iterator/*<String>*/classNamesIterator = new ArrayList/*<String>*/(
1978: jaxxFileClassNames).iterator();
1979: while (filesIterator.hasNext()) {
1980: File file = (File) filesIterator.next();
1981: String className = (String) classNamesIterator
1982: .next();
1983: if (symbolTables.get(file) == null) {
1984: compiled = true;
1985: if (compilers.containsKey(className))
1986: throw new CompilerException(
1987: "Internal error: "
1988: + className
1989: + " is already being compiled, attempting to compile it again");
1990:
1991: File destDir = options.getTargetDirectory();
1992: if (destDir != null) {
1993: int dotPos = className.lastIndexOf(".");
1994: if (dotPos != -1)
1995: destDir = new File(destDir, className
1996: .substring(0, dotPos)
1997: .replace('.',
1998: File.separatorChar));
1999: destDir.mkdirs();
2000: } else
2001: destDir = file.getParentFile();
2002: JAXXCompiler compiler = new JAXXCompiler(file
2003: .getParentFile(), file, className,
2004: options);
2005: compilers.put(className, compiler);
2006: compiler.compileFirstPass();
2007: assert !symbolTables.values().contains(
2008: compiler.getSymbolTable()) : "symbolTable is already registered";
2009: symbolTables.put(file, compiler
2010: .getSymbolTable());
2011: if (compiler.failed)
2012: success = false;
2013: }
2014: }
2015:
2016: } while (compiled);
2017:
2018: // pass 2
2019: currentPass = PASS_2;
2020: if (success) {
2021: assert jaxxFiles.size() == jaxxFileClassNames.size();
2022: List/*<String>*/jaxxFilesClone = new ArrayList(
2023: jaxxFiles);
2024: Iterator/*<String>*/filesIterator = jaxxFilesClone
2025: .iterator();
2026: Iterator/*<String>*/classNamesIterator = jaxxFileClassNames
2027: .iterator();
2028: while (filesIterator.hasNext()) {
2029: File file = (File) filesIterator.next();
2030: String className = (String) classNamesIterator
2031: .next();
2032: JAXXCompiler compiler = (JAXXCompiler) compilers
2033: .get(className);
2034: if (compiler == null)
2035: throw new CompilerException(
2036: "Internal error: could not find compiler for "
2037: + className
2038: + " during second pass");
2039: if (!compiler.failed)
2040: compiler.runInitializers();
2041: compiler.compileSecondPass();
2042: if (compiler.failed)
2043: success = false;
2044: }
2045: if (!jaxxFilesClone.equals(jaxxFiles))
2046: throw new AssertionError(
2047: "Internal error: compilation set altered during pass 2 (was "
2048: + jaxxFilesClone + ", modified to "
2049: + jaxxFiles + ")");
2050: }
2051:
2052: // stylesheet application
2053: if (success) {
2054: assert jaxxFiles.size() == jaxxFileClassNames.size();
2055: Iterator/*<String>*/filesIterator = jaxxFiles
2056: .iterator();
2057: Iterator/*<String>*/classNamesIterator = jaxxFileClassNames
2058: .iterator();
2059: while (filesIterator.hasNext()) {
2060: File file = (File) filesIterator.next();
2061: String className = (String) classNamesIterator
2062: .next();
2063: JAXXCompiler compiler = (JAXXCompiler) compilers
2064: .get(className);
2065: if (compiler == null)
2066: throw new CompilerException(
2067: "Internal error: could not find compiler for "
2068: + className
2069: + " during stylesheet application");
2070: compiler.applyStylesheets();
2071: if (compiler.failed)
2072: success = false;
2073: }
2074: }
2075:
2076: // code generation
2077: if (success) {
2078: assert jaxxFiles.size() == jaxxFileClassNames.size();
2079: Iterator/*<String>*/filesIterator = jaxxFiles
2080: .iterator();
2081: Iterator/*<String>*/classNamesIterator = jaxxFileClassNames
2082: .iterator();
2083: while (filesIterator.hasNext()) {
2084: File file = (File) filesIterator.next();
2085: String className = (String) classNamesIterator
2086: .next();
2087: JAXXCompiler compiler = (JAXXCompiler) compilers
2088: .get(className);
2089: if (compiler == null)
2090: throw new CompilerException(
2091: "Internal error: could not find compiler for "
2092: + className
2093: + " during code generation");
2094: compiler.generateCode();
2095: if (compiler.failed)
2096: success = false;
2097: }
2098: }
2099:
2100: // javac
2101: if (success && options.getRunJavac()) {
2102: assert jaxxFiles.size() == jaxxFileClassNames.size();
2103: Iterator/*<String>*/filesIterator = jaxxFiles
2104: .iterator();
2105: Iterator/*<String>*/classNamesIterator = jaxxFileClassNames
2106: .iterator();
2107: while (filesIterator.hasNext()) {
2108: File file = (File) filesIterator.next();
2109: String className = (String) classNamesIterator
2110: .next();
2111: JAXXCompiler compiler = (JAXXCompiler) compilers
2112: .get(className);
2113: if (compiler == null)
2114: throw new CompilerException(
2115: "Internal error: could not find compiler for "
2116: + className
2117: + " during compilation");
2118: compiler.runJavac();
2119: if (compiler.failed)
2120: success = false;
2121: }
2122: }
2123:
2124: if (warningCount == 1)
2125: System.err.println("1 warning");
2126: else if (warningCount > 0)
2127: System.err.println(warningCount + " warnings");
2128:
2129: if (errorCount == 1)
2130: System.err.println("1 error");
2131: else if (errorCount > 0)
2132: System.err.println(errorCount + " errors");
2133:
2134: return success;
2135: } catch (CompilerException e) {
2136: System.err.println(e.getMessage());
2137: e.printStackTrace();
2138: return false;
2139: } catch (Throwable e) {
2140: e.printStackTrace();
2141: return false;
2142: } finally {
2143: reset();
2144: }
2145: }
2146:
2147: private static void showUsage() {
2148: System.out.println("Usage: jaxxc <options> <source files>");
2149: System.out.println();
2150: System.out.println("Source files must end in extension .jaxx");
2151: System.out
2152: .println("Use JAXX_OPTS environment variable to pass arguments to Java runtime");
2153: System.out.println();
2154: System.out.println("Supported options include:");
2155: System.out
2156: .println(" -classpath <paths> paths to search for user classes");
2157: System.out.println(" -cp <paths> same as -classpath");
2158: System.out
2159: .println(" -d <directory> target directory for generated class files");
2160: System.out
2161: .println(" -javac_opts <opts> options to pass to javac");
2162: System.out
2163: .println(" -java or -j produce .java files, but do not compile them");
2164: System.out
2165: .println(" -keep or -k preserve generated .java files after compilation");
2166: System.out
2167: .println(" -optimize or -o optimize during compilation");
2168: System.out
2169: .println(" -version display version information");
2170: System.out.println();
2171: System.out
2172: .println("See http://www.jaxxframework.org/ for full documentation.");
2173: }
2174:
2175: public static String getVersion() {
2176: return "1.0.3-beta2";
2177: }
2178:
2179: public static void main(String[] arg) throws Exception {
2180: boolean success = true;
2181:
2182: CompilerOptions options = new CompilerOptions();
2183: List/*<String>*/files = new ArrayList/*<String>*/();
2184: for (int i = 0; i < arg.length; i++) {
2185: if (arg[i].endsWith(".jaxx")) {
2186: files.add(arg[i]);
2187: } else if (arg[i].equals("-d")) {
2188: if (++i < arg.length) {
2189: File targetDirectory = new File(arg[i]);
2190: if (!targetDirectory.exists()) {
2191: System.err
2192: .println("Error: could not find target directory: "
2193: + targetDirectory);
2194: errorCount++;
2195: success = false;
2196: }
2197: options.setTargetDirectory(targetDirectory);
2198: } else
2199: success = false;
2200: } else if (arg[i].equals("-cp")
2201: || arg[i].equals("-classpath")) {
2202: if (++i < arg.length)
2203: options.setClassPath(arg[i]);
2204: else
2205: success = false;
2206: } else if (arg[i].equals("-javac_opts")) {
2207: if (++i < arg.length)
2208: options.setJavacOpts(arg[i]);
2209: else
2210: success = false;
2211: } else if (arg[i].equals("-k") || arg[i].equals("-keep"))
2212: options.setKeepJavaFiles(true);
2213: else if (arg[i].equals("-j") || arg[i].equals("-java")) {
2214: options.setKeepJavaFiles(true);
2215: options.setRunJavac(false);
2216: } else if (arg[i].equals("-o")
2217: || arg[i].equals("-optimize"))
2218: options.setOptimize(true);
2219: else if (arg[i].equals("-version")) {
2220: System.err.println("jaxxc version " + getVersion()
2221: + " by Ethan Nicholas");
2222: System.err.println("http://www.jaxxframework.org/");
2223: System.exit(0);
2224: } else if (arg[i].equals("-internalDumpVersion")) { // used by ant to extract the version info
2225: System.out.println("jaxx.version=" + getVersion());
2226: return;
2227: } else {
2228: success = false;
2229: }
2230: }
2231:
2232: success &= (errorCount == 0 && files.size() > 0);
2233:
2234: if (success)
2235: success = compile(new File("."), (String[]) files
2236: .toArray(new String[files.size()]), options);
2237: else {
2238: showUsage();
2239: System.exit(1);
2240: }
2241:
2242: System.exit(success ? 0 : 1);
2243: }
2244: }
|