[Buix-commits] r865 - in lutinjaxx/trunk: . jaxx-core/src/main/java/jaxx/compiler jaxx-core/src/main/java/jaxx/runtime jaxx-core/src/main/java/jaxx/runtime/swing jaxx-core/src/main/java/jaxx/tags jaxx-core/src/main/resources/META-INF/services jaxx-core/src/test/java/jaxx/runtime jaxx-swing-action jaxx-swing-tab
Author: tchemit Date: 2008-10-02 12:50:25 +0000 (Thu, 02 Oct 2008) New Revision: 865 Added: lutinjaxx/trunk/jaxx-core/ lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/compiler/JAXXCompiler.java lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/DataBindingListener.java lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/DataBindingUpdateListener.java lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/JAXXContext.java lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/JAXXObject.java lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/Util.java lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/css/ lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/Item.java lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/JAXXButtonGroup.java lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/JAXXComboBox.java lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/JAXXList.java lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/JAXXTab.java lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/JAXXToggleButton.java lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/JAXXTree.java lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/Spacer.java lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/TabInfo.java lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/TabInfoPropertyChangeListener.java lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/Utils.java lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/tags/swing/ lutinjaxx/trunk/jaxx-core/src/main/resources/META-INF/services/jaxx.spi.Initializer lutinjaxx/trunk/jaxx-core/src/test/java/jaxx/runtime/UtilTest.java Removed: lutinjaxx/trunk/core/ lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/compiler/JAXXCompiler.java lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/JAXXObject.java lutinjaxx/trunk/jaxx-core/src/main/resources/META-INF/services/jaxx.spi.Initializer Modified: lutinjaxx/trunk/jaxx-swing-action/pom.xml lutinjaxx/trunk/jaxx-swing-tab/pom.xml lutinjaxx/trunk/pom.xml Log: refactor directory layout of modules (no more core or util but jaxx-core and jaxx-util,...) pass to version 0.5-SNAPSHOT Copied: lutinjaxx/trunk/jaxx-core (from rev 857, lutinjaxx/trunk/core) Deleted: lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/compiler/JAXXCompiler.java =================================================================== --- lutinjaxx/trunk/core/src/main/java/jaxx/compiler/JAXXCompiler.java 2008-09-24 19:58:26 UTC (rev 857) +++ lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/compiler/JAXXCompiler.java 2008-10-02 12:50:25 UTC (rev 865) @@ -1,2018 +0,0 @@ -/* - * Copyright 2006 Ethan Nicholas. All rights reserved. - * Use is subject to license terms. - */ -package jaxx.compiler; - -import jaxx.CompilerException; -import jaxx.UnsupportedAttributeException; -import jaxx.UnsupportedTagException; -import jaxx.css.Rule; -import jaxx.css.Stylesheet; -import jaxx.parser.ParseException; -import jaxx.reflect.ClassDescriptor; -import jaxx.reflect.ClassDescriptorLoader; -import jaxx.reflect.FieldDescriptor; -import jaxx.reflect.MethodDescriptor; -import jaxx.runtime.ComponentDescriptor; -import jaxx.runtime.JAXXObject; -import jaxx.runtime.JAXXObjectDescriptor; -import jaxx.runtime.swing.Application; -import jaxx.spi.Initializer; -import jaxx.tags.DefaultObjectHandler; -import jaxx.tags.TagHandler; -import jaxx.tags.TagManager; -import jaxx.types.TypeManager; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.xml.sax.Attributes; -import org.xml.sax.InputSource; -import org.xml.sax.Locator; -import org.xml.sax.SAXException; -import org.xml.sax.SAXParseException; -import org.xml.sax.helpers.AttributesImpl; -import org.xml.sax.helpers.XMLFilterImpl; - -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; -import javax.xml.transform.ErrorListener; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerConfigurationException; -import javax.xml.transform.TransformerException; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.dom.DOMResult; -import javax.xml.transform.sax.SAXSource; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.ObjectOutputStream; -import java.io.PrintWriter; -import java.io.UnsupportedEncodingException; -import java.lang.reflect.Modifier; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.net.URLDecoder; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.ServiceLoader; -import java.util.Set; -import java.util.Stack; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.zip.GZIPOutputStream; - -/** Compiles JAXX files into Java classes. */ -public class JAXXCompiler { - /** log */ - protected static final Log log = LogFactory.getLog(JAXXCompiler.class); - - /** - * True to throw exceptions when we encounter unresolvable classes, false to ignore. - * This is currently set to false until JAXX has full support for inner classes - * (including enumerations), because currently they don't always resolve (but will - * generally compile without error anyway). - */ - public static final boolean STRICT_CHECKS = false; - - public static final String JAXX_NAMESPACE = "http://www.jaxxframework.org/"; - public static final String JAXX_INTERNAL_NAMESPACE = "http://www.jaxxframework.org/internal"; - - /** Maximum length of an inlinable creation method. */ - private static final int INLINE_THRESHOLD = 300; - - /** Contains import declarations (of the form "javax.swing.") which are always imported in all compiler instances. */ - private static List<String> staticImports = new ArrayList<String>(); - - static { - //TODO humm, we should be able to import only what is needed - staticImports.add("java.awt.*"); - staticImports.add("java.awt.event.*"); - staticImports.add("java.beans.*"); - staticImports.add("java.io.*"); - staticImports.add("java.lang.*"); - staticImports.add("java.util.*"); - staticImports.add("javax.swing.*"); - staticImports.add("javax.swing.border.*"); - staticImports.add("javax.swing.event.*"); - staticImports.add("jaxx.runtime.swing.JAXXButtonGroup"); - staticImports.add("jaxx.runtime.swing.HBox"); - staticImports.add("jaxx.runtime.swing.VBox"); - staticImports.add("jaxx.runtime.swing.Table"); - staticImports.add("static org.codelutin.i18n.I18n._"); - staticImports.add("static org.codelutin.jaxx.util.UIHelper.createImageIcon"); - } - - private static DefaultObjectHandler firstPassClassTagHandler = new DefaultObjectHandler(ClassDescriptorLoader.getClassDescriptor(Object.class)); - - /** - * A list of Runnables which will be run after the first compilation pass. This is primarily used - * to trigger the creation of CompiledObjects, which cannot be created during the first pass and must be - * created in document order. - */ - private List<Runnable> initializers = new ArrayList<Runnable>(); - - /** Files being compiled during the compilation session, may be modified as compilation progresses and additional dependencies are found. */ - private static List<File> jaxxFiles = new ArrayList<File>(); - - /** Class names corresponding to the files in the jaxxFiles list. */ - private static List<String> jaxxFileClassNames = new ArrayList<String>(); - - /** Maps the names of classes being compiled to the compiler instance handling the compilation. */ - private static Map<String, JAXXCompiler> compilers = new HashMap<String, JAXXCompiler>(); - - /** Maps the names of classes being compiled to their symbol tables (created after the first compiler pass). */ - private static Map<File, SymbolTable> symbolTables = new HashMap<File, SymbolTable>(); - - private CompilerOptions options; - - /** Used for error reporting purposes, so we can report the right line number. */ - private Stack<Element> tagsBeingCompiled = new Stack<Element>(); - - /** Used for error reporting purposes, so we can report the right source file. */ - private Stack<File> sourceFiles = new Stack<File>(); - - /** Maps object ID strings to the objects themselves. These are created during the second compilation pass. */ - private Map<String, CompiledObject> objects = new LinkedHashMap<String, CompiledObject>(); - - /** Maps objects to their ID strings. These are created during the second compilation pass. */ - private Map<CompiledObject, String> ids = new LinkedHashMap<CompiledObject, String>(); - - private static int errorCount; - private static int warningCount; - - private boolean failed; - - /** Object corresponding to the root tag in the document. */ - private CompiledObject root; - - /** Contains strings of the form "javax.swing." */ - private Set<String> importedPackages = new HashSet<String>(); - - /** Contains strings of the form "javax.swing.Timer" */ - private Set<String> importedClasses = new HashSet<String>(); - - /** Keeps track of open components (components still having children added). */ - private Stack<CompiledObject> openComponents = new Stack<CompiledObject>(); - - /** Sequence number used to create automatic variable names. */ - private int autogenID = 0; - - private List<DataBinding> dataBindings = new ArrayList<DataBinding>(); - - private JavaFile javaFile = new JavaFile(); - - // true if a main() method has been declared in a script - boolean mainDeclared; - - private SymbolTable symbolTable = new SymbolTable(); - - // TODO: replace these public StringBuffers with something a little less stupid - - /** Extra code to be added to the instance initializer. */ - public StringBuffer initializer = new StringBuffer(); - - /** Extra code to be added at the end of the instance initializer. */ - public StringBuffer lateInitializer = new StringBuffer(); - - /** Extra code to be added to the class body. */ - public StringBuffer bodyCode = new StringBuffer(); - - /** Code to initialize data bindings. */ - public StringBuffer initDataBindings = new StringBuffer(); - - /** Body of the applyDataBinding method. */ - public StringBuffer applyDataBinding = new StringBuffer(); - - /** Body of the removeDataBinding method. */ - public StringBuffer removeDataBinding = new StringBuffer(); - - /** Body of the processDataBinding method. */ - public StringBuffer processDataBinding = new StringBuffer(); - - /** Base directory used for path resolution (normally the directory in which the .jaxx file resides). */ - private File baseDir; - - /** .jaxx file being compiled. */ - private File src; - - /** Generated .java file. */ - private File dest; - - /** Parsed XML of src file. */ - private Document document; - - /** Name of class being compiled. */ - private String outputClassName; - - private ScriptManager scriptManager = new ScriptManager(this); - - /** Combination of all stylesheets registered using {@link #registerStylesheet}. */ - private Stylesheet stylesheet; - - /** Contains all attributes defined inline on class tags. */ - private List<Rule> inlineStyles = new ArrayList<Rule>(); - - /** - * Maps objects (expressed in Java code) to event listener classes (e.g. MouseListener) to Lists of EventHandlers. The final list - * contains all event handlers of a particular type attached to a particular object (again, as represented by a Java expression). - */ - private Map<String, Map<ClassDescriptor, List<EventHandler>>> eventHandlers = new HashMap<String, Map<ClassDescriptor, List<EventHandler>>>(); - - private Map<Object, String> uniqueIds = new HashMap<Object, String>(); - - private Map<EventHandler, String> eventHandlerMethodNames = new HashMap<EventHandler, String>(); - - /** ClassLoader which searches the user-specified class path in addition to the normal class path */ - private ClassLoader classLoader; - - private static final int PASS_1 = 0; - private static final int PASS_2 = 1; - private static int currentPass; - - static { - try { - // fixme beware, this is a very dangerous thing to use a static block - //loadLibraries(); - // fixeme for the moment the compiler is init in maven plugin, not here - } - catch (Exception e) { - throw new RuntimeException(e); - } - } - - public static void init() { - // forces static initializer to run if it hasn't yet - } - - public static void loadLibraries(boolean verbose) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException { - ClassLoader classloader = Thread.currentThread().getContextClassLoader(); - if (verbose) { - log.info("with cl " + classloader); - } - ServiceLoader<Initializer> loader = ServiceLoader.load(Initializer.class, classloader); - for (Initializer initializer : loader) { - if (verbose) { - log.info("load initializer " + initializer); - } - initializer.initialize(); - } - } - - private JAXXCompiler(ClassLoader classLoader) { - this.options = new CompilerOptions(); - this.classLoader = classLoader; - addImport("java.lang.*"); - } - - - /** - * Creates a new JAXXCompiler. - * - * @param baseDir classpath location - * @param options options to pass to javac - * @param src location of file to compile - * @param outputClassName the out file name - */ - protected JAXXCompiler(File baseDir, File src, String outputClassName, CompilerOptions options) { - this.baseDir = baseDir; - this.src = src; - sourceFiles.push(src); - this.outputClassName = outputClassName; - this.options = options; - addImport(outputClassName.substring(0, outputClassName.lastIndexOf(".") + 1) + "*"); - for (Object staticImport : staticImports) - addImport((String) staticImport); - } - - - /** - * Creates a dummy JAXXCompiler for use in unit testing. - * - * @return the compiler - */ - public static JAXXCompiler createDummyCompiler() { - return createDummyCompiler(JAXXCompiler.class.getClassLoader()); - } - - - /** - * Creates a dummy JAXXCompiler for use in unit testing. - * - * @param classLoader class loader to use - * @return the compiler - */ - public static JAXXCompiler createDummyCompiler(ClassLoader classLoader) { - return new JAXXCompiler(classLoader); - } - - - public CompilerOptions getOptions() { - return options; - } - - - public JavaFile getJavaFile() { - return javaFile; - } - - - private void compileFirstPass() throws IOException { - try { - InputStream in = new FileInputStream(src); - document = parseDocument(in); - in.close(); - compileFirstPass(document.getDocumentElement()); - } - catch (SAXParseException e) { - reportError(e.getLineNumber(), "Invalid XML: " + e.getMessage()); - } - catch (SAXException e) { - reportError(null, "Error parsing XML document: " + e); - } - } - - - private void runInitializers() { - for (Runnable runnable : initializers) { - if (log.isDebugEnabled()) { - log.debug(runnable); - } - runnable.run(); - } - initializers.clear(); - } - - - /** - * Registers a <code>Runnable</code> which will be executed after the first - * compilation pass is complete. - * - * @param r runnable to register - */ - public void registerInitializer(Runnable r) { - initializers.add(r); - } - - - private void compileSecondPass() throws IOException { - if (!tagsBeingCompiled.isEmpty()) { - throw new RuntimeException("Internal error: starting pass two, but tagsBeingCompiled is not empty: " + tagsBeingCompiled); - } - compileSecondPass(document.getDocumentElement()); - } - - - private void applyStylesheets() { - for (Object o : new ArrayList<CompiledObject>(objects.values())) { - CompiledObject object = (CompiledObject) o; - TagManager.getTagHandler(object.getObjectClass()).applyStylesheets(object, this); - } - } - - - private void generateCode() throws IOException { - if (options.getTargetDirectory() != null) { - dest = new File(options.getTargetDirectory(), outputClassName.replace('.', File.separatorChar) + ".java"); - } else { - dest = new File(baseDir, outputClassName.substring(outputClassName.lastIndexOf(".") + 1) + ".java"); - } - PrintWriter out = new PrintWriter(new FileWriter(dest)); - createJavaSource(out); - out.close(); - } - - private void createJavaSource(PrintWriter out) throws IOException { - int dotPos = outputClassName.lastIndexOf("."); - String packageName = dotPos != -1 ? outputClassName.substring(0, dotPos) : null; - String simpleClassName = outputClassName.substring(dotPos + 1); - outputClass(packageName, simpleClassName, out); - } - - - public String getOutputClassName() { - return outputClassName; - } - - public static SAXParser getSAXParser() { - try { - SAXParserFactory factory = SAXParserFactory.newInstance(); - factory.setNamespaceAware(true); - SAXParser parser; - parser = factory.newSAXParser(); - return parser; - } - catch (SAXException e) { - throw new RuntimeException(e); - } - catch (ParserConfigurationException e) { - throw new RuntimeException(e); - } - } - - public static Document parseDocument(InputStream in) throws IOException, SAXException { - try { - TransformerFactory factory = TransformerFactory.newInstance(); - Transformer transformer = factory.newTransformer(); - transformer.setErrorListener(new ErrorListener() { - public void warning(TransformerException ex) throws TransformerException { - throw ex; - } - - public void error(TransformerException ex) throws TransformerException { - throw ex; - } - - public void fatalError(TransformerException ex) throws TransformerException { - throw ex; - } - }); - - DOMResult result = new DOMResult(); - transformer.transform(new SAXSource(new XMLFilterImpl(getSAXParser().getXMLReader()) { - Locator locator; - - @Override - public void setDocumentLocator(Locator locator) { - this.locator = locator; - } - - @Override - public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { - AttributesImpl resultAtts = new AttributesImpl(atts); - resultAtts.addAttribute(JAXX_INTERNAL_NAMESPACE, "line", "internal:line", "CDATA", String.valueOf(locator.getLineNumber())); - getContentHandler().startElement(uri, localName, qName, resultAtts); - } - }, new InputSource(in)), result); - return (Document) result.getNode(); - } - catch (TransformerConfigurationException e) { - throw new RuntimeException(e); - } - catch (TransformerException e) { - Throwable ex = e; - while (ex.getCause() != null) - ex = ex.getCause(); - if (ex instanceof IOException) - throw (IOException) ex; - if (ex instanceof SAXException) - throw (SAXException) ex; - if (ex instanceof RuntimeException) - throw (RuntimeException) ex; - throw new RuntimeException(ex); - } - } - - - public File getBaseDir() { - return baseDir; - } - - - public Set<String> getImportedClasses() { - return importedClasses; - } - - - public Set<String> getImportedPackages() { - return importedPackages; - } - - - private boolean inlineCreation(CompiledObject object) { - return object.getId().startsWith("$") && object.getInitializationCode(this).length() < INLINE_THRESHOLD; - } - - - public void checkOverride(CompiledObject object) throws CompilerException { - if (object.getId().startsWith("$")) { - return; - } - ClassDescriptor ancestor = root.getObjectClass(); - if (ancestor == object.getObjectClass()) { - return; - } - while (ancestor != null) { - try { - FieldDescriptor f = ancestor.getDeclaredFieldDescriptor(object.getId()); - if (!f.getType().isAssignableFrom(object.getObjectClass())) { - reportError("attempting to redefine superclass member '" + object.getId() + "' as incompatible type (was " + f.getType() + ", redefined as " + object.getObjectClass() + ")"); - } - object.setOverride(true); - break; - } - catch (NoSuchFieldException e) { - ancestor = ancestor.getSuperclass(); - } - } - } - - - private Iterator<CompiledObject> getObjectCreationOrder() { - return objects.values().iterator(); - } - - - protected JavaMethod createConstructor(String className) throws CompilerException { - StringBuffer code = new StringBuffer(); - String constructorParams = root.getConstructorParams(); - if (constructorParams != null) { - code.append(" super(").append(constructorParams).append(");"); - code.append(getLineSeparator()); - } - code.append("$initialize();"); - code.append(getLineSeparator()); - return new JavaMethod(Modifier.PUBLIC, null, className, null, null, code.toString()); - } - - - protected JavaMethod createInitializer(String className) throws CompilerException { - StringBuffer code = new StringBuffer(); - code.append("$objectMap.put(").append(TypeManager.getJavaCode(root.getId())).append(", this);"); - code.append(getLineSeparator()); - - Iterator<CompiledObject> i = getObjectCreationOrder(); - boolean lastWasMethodCall = false; - while (i.hasNext()) { - CompiledObject object = i.next(); - if (object != root && !object.isOverride()) { - if (inlineCreation(object)) { - if (lastWasMethodCall) { - lastWasMethodCall = false; - code.append(getLineSeparator()); - } - code.append(getCreationCode(object)); - code.append(getLineSeparator()); - } else { - code.append(object.getCreationMethodName()).append("();"); - code.append(getLineSeparator()); - lastWasMethodCall = true; - } - } - } - String rootCode = root.getInitializationCode(this); - if (rootCode != null && rootCode.length() > 0) { - code.append(rootCode); - code.append(getLineSeparator()); - } - code.append(getLineSeparator()); - if (initializer.length() > 0) { - code.append(initializer); - code.append(getLineSeparator()); - } - code.append("$completeSetup();"); - code.append(getLineSeparator()); - return new JavaMethod(Modifier.PRIVATE, "void", "$initialize", null, null, code.toString()); - } - - - protected JavaMethod createCompleteSetupMethod() { - StringBuffer code = new StringBuffer(); - code.append("allComponentsCreated = true;"); - code.append(getLineSeparator()); - for (CompiledObject object : objects.values()) { - if (object.getId().startsWith("$")) { - code.append(object.getAdditionCode()); - } else { - code.append(object.getAdditionMethodName()).append("();").append(getLineSeparator()); - String additionCode = object.getAdditionCode(); - if (additionCode.length() > 0) { - additionCode = "if (allComponentsCreated) {" + getLineSeparator() + additionCode + "}"; - } - javaFile.addMethod(new JavaMethod(Modifier.PROTECTED, "void", object.getAdditionMethodName(), null, null, additionCode)); - } - code.append(getLineSeparator()); - } - - code.append(initDataBindings); - - if (lateInitializer.length() > 0) { - code.append(lateInitializer); - code.append(getLineSeparator()); - } - return new JavaMethod(Modifier.PRIVATE, "void", "$completeSetup", null, null, code.toString()); - } - - - protected JavaMethod createProcessDataBindingMethod() { - StringBuffer code = new StringBuffer(); - boolean superclassIsJAXXObject = ClassDescriptorLoader.getClassDescriptor(JAXXObject.class).isAssignableFrom(root.getObjectClass()); - // the force parameter forces the update to happen even if it is already in activeBindings. This - // is used on superclass invocations b/c by the time the call gets to the superclass, it is already - // marked active and would otherwise be skipped - if (processDataBinding.length() > 0) { - code.append(" if (!$force && $activeBindings.contains($dest)) return;"); - code.append(getLineSeparator()); - code.append(" $activeBindings.add($dest);"); - code.append(getLineSeparator()); - code.append(" try {"); - code.append(getLineSeparator()); - if (processDataBinding.length() > 0) { - code.append(processDataBinding); - code.append(getLineSeparator()); - } - if (superclassIsJAXXObject) { - code.append(" else"); - code.append(getLineSeparator()); - code.append(" super.processDataBinding($dest, true);"); - code.append(getLineSeparator()); - } - code.append(" }"); - code.append(getLineSeparator()); - code.append(" finally {"); - code.append(getLineSeparator()); - code.append(" $activeBindings.remove($dest);"); - code.append(getLineSeparator()); - code.append(" }"); - code.append(getLineSeparator()); - } else if (superclassIsJAXXObject) { - code.append(" super.processDataBinding($dest, true);"); - code.append(getLineSeparator()); - } - return new JavaMethod(Modifier.PUBLIC, "void", "processDataBinding", - new JavaArgument[]{new JavaArgument("String", "$dest"), new JavaArgument("boolean", "$force")}, - null, code.toString()); - } - - - protected void createJavaFile(String packageName, String className) throws CompilerException { - String fullClassName = packageName != null ? packageName + "." + className : className; - if (root == null) { - throw new CompilerException("root tag must be a class tag"); - } - ClassDescriptor superclass = root.getObjectClass(); - boolean superclassIsJAXXObject = ClassDescriptorLoader.getClassDescriptor(JAXXObject.class).isAssignableFrom(superclass); - javaFile.setModifiers(Modifier.PUBLIC); - javaFile.setClassName(fullClassName); - javaFile.setSuperClass(getCanonicalName(superclass)); - javaFile.setInterfaces(new String[]{getCanonicalName(JAXXObject.class)}); - - for (CompiledObject object : objects.values()) { - if (!object.isOverride() && !(object instanceof ScriptInitializer)) { - int access = object.getId().startsWith("$") ? Modifier.PRIVATE : Modifier.PROTECTED; - if (object == root) { - javaFile.addField(new JavaField(access, fullClassName, object.getId(), "this")); - } else { - javaFile.addField(new JavaField(access, getCanonicalName(object.getObjectClass()), object.getId())); - } - } - } - - if (!superclassIsJAXXObject) { - javaFile.addField(new JavaField(Modifier.PROTECTED, "java.util.List<Object>", "$activeBindings", "new ArrayList<Object>()")); - javaFile.addField(new JavaField(Modifier.PROTECTED, "java.util.Map<String,Object>", "$bindingSources", "new HashMap<String,Object>()")); - } - - if (stylesheet != null) { - javaFile.addField(new JavaField(0, "java.util.Map", "$previousValues", "new java.util.HashMap()")); - } - - javaFile.addMethod(createConstructor(className)); - javaFile.addMethod(createInitializer(className)); - - for (DataBinding dataBinding : dataBindings) { - if (dataBinding.compile(true)) { - initDataBindings.append("applyDataBinding(").append(TypeManager.getJavaCode(dataBinding.getId())).append(");").append(JAXXCompiler.getLineSeparator()); - } - } - - javaFile.addBodyCode(bodyCode.toString()); - - for (CompiledObject object : objects.values()) { - if (!inlineCreation(object) && object != root) { - javaFile.addMethod(new JavaMethod(Modifier.PROTECTED, "void", object.getCreationMethodName(), null, null, getCreationCode(object))); - } - } - - javaFile.addField(new JavaField(Modifier.PRIVATE, "boolean", "allComponentsCreated")); - - javaFile.addMethod(createCompleteSetupMethod()); - - javaFile.addMethod(new JavaMethod(Modifier.PUBLIC, "void", "applyDataBinding", new JavaArgument[]{new JavaArgument("String", "$binding")}, - null, applyDataBinding.toString() + getLineSeparator() + " processDataBinding($binding);")); - - javaFile.addMethod(new JavaMethod(Modifier.PUBLIC, "void", "removeDataBinding", new JavaArgument[]{new JavaArgument("String", "$binding")}, - null, removeDataBinding.toString())); - - javaFile.addMethod(new JavaMethod(Modifier.PUBLIC, "void", "processDataBinding", new JavaArgument[]{new JavaArgument("String", "dest")}, - null, "processDataBinding(dest, false);")); - - javaFile.addMethod(createProcessDataBindingMethod()); - - if (!superclassIsJAXXObject) { - javaFile.addField(createObjectMap()); - javaFile.addMethod(createGetObjectByIdMethod()); - } - - javaFile.addField(createJAXXObjectDescriptorField()); - javaFile.addMethod(createGetJAXXObjectDescriptorMethod()); - - ClassDescriptor currentClass = root.getObjectClass(); - MethodDescriptor firePropertyChange = null; - while (firePropertyChange == null && currentClass != null) { - try { - firePropertyChange = currentClass.getDeclaredMethodDescriptor("firePropertyChange", new ClassDescriptor[]{ - ClassDescriptorLoader.getClassDescriptor(String.class), - ClassDescriptorLoader.getClassDescriptor(Object.class), - ClassDescriptorLoader.getClassDescriptor(Object.class) - }); - - } - catch (NoSuchMethodException e) { - currentClass = currentClass.getSuperclass(); - } - } - - int modifiers = firePropertyChange != null ? firePropertyChange.getModifiers() : 0; - if (Modifier.isPublic(modifiers)) { - // we have all the support we need - } - if (Modifier.isProtected(modifiers)) { - // there is property change support but the firePropertyChange method is protected - javaFile.addMethod(new JavaMethod(Modifier.PUBLIC, "void", "firePropertyChange", new JavaArgument[]{ - new JavaArgument("java.lang.String", "propertyName"), new JavaArgument("java.lang.Object", "oldValue"), new JavaArgument("java.lang.Object", "newValue")}, - null, "super.firePropertyChange(propertyName, oldValue, newValue);")); - } else { - // either no support at all or firePropertyChange isn't accessible - addPropertyChangeSupport(javaFile); - } - - addEventHandlers(javaFile); - - if (ClassDescriptorLoader.getClassDescriptor(Application.class).isAssignableFrom(root.getObjectClass()) && !mainDeclared) { - // TODO: check for existing main method first - javaFile.addMethod(new JavaMethod(Modifier.PUBLIC | Modifier.STATIC, "void", "main", - new JavaArgument[]{new JavaArgument("String[]", "arg")}, null, - "SwingUtilities.invokeLater(new Runnable() { public void run() { new " + className + "().setVisible(true); } });")); - } - } - - - protected void outputClass(String packageName, String className, PrintWriter out) throws CompilerException { - createJavaFile(packageName, className); - out.println(javaFile.toString()); - } - - - public void addImport(String text) { - if (text.endsWith("*")) { - importedPackages.add(text.substring(0, text.length() - 1)); - } else { - importedClasses.add(text); - } - - if (!text.equals("*")) { - getJavaFile().addImport(text); - } - } - - - private JavaField createObjectMap() { - return new JavaField(Modifier.PROTECTED, "Map<String,Object>", "$objectMap", "new HashMap<String,Object>()"); - } - - - protected JavaMethod createGetObjectByIdMethod() { - return new JavaMethod(Modifier.PUBLIC, "java.lang.Object", "getObjectById", - new JavaArgument[]{new JavaArgument("String", "id")}, null, - "return $objectMap.get(id);"); - } - - - public JAXXObjectDescriptor getJAXXObjectDescriptor() { - runInitializers(); - CompiledObject[] components = new ArrayList<CompiledObject>(objects.values()).toArray(new CompiledObject[objects.size()]); - assert initializers.isEmpty() : "there are pending initializers remaining"; - assert root != null : "root object has not been defined"; - assert Arrays.asList(components).contains(root) : "root object is not registered"; - ComponentDescriptor[] descriptors = new ComponentDescriptor[components.length]; - // as we print, sort the array so that component's parents are always before the components themselves - for (int i = 0; i < components.length; i++) { - CompiledObject parent = components[i].getParent(); - while (parent != null) { - boolean found = false; - for (int j = i + 1; j < components.length; j++) { // found parent after component, swap them - if (components[j] == parent) { - components[j] = components[i]; - components[i] = parent; - found = true; - break; - } - } - if (!found) { - break; - } - parent = components[i].getParent(); - } - int parentIndex = -1; - if (parent != null) { - for (int j = 0; j < i; j++) { - if (components[j] == parent) { - parentIndex = j; - break; - } - } - } - descriptors[i] = new ComponentDescriptor(components[i].getId(), components[i] == root ? outputClassName : components[i].getObjectClass().getName(), - components[i].getStyleClass(), parentIndex != -1 ? descriptors[parentIndex] : null); - } - - Stylesheet stylesheet = getStylesheet(); - if (stylesheet == null) - stylesheet = new Stylesheet(); - - return new JAXXObjectDescriptor(descriptors, stylesheet); - } - - - protected JavaField createJAXXObjectDescriptorField() { - try { - JAXXObjectDescriptor descriptor = getJAXXObjectDescriptor(); - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - ObjectOutputStream out = new ObjectOutputStream(new GZIPOutputStream(buffer)); - out.writeObject(descriptor); - out.close(); - // the use of the weird deprecated constructor is deliberate -- we need to store the data as a String - // in the compiled class file, since byte array initialization is horribly inefficient compared to - // String initialization. So we store the bytes in the String, and we quite explicitly want a 1:1 - // mapping between bytes and chars, with the high byte of the char set to zero. We can then safely - // reconstitute the original byte[] at a later date. This is unquestionably an abuse of the String - // type, but if we could efficiently store a byte[] we wouldn't have to do this. - String data = new String(buffer.toByteArray(), 0); - - int sizeLimit = 65000; // constant strings are limited to 64K, and I'm not brave enough to push right up to the limit - if (data.length() < sizeLimit) { - return new JavaField(Modifier.PRIVATE | Modifier.STATIC, "java.lang.String", "$jaxxObjectDescriptor", TypeManager.getJavaCode(data)); - } else { - StringBuffer initializer = new StringBuffer(); - for (int i = 0; i < data.length(); i += sizeLimit) { - String name = "$jaxxObjectDescriptor" + i; - javaFile.addField(new JavaField(Modifier.PRIVATE | Modifier.STATIC, "java.lang.String", name, - TypeManager.getJavaCode(data.substring(i, Math.min(i + sizeLimit, data.length()))))); - if (initializer.length() > 0) { - initializer.append(" + "); - } - initializer.append("String.valueOf(").append(name).append(")"); - } - return new JavaField(Modifier.PRIVATE | Modifier.STATIC, "java.lang.String", "$jaxxObjectDescriptor", initializer.toString()); - } - } - catch (IOException e) { - throw new RuntimeException("Internal error: can't-happen error", e); - } - } - - - protected JavaMethod createGetJAXXObjectDescriptorMethod() { - return new JavaMethod(Modifier.PUBLIC | Modifier.STATIC, "jaxx.runtime.JAXXObjectDescriptor", "$getJAXXObjectDescriptor", - null, null, "return jaxx.runtime.Util.decodeCompressedJAXXObjectDescriptor($jaxxObjectDescriptor);"); - } - - - public String getEventHandlerMethodName(EventHandler handler) { - String result = eventHandlerMethodNames.get(handler); - if (result == null) { - result = "$ev" + eventHandlerMethodNames.size(); - eventHandlerMethodNames.put(handler, result); - } - return result; - } - - protected void addEventHandlers(JavaFile javaFile) { - for (Map.Entry<String, Map<ClassDescriptor, List<EventHandler>>> e1 : eventHandlers.entrySet()) { - // outer loop is iterating over different objects (well, technically, different Java expressions) - for (Map.Entry<ClassDescriptor, List<EventHandler>> e2 : e1.getValue().entrySet()) { - // iterate over different types of listeners for this particular object (MouseListener, ComponentListener, etc.) - for (EventHandler handler : e2.getValue()) { - // iterate over individual event handlers of a single type - String methodName = getEventHandlerMethodName(handler); - MethodDescriptor listenerMethod = handler.getListenerMethod(); - if (listenerMethod.getParameterTypes().length != 1) { - throw new CompilerException("Expected event handler " + listenerMethod.getName() + " of class " + handler.getListenerClass() + " to have exactly one argument"); - } - javaFile.addMethod(new JavaMethod(Modifier.PUBLIC, "void", methodName, - new JavaArgument[]{new JavaArgument(getCanonicalName(listenerMethod.getParameterTypes()[0]), "event")}, null, - handler.getJavaCode())); - - } - } - } - } - - protected String getCreationCode(CompiledObject object) throws CompilerException { - if (object instanceof ScriptInitializer) { - return object.getInitializationCode(this); - } - StringBuffer result = new StringBuffer(); - result.append(object.getId()); - result.append(" = "); - String constructorParams = object.getConstructorParams(); - if (constructorParams != null) { - result.append(" new ").append(getCanonicalName(object.getObjectClass())).append("(").append(constructorParams).append(");"); - //result.append("(").append(getCanonicalName(object.getObjectClass())).append(") new ").append(getCanonicalName(object.getObjectClass())).append("(").append(constructorParams).append(");"); - } else { - result.append("new ").append(getCanonicalName(object.getObjectClass())).append("();"); - } - result.append(getLineSeparator()); - String initCode = object.getInitializationCode(this); - if (initCode != null && initCode.length() > 0) { - result.append(initCode); - } - result.append("$objectMap.put(").append(TypeManager.getJavaCode(object.getId())).append(", ").append(object.getId()).append(");"); - - return result.toString(); - } - - - protected void addPropertyChangeSupport(JavaFile javaFile) throws CompilerException { - javaFile.addField(new JavaField(0, "java.beans.PropertyChangeSupport", "$propertyChangeSupport")); - - javaFile.addMethod(new JavaMethod(0, "java.beans.PropertyChangeSupport", "$getPropertyChangeSupport", null, null, - "if ($propertyChangeSupport == null)\n" + - " $propertyChangeSupport = new PropertyChangeSupport(this);\n" + - "return $propertyChangeSupport;")); - - javaFile.addMethod(new JavaMethod(Modifier.PUBLIC, "void", "addPropertyChangeListener", new JavaArgument[]{ - new JavaArgument("java.beans.PropertyChangeListener", "listener")}, null, - "$getPropertyChangeSupport().addPropertyChangeListener(listener);")); - - javaFile.addMethod(new JavaMethod(Modifier.PUBLIC, "void", "addPropertyChangeListener", new JavaArgument[]{ - new JavaArgument("java.lang.String", "property"), new JavaArgument("java.beans.PropertyChangeListener", "listener")}, null, - "$getPropertyChangeSupport().addPropertyChangeListener(property, listener);")); - - javaFile.addMethod(new JavaMethod(Modifier.PUBLIC, "void", "removePropertyChangeListener", new JavaArgument[]{ - new JavaArgument("java.beans.PropertyChangeListener", "listener")}, null, - "$getPropertyChangeSupport().removePropertyChangeListener(listener);")); - - javaFile.addMethod(new JavaMethod(Modifier.PUBLIC, "void", "removePropertyChangeListener", new JavaArgument[]{ - new JavaArgument("java.lang.String", "property"), new JavaArgument("java.beans.PropertyChangeListener", "listener")}, null, - "$getPropertyChangeSupport().removePropertyChangeListener(property, listener);")); - - javaFile.addMethod(new JavaMethod(Modifier.PUBLIC, "void", "firePropertyChange", new JavaArgument[]{ - new JavaArgument("java.lang.String", "propertyName"), new JavaArgument("java.lang.Object", "oldValue"), new JavaArgument("java.lang.Object", "newValue")}, - null, "$getPropertyChangeSupport().firePropertyChange(propertyName, oldValue, newValue);")); - } - - - public void compileFirstPass(final Element tag) throws IOException { - tagsBeingCompiled.push(tag); - - String namespace = tag.getNamespaceURI(); - String fullClassName; - String localName = tag.getLocalName(); - boolean namespacePrefix = tag.getPrefix() != null; - // resolve class tags into fully-qualified class name - if (namespace != null && namespace.endsWith("*")) { - String packageName = namespace.substring(0, namespace.length() - 1); - if (localName.startsWith(packageName)) // class name is fully-qualified already - { - fullClassName = TagManager.resolveClassName(localName, this); - } else { // namespace not included in class name, probably need the namespace to resolve - fullClassName = TagManager.resolveClassName(packageName + localName, this); - if (fullClassName == null && !namespacePrefix) // it was just a default namespace, try again without using the namespace - { - fullClassName = TagManager.resolveClassName(localName, this); - } - } - } else { - fullClassName = TagManager.resolveClassName(localName, this); - } - - if (fullClassName != null) { // we are definitely dealing with a class tag - addDependencyClass(fullClassName); - namespace = fullClassName.substring(0, fullClassName.lastIndexOf(".") + 1) + "*"; - if (symbolTable.getSuperclassName() == null) { - symbolTable.setSuperclassName(fullClassName); - } - String id = tag.getAttribute("id"); - if (id.length() > 0) { - symbolTable.getClassTagIds().put(id, fullClassName); - } - } - // during the first pass, we can't create ClassDescriptors for JAXX files because they may not have been processed yet - // (and we can't wait until they have been processed because of circular dependencies). So we don't do any processing - // during the first pass which requires having a ClassDescriptor; here we determine whether we have a class tag or not - // (class tag namespaces end in "*") and use a generic handler if so. The real handler is used during the second pass. - TagHandler handler = (namespace != null && namespace.endsWith("*")) ? firstPassClassTagHandler : TagManager.getTagHandler(tag.getNamespaceURI(), localName, namespacePrefix, this); - if (handler != firstPassClassTagHandler && handler instanceof DefaultObjectHandler) { - fullClassName = ((DefaultObjectHandler) handler).getBeanClass().getName(); - //namespace = fullClassName.substring(0, fullClassName.lastIndexOf(".") + 1) + "*"; - handler = firstPassClassTagHandler; - } - if (handler == firstPassClassTagHandler) { - final String finalClassName = fullClassName; - registerInitializer(new Runnable() { // register an initializer which will create the CompiledObject after pass 1 - - public void run() { - DefaultObjectHandler handler = (DefaultObjectHandler) TagManager.getTagHandler(null, finalClassName, JAXXCompiler.this); - if (handler == null) { - throw new CompilerException("Internal error: missing TagHandler for '" + finalClassName + "'"); - } - handler.registerCompiledObject(tag, JAXXCompiler.this); - } - }); - } - if (handler != null) { - try { - handler.compileFirstPass(tag, this); - } - catch (CompilerException e) { - reportError(e); - } - } else { - reportError("Could not find a Java class corresponding to: <" + tag.getTagName() + ">"); - failed = true; - } - - Element finished = tagsBeingCompiled.pop(); - if (finished != tag) { - throw new RuntimeException("internal error: just finished compiling " + tag + ", but top of tagsBeingCompiled stack is " + finished); - } - } - - - public void compileSecondPass(Element tag) throws IOException { - tagsBeingCompiled.push(tag); - - TagHandler handler = TagManager.getTagHandler(tag.getNamespaceURI(), tag.getLocalName(), tag.getPrefix() != null, this); - if (handler != null) { - handler.compileSecondPass(tag, this); - } else { - reportError("Could not find a Java class corresponding to: <" + tag.getTagName() + ">"); - assert false : "can't-happen error: error should have been reported during the fast pass and caused an abort"; - failed = true; - } - - Element finished = tagsBeingCompiled.pop(); - if (finished != tag) { - throw new RuntimeException("internal error: just finished compiling " + tag + ", but top of tagsBeingCompiled stack is " + finished); - } - - } - - - // 1.5 adds getCanonicalName; unfortunately we can't depend on 1.5 features yet - public static String getCanonicalName(Class clazz) { - if (clazz.isArray()) { - String canonicalName = getCanonicalName(clazz.getComponentType()); - if (canonicalName != null) { - return canonicalName + "[]"; - } - return null; - } - return clazz.getName().replace('$', '.'); - } - - - public static String getCanonicalName(ClassDescriptor clazz) { - if (clazz.isArray()) { - String canonicalName = getCanonicalName(clazz.getComponentType()); - if (canonicalName != null) { - return canonicalName + "[]"; - } - return null; - } - return clazz.getName().replace('$', '.'); - } - - - public static String capitalize(String s) { - if (s.length() == 0) { - return s; - } - return Character.toUpperCase(s.charAt(0)) + s.substring(1); - } - - - public String[] parseParameterList(String parameters) throws CompilerException { - List<String> result = new ArrayList<String>(); - StringBuffer current = new StringBuffer(); - int state = 0; // normal - for (int i = 0; i < parameters.length(); i++) { - char c = parameters.charAt(i); - switch (state) { - case 0: // normal - switch (c) { - case '"': - current.append(c); - state = 1; - break; // in quoted string - case '\\': - current.append(c); - state = 2; - break; // immediately after backslash - case ',': - if (current.length() > 0) { - result.add(current.toString()); - current.setLength(0); - break; - } else - reportError("error parsing parameter list: " + parameters); - default: - current.append(c); - } - break; - case 1: // in quoted string - switch (c) { - case '"': - current.append(c); - state = 0; - break; // normal - case '\\': - current.append(c); - state = 3; - break; // immediate after backslash in quoted string - default: - current.append(c); - } - break; - case 2: // immediately after backslash - current.append(c); - state = 0; // normal - break; - case 3: // immediately after backslash in quoted string - current.append(c); - state = 1; // in quoted string - break; - } - } - if (current.length() > 0) { - result.add(current.toString()); - } - return result.toArray(new String[result.size()]); - } - - - public void openComponent(CompiledObject component) throws CompilerException { - openComponent(component, null); - } - - - public void openComponent(CompiledObject component, String constraints) throws CompilerException { - CompiledObject parent = getOpenComponent(); - openInvisibleComponent(component); - if (parent != null && !component.isOverride()) { - parent.addChild(component, constraints, this); - } - } - - - public void openInvisibleComponent(CompiledObject component) { - if (!ids.containsKey(component)) { - registerCompiledObject(component); - } - openComponents.push(component); - } - - - public CompiledObject getOpenComponent() { - if (openComponents.isEmpty()) { - return null; - } - return openComponents.peek(); - } - - - public void closeComponent(CompiledObject component) { - if (openComponents.pop() != component) { - throw new IllegalArgumentException("can only close the topmost open object"); - } - } - - - public CompiledObject getRootObject() { - return root; - } - - - public void registerCompiledObject(CompiledObject object) { - assert symbolTables.values().contains(symbolTable) : "attempting to register CompiledObject before pass 1 is complete"; - if (root == null) { - root = object; - } - - String id = object.getId(); - if (ids.containsKey(object)) { - reportError("object '" + object + "' is already registered with id '" + ids.get(object) + "', cannot re-register as '" + id + "'"); - } - if (objects.containsKey(id) && !(objects.get(id) instanceof Element)) { - reportError("id '" + id + "' is already registered to component " + objects.get(id)); - } - objects.put(id, object); - ids.put(object, id); - } - - - public String getAutoId(ClassDescriptor objectClass) { - if (options.getOptimize()) { - return "$" + Integer.toString(autogenID++, 36); - } else { - String name = objectClass.getName(); - name = name.substring(name.lastIndexOf(".") + 1); - return "$" + name + autogenID++; - } - } - - - public String getUniqueId(Object object) { - String result = uniqueIds.get(object); - if (result == null) { - result = "$u" + uniqueIds.size(); - uniqueIds.put(object, result); - } - return result; - } - - - public SymbolTable getSymbolTable() { - return symbolTable; - } - - - public CompiledObject getCompiledObject(String id) { - runInitializers(); - assert symbolTables.values().contains(symbolTable) : "attempting to retrieve CompiledObject before pass 1 is complete"; - return objects.get(id); - } - - - private Matcher leftBraceMatcher = Pattern.compile("^(\\{)|[^\\\\](\\{)").matcher(""); - - private int getNextLeftBrace(String string, int pos) { - leftBraceMatcher.reset(string); - return leftBraceMatcher.find(pos) ? Math.max(leftBraceMatcher.start(1), leftBraceMatcher.start(2)) : -1; - } - - - private Matcher rightBraceMatcher = Pattern.compile("^(\\})|[^\\\\](\\})").matcher(""); - - private int getNextRightBrace(String string, int pos) { - leftBraceMatcher.reset(string); - rightBraceMatcher.reset(string); - int openCount = 1; - int rightPos; - while (openCount > 0) { - pos++; - int leftPos = leftBraceMatcher.find(pos) ? Math.max(leftBraceMatcher.start(1), leftBraceMatcher.start(2)) : -1; - rightPos = rightBraceMatcher.find(pos) ? Math.max(rightBraceMatcher.start(1), rightBraceMatcher.start(2)) : -1; - assert leftPos == -1 || leftPos >= pos; - assert rightPos == -1 || rightPos >= pos; - if (leftPos != -1 && leftPos < rightPos) { - pos = leftPos; - openCount++; - } else if (rightPos != -1) { - pos = rightPos; - openCount--; - } else - openCount = 0; - } - return pos; - } - - - /** - * Examine an attribute value for data binding expressions. Returns a 'cooked' expression which - * can be used to determine the resulting value. It is expected that this expression will be used - * as the source expression in a call to {@link #registerDataBinding}. - * If the attribute value does not invoke data binding, this method returns <code>null</code> - * - * @param stringValue the string value of the property from the XML - * @param type the type of the property, from the <code>JAXXPropertyDescriptor</code> - * @return a processed version of the expression - * @throws jaxx.CompilerException ? - */ - public String processDataBindings(String stringValue, ClassDescriptor type) throws CompilerException { - int pos = getNextLeftBrace(stringValue, 0); - if (pos != -1) { - StringBuffer expression = new StringBuffer(); - int lastPos = 0; - while (pos != -1 && pos < stringValue.length()) { - if (pos > lastPos) { - if (expression.length() > 0) { - expression.append(" + "); - } - expression.append('"'); - expression.append(JAXXCompiler.escapeJavaString(stringValue.substring(lastPos, pos))); - expression.append('"'); - } - - if (expression.length() > 0) { - expression.append(" + "); - } - expression.append('('); - int pos2 = getNextRightBrace(stringValue, pos + 1); - if (pos2 == -1) { - reportError("unmatched '{' in expression: " + stringValue); - return ""; - } - expression.append(stringValue.substring(pos + 1, pos2)); - expression.append(')'); - pos2++; - if (pos2 < stringValue.length()) { - pos = getNextLeftBrace(stringValue, pos2); - lastPos = pos2; - } else { - pos = stringValue.length(); - lastPos = pos; - } - } - if (lastPos < stringValue.length()) { - if (expression.length() > 0) { - expression.append(" + "); - } - expression.append('"'); - expression.append(JAXXCompiler.escapeJavaString(stringValue.substring(lastPos))); - expression.append('"'); - } - return type == ClassDescriptorLoader.getClassDescriptor(String.class) ? "String.valueOf(" + expression + ")" : expression.toString(); - } - return null; - } - - - public void registerDataBinding(String src, String dest, String assignment) { - try { - src = checkJavaCode(src); - dataBindings.add(new DataBinding(src, dest, assignment, this)); - } - catch (CompilerException e) { - reportError("While parsing data binding for '" + dest.substring(dest.lastIndexOf(".") + 1) + "': " + e.getMessage()); - } - } - - - public ScriptManager getScriptManager() { - return scriptManager; - } - - - /** - * Verifies that a snippet of Java code parses correctly. A warning is generated if the string has enclosing - * curly braces. - * - * @param javaCode the Java code snippet to test - * @return a "cooked" version of the string which has enclosing curly braces removed. - * @throws CompilerException if the code cannot be parsed - */ - public String checkJavaCode(String javaCode) { - javaCode = scriptManager.trimScript(javaCode); - scriptManager.checkParse(javaCode); - return javaCode; - } - - - public void registerEventHandler(EventHandler handler) { - String objectCode = handler.getObjectCode(); - Map<ClassDescriptor, List<EventHandler>> listeners = eventHandlers.get(objectCode); - if (listeners == null) { - listeners = new HashMap<ClassDescriptor, List<EventHandler>>(); - eventHandlers.put(objectCode, listeners); - } - ClassDescriptor listenerClass = handler.getListenerClass(); - List<EventHandler> handlerList = listeners.get(listenerClass); - if (handlerList == null) { - handlerList = new ArrayList<EventHandler>(); - listeners.put(listenerClass, handlerList); - } - handlerList.add(handler); - } - - - public FieldDescriptor[] getScriptFields() { - List<FieldDescriptor> scriptFields = symbolTable.getScriptFields(); - return scriptFields.toArray(new FieldDescriptor[scriptFields.size()]); - } - - - public void addScriptField(FieldDescriptor field) { - symbolTable.getScriptFields().add(field); - } - - - public MethodDescriptor[] getScriptMethods() { - List<MethodDescriptor> scriptMethods = symbolTable.getScriptMethods(); - return scriptMethods.toArray(new MethodDescriptor[scriptMethods.size()]); - } - - - public void addScriptMethod(MethodDescriptor method) { - if (method.getName().equals("main") && method.getParameterTypes().length == 1 && method.getParameterTypes()[0].getName().equals("[Ljava.lang.String;")) { - mainDeclared = true; - } - symbolTable.getScriptMethods().add(method); - } - - - public void registerScript(String script) throws CompilerException { - registerScript(script, null); - } - - - public void registerScript(String script, File sourceFile) throws CompilerException { - if (sourceFile != null) { - sourceFiles.push(sourceFile); - } - script = script.trim(); - if (!"".equals(script) && !script.endsWith("}") && !script.endsWith(";")) { - script += ";"; - } - scriptManager.registerScript(script); - - if (sourceFile != null) { - File pop = sourceFiles.pop(); - if (pop != sourceFile) { - throw new RuntimeException("leaving registerScript(), but " + sourceFile + " was not the top entry on the stack (found " + pop + " instead)"); - } - } - } - - - public String preprocessScript(String script) throws CompilerException { - return scriptManager.preprocessScript(script); - } - - - public void registerStylesheet(Stylesheet stylesheet) { - if (this.stylesheet == null) { - this.stylesheet = stylesheet; - } else { - this.stylesheet.add(stylesheet.getRules()); - } - } - - - public Stylesheet getStylesheet() { - Stylesheet merged = new Stylesheet(); - if (stylesheet != null) { - merged.add(stylesheet.getRules()); - } - merged.add(inlineStyles.toArray(new Rule[inlineStyles.size()])); - return merged; - } - - - public Stack<File> getSourceFiles() { - return sourceFiles; - } - - - public void addInlineStyle(CompiledObject object, String propertyName, boolean dataBinding) { - inlineStyles.add(Rule.inlineAttribute(object, propertyName, dataBinding)); - } - - - public void reportWarning(String warning) { - Element currentTag = null; - if (!tagsBeingCompiled.isEmpty()) { - currentTag = tagsBeingCompiled.peek(); - } - reportWarning(currentTag, warning, 0); - } - - - public void reportWarning(Element tag, String warning, int lineOffset) { - String lineNumber = null; - if (tag != null) { - String lineAttr = tag.getAttributeNS(JAXX_INTERNAL_NAMESPACE, "line"); - if (lineAttr.length() > 0) { - lineNumber = lineAttr; - } - } - File src = sourceFiles.peek(); - try { - src = src.getCanonicalFile(); - } - catch (IOException e) { - // ignore ? - } - - System.err.print(src); - if (lineNumber != null) { - System.err.print(":" + ((sourceFiles.size() == 1) ? Integer.parseInt(lineNumber) + lineOffset : lineOffset + 1)); - } - System.err.println(": Warning: " + warning); - warningCount++; - } - - - public void reportError(String error) { - Element currentTag = null; - if (!tagsBeingCompiled.isEmpty()) { - currentTag = tagsBeingCompiled.peek(); - } - reportError(currentTag, error); - } - - - public void reportError(CompilerException ex) { - reportError(null, ex); - } - - - public void reportError(String extraMessage, CompilerException ex) { - String message = ex.getMessage(); - if (ex.getClass() == UnsupportedAttributeException.class || ex.getClass() == UnsupportedTagException.class) { - message = ex.getClass().getName().substring(ex.getClass().getName().lastIndexOf(".") + 1) + ": " + message; - } - int lineOffset; - if (ex instanceof ParseException) { - lineOffset = Math.max(0, ((ParseException) ex).getLine() - 1); - } else { - lineOffset = 0; - } - Element currentTag = null; - if (!tagsBeingCompiled.isEmpty()) { - currentTag = tagsBeingCompiled.peek(); - } - reportError(currentTag, extraMessage != null ? extraMessage + message : message, lineOffset); - } - - - public void reportError(Element tag, String error) { - reportError(tag, error, 0); - } - - - public void reportError(Element tag, String error, int lineOffset) { - int lineNumber = 0; - if (tag != null) { - String lineAttr = tag.getAttributeNS(JAXX_INTERNAL_NAMESPACE, "line"); - if (lineAttr.length() > 0) { - lineNumber = Integer.parseInt(lineAttr); - } - } - lineNumber = Math.max(lineNumber, 1) + lineOffset; - reportError(lineNumber, error); - } - - - public void reportError(int lineNumber, String error) { - File src = sourceFiles.isEmpty() ? null : sourceFiles.peek(); - try { - if (src != null) { - src = src.getCanonicalFile(); - } - } - catch (IOException e) { - // ignore ? - } - - System.err.print(src != null ? src.getPath() : "<unknown source>"); - if (lineNumber > 0) { - System.err.print(":" + lineNumber); - } - System.err.println(": " + error); - errorCount++; - failed = true; - } - - - /** - * Escapes a string using standard Java escape sequences, generally in preparation to including it in a string literal - * in a compiled Java file. - * - * @param raw the raw string to be escape - * @return a string in which all 'dangerous' characters have been replaced by equivalent Java escape sequences - */ - public static String escapeJavaString(String raw) { - StringBuffer out = new StringBuffer(raw); - for (int i = 0; i < out.length(); i++) { - char c = out.charAt(i); - if (c == '\\' || c == '"') { - out.insert(i, '\\'); - i++; - } else if (c == '\n') { - out.replace(i, i + 1, "\\n"); - i++; - } else if (c == '\r') { - out.replace(i, i + 1, "\\r"); - i++; - } else if (c < 32 || c > 127) { - String value = Integer.toString((int) c, 16); - while (value.length() < 4) { - value = "0" + value; - } - out.replace(i, i + 1, "\\u" + value); - i += 5; - } - } - return out.toString(); - } - - - /** - * Returns the system line separator string. - * - * @return the string used to separate lines - */ - public static String getLineSeparator() { - return System.getProperty("line.separator", "\n"); - } - - - /** - * Returns a <code>ClassLoader</code> which searches the user-specified class path in addition - * to the normal system class path. - * - * @return <code>ClassLoader</code> to use while resolving class references - */ - public ClassLoader getClassLoader() { - if (classLoader == null) { - String classPath = options.getClassPath(); - if (classPath == null) { - classPath = "."; - } - String[] paths = classPath.split(File.pathSeparator); - URL[] urls = new URL[paths.length]; - for (int i = 0; i < paths.length; i++) { - try { - urls[i] = new File(paths[i]).toURI().toURL(); - } - catch (MalformedURLException e) { - throw new RuntimeException(e); - } - } - classLoader = new URLClassLoader(urls, getClass().getClassLoader()); - } - - return classLoader; - } - - - /** - * @param className the name of the class to use - * @return the compiler instance which is processing the specified JAXX class. Each class is compiled by a - * different compiler instance. - */ - public static JAXXCompiler getJAXXCompiler(String className) { - return compilers != null ? compilers.get(className) : null; - } - - - /** - * @param className the name of the class to use - * @return the symbol table for the specified JAXX class. Must be called during the second compiler pass. - * Returns <code>null</code> if no such symbol table could be found. - */ - public static SymbolTable getSymbolTable(String className) { - JAXXCompiler compiler = getJAXXCompiler(className); - if (compiler == null) { - return null; - } - return compiler.getSymbolTable(); - } - - public static File URLtoFile(URL url) { - return URLtoFile(url.toString()); - } - - public static File URLtoFile(String urlString) { - if (!urlString.startsWith("file:")) { - throw new IllegalArgumentException("url must start with 'file:'"); - } - urlString = urlString.substring("file:".length()); - if (urlString.startsWith("/") && System.getProperty("os.name").startsWith("Windows")) { - urlString = urlString.substring(1); - } - try { - return new File(URLDecoder.decode(urlString.replace('/', File.separatorChar), "utf-8")); - } - catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } - - public void addDependencyClass(String className) { - if (!jaxxFileClassNames.contains(className)) { - URL jaxxURL = getClassLoader().getResource(className.replace('.', '/') + ".jaxx"); - URL classURL = getClassLoader().getResource(className.replace('.', '/') + ".class"); - if (jaxxURL != null && classURL != null) { - try { - File jaxxFile = URLtoFile(jaxxURL); - File classFile = URLtoFile(classURL); - if (classFile.lastModified() > jaxxFile.lastModified()) { - return; // class file is newer, no need to recompile - } - } - catch (Exception e) { - // do nothing - } - } - - if (jaxxURL != null && jaxxURL.toString().startsWith("file:")) { - File jaxxFile = URLtoFile(jaxxURL); - try { - jaxxFile = jaxxFile.getCanonicalFile(); - } - catch (IOException ex) { - // ignore ? - } - assert jaxxFile.getName().equalsIgnoreCase(className.substring(className.lastIndexOf(".") + 1) + ".jaxx") : - "expecting file name to match " + className + ", but found " + jaxxFile.getName(); - if (jaxxFile.getName().equals(className.substring(className.lastIndexOf(".") + 1) + ".jaxx")) { // check case match - if (currentPass == PASS_2) { - throw new AssertionError("Internal error: adding dependency class " + className + " during second compilation pass"); - } - jaxxFileClassNames.add(className); - jaxxFiles.add(jaxxFile); - } - } - } - } - - - /** - * Compiled a set of files, expressed as paths relative to a base directory. The class names of the compiled files are derived - * from the relative path strings (e.g. "example/Foo.jaxx" compiles into a class named "example.Foo"). Returns <code>true</code> - * if compilation succeeds, <code>false</code> if it fails. Warning and error messages are sent to <code>System.err</code>. - * - * @param base the directory against which to resolve relative paths - * @param relativePaths a list of relative paths to .jaxx files being compiled - * @param options the compiler options to use - * @return <code>true</code> if compilation succeeds, <code>false</code> otherwise - */ - public static synchronized boolean compile(File base, String[] relativePaths, CompilerOptions options) { - File[] files = new File[relativePaths.length]; - String[] classNames = new String[relativePaths.length]; - for (int i = 0; i < files.length; i++) { - files[i] = new File(base, relativePaths[i]); - classNames[i] = relativePaths[i].substring(0, relativePaths[i].lastIndexOf(".")); - classNames[i] = classNames[i].replace(File.separatorChar, '.'); - classNames[i] = classNames[i].replace('/', '.'); - classNames[i] = classNames[i].replace('\\', '.'); - classNames[i] = classNames[i].replace(':', '.'); - } - return compile(files, classNames, options); - } - - - /** Resets all state in preparation for a new compilation session. */ - private static void reset() { - errorCount = 0; - warningCount = 0; - jaxxFiles.clear(); - jaxxFileClassNames.clear(); - symbolTables.clear(); - compilers.clear(); - } - - - /** - * Compiled a set of files, with the class names specified explicitly. The class compiled from files[i] will be named classNames[i]. - * Returns <code>true</code> if compilation succeeds, <code>false</code> if it fails. Warning and error messages are sent to - * <code>System.err</code>. - * - * @param files the .jaxx files to compile - * @param classNames the names of the classes being compiled - * @param options the compiler options to use - * @return <code>true</code> if compilation succeeds, <code>false</code> otherwise - */ - public static synchronized boolean compile(File[] files, String[] classNames, CompilerOptions options) { - reset(); // just to be safe... - jaxxFiles.addAll(Arrays.asList(files)); - jaxxFileClassNames.addAll(Arrays.asList(classNames)); - try { - boolean success = true; - - // pass 1 - currentPass = PASS_1; - boolean compiled; - do { - compiled = false; - assert jaxxFiles.size() == jaxxFileClassNames.size(); - java.util.Iterator<File> filesIterator = new ArrayList<File>(jaxxFiles).iterator(); // clone it so it can safely be modified while we're iterating - java.util.Iterator<String> classNamesIterator = new ArrayList<String>(jaxxFileClassNames).iterator(); - while (filesIterator.hasNext()) { - File file = filesIterator.next(); - String className = classNamesIterator.next(); - if (options.isVerbose()) { - log.info("compile first pass for " + className); - } - if (symbolTables.get(file) == null) { - compiled = true; - if (compilers.containsKey(className)) { - throw new CompilerException("Internal error: " + className + " is already being compiled, attempting to compile it again"); - } - - File destDir = options.getTargetDirectory(); - if (destDir != null) { - int dotPos = className.lastIndexOf("."); - if (dotPos != -1) { - destDir = new File(destDir, className.substring(0, dotPos).replace('.', File.separatorChar)); - } - destDir.mkdirs(); - } else { - //destDir = file.getParentFile(); - } - JAXXCompiler compiler = new JAXXCompiler(file.getParentFile(), file, className, options); - compilers.put(className, compiler); - compiler.compileFirstPass(); - assert !symbolTables.values().contains(compiler.getSymbolTable()) : "symbolTable is already registered"; - symbolTables.put(file, compiler.getSymbolTable()); - if (compiler.failed) { - success = false; - } - } - } - - } while (compiled); - - // pass 2 - currentPass = PASS_2; - if (success) { - assert jaxxFiles.size() == jaxxFileClassNames.size(); - List<File> jaxxFilesClone = new ArrayList<File>(jaxxFiles); - for (String className : jaxxFileClassNames) { - JAXXCompiler compiler = compilers.get(className); - if (compiler == null) - throw new CompilerException("Internal error: could not find compiler for " + className + " during second pass"); - if (options.isVerbose()) { - - log.info("runInitializers for " + className); - } - if (!compiler.failed) { - compiler.runInitializers(); - } - if (options.isVerbose()) { - - log.info("compile second pass for " + className); - } - compiler.compileSecondPass(); - if (options.isVerbose()) { - - log.info("done with result [" + !compiler.failed + "] for " + className); - } - if (!compiler.failed) { - - } else { - success = false; - } - } - if (!jaxxFilesClone.equals(jaxxFiles)) { - throw new AssertionError("Internal error: compilation set altered during pass 2 (was " + jaxxFilesClone + ", modified to " + jaxxFiles + ")"); - } - } - - // stylesheet application - if (success) { - assert jaxxFiles.size() == jaxxFileClassNames.size(); - for (String className : jaxxFileClassNames) { - JAXXCompiler compiler = compilers.get(className); - if (compiler == null) { - throw new CompilerException("Internal error: could not find compiler for " + className + " during stylesheet application"); - } - compiler.applyStylesheets(); - if (compiler.failed) { - success = false; - } - } - } - - // code generation - if (success) { - assert jaxxFiles.size() == jaxxFileClassNames.size(); - for (String className : jaxxFileClassNames) { - JAXXCompiler compiler = compilers.get(className); - if (compiler == null) { - throw new CompilerException("Internal error: could not find compiler for " + className + " during code generation"); - } - compiler.generateCode(); - if (compiler.failed) { - success = false; - } - } - } - - if (warningCount == 1) { - System.err.println("1 warning"); - } else if (warningCount > 0) { - System.err.println(warningCount + " warnings"); - } - if (errorCount == 1) { - System.err.println("1 error"); - } else if (errorCount > 0) { - System.err.println(errorCount + " errors"); - } - return success; - } - catch (CompilerException e) { - System.err.println(e.getMessage()); - e.printStackTrace(); - return false; - } - catch (Throwable e) { - e.printStackTrace(); - return false; - } - finally { - reset(); - } - } - - - private static void showUsage() { - System.out.println("Usage: jaxxc <options> <source files>"); - System.out.println(); - System.out.println("Source files must end in extension .jaxx"); - System.out.println("Use JAXX_OPTS environment variable to pass arguments to Java runtime"); - System.out.println(); - System.out.println("Supported options include:"); - System.out.println(" -classpath <paths> paths to search for user classes"); - System.out.println(" -cp <paths> same as -classpath"); - System.out.println(" -d <directory> target directory for generated class files"); - System.out.println(" -java or -j produce .java files, but do not compile them"); - System.out.println(" -keep or -k preserve generated .java files after compilation"); - System.out.println(" -optimize or -o optimize during compilation"); - System.out.println(" -version display version information"); - System.out.println(); - System.out.println("See http://www.jaxxframework.org/ for full documentation."); - } - - - public static String getVersion() { - return "1.0.4"; - } - - - public static void main(String[] arg) throws Exception { - boolean success = true; - - CompilerOptions options = new CompilerOptions(); - List<String> files = new ArrayList<String>(); - for (int i = 0; i < arg.length; i++) { - if (arg[i].endsWith(".jaxx")) { - files.add(arg[i]); - } else if (arg[i].equals("-d")) { - if (++i < arg.length) { - File targetDirectory = new File(arg[i]); - if (!targetDirectory.exists()) { - System.err.println("Error: could not find target directory: " + targetDirectory); - errorCount++; - success = false; - } - options.setTargetDirectory(targetDirectory); - } else { - success = false; - } - } else if (arg[i].equals("-cp") || arg[i].equals("-classpath")) { - if (++i < arg.length) { - options.setClassPath(arg[i]); - } else { - success = false; - } - } else if (arg[i].equals("-javac_opts")) { - if (++i < arg.length) { - options.setJavacOpts(arg[i]); - } else { - success = false; - } - } else if (arg[i].equals("-k") || arg[i].equals("-keep")) - options.setKeepJavaFiles(true); - else if (arg[i].equals("-j") || arg[i].equals("-java")) { - options.setKeepJavaFiles(true); - } else if (arg[i].equals("-o") || arg[i].equals("-optimize")) - options.setOptimize(true); - else if (arg[i].equals("-version")) { - System.err.println("jaxxc version " + getVersion() + " by Ethan Nicholas"); - System.err.println("http://www.jaxxframework.org/"); - System.exit(0); - } else if (arg[i].equals("-internalDumpVersion")) { // used by ant to extract the version info - System.out.println("jaxx.version=" + getVersion()); - return; - } else { - success = false; - } - } - - success &= (errorCount == 0 && files.size() > 0); - - if (success) { - success = compile(new File("."), files.toArray(new String[files.size()]), options); - } else { - showUsage(); - System.exit(1); - } - - System.exit(success ? 0 : 1); - } - - public File getDest() { - return dest; - } - - public void setFailed(boolean failed) { - this.failed = failed; - } -} Copied: lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/compiler/JAXXCompiler.java (from rev 858, lutinjaxx/trunk/core/src/main/java/jaxx/compiler/JAXXCompiler.java) =================================================================== --- lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/compiler/JAXXCompiler.java (rev 0) +++ lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/compiler/JAXXCompiler.java 2008-10-02 12:50:25 UTC (rev 865) @@ -0,0 +1,2096 @@ +/* + * Copyright 2006 Ethan Nicholas. All rights reserved. + * Use is subject to license terms. + */ +package jaxx.compiler; + +import jaxx.CompilerException; +import jaxx.UnsupportedAttributeException; +import jaxx.UnsupportedTagException; +import jaxx.css.Rule; +import jaxx.css.Stylesheet; +import jaxx.parser.ParseException; +import jaxx.reflect.ClassDescriptor; +import jaxx.reflect.ClassDescriptorLoader; +import jaxx.reflect.FieldDescriptor; +import jaxx.reflect.MethodDescriptor; +import jaxx.runtime.ComponentDescriptor; +import jaxx.runtime.JAXXObject; +import jaxx.runtime.JAXXObjectDescriptor; +import jaxx.runtime.swing.Application; +import jaxx.spi.Initializer; +import jaxx.tags.DefaultObjectHandler; +import jaxx.tags.TagHandler; +import jaxx.tags.TagManager; +import jaxx.types.TypeManager; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.helpers.AttributesImpl; +import org.xml.sax.helpers.XMLFilterImpl; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.transform.ErrorListener; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.sax.SAXSource; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectOutputStream; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Modifier; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.Stack; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.GZIPOutputStream; + +/** Compiles JAXX files into Java classes. */ +public class JAXXCompiler { + /** log */ + protected static final Log log = LogFactory.getLog(JAXXCompiler.class); + + /** + * True to throw exceptions when we encounter unresolvable classes, false to ignore. + * This is currently set to false until JAXX has full support for inner classes + * (including enumerations), because currently they don't always resolve (but will + * generally compile without error anyway). + */ + public static final boolean STRICT_CHECKS = false; + + public static final String JAXX_NAMESPACE = "http://www.jaxxframework.org/"; + public static final String JAXX_INTERNAL_NAMESPACE = "http://www.jaxxframework.org/internal"; + + /** Maximum length of an inlinable creation method. */ + private static final int INLINE_THRESHOLD = 300; + + /** Contains import declarations (of the form "javax.swing.") which are always imported in all compiler instances. */ + private static List<String> staticImports = new ArrayList<String>(); + + static { + //TODO humm, we should be able to import only what is needed + staticImports.add("java.awt.*"); + staticImports.add("java.awt.event.*"); + staticImports.add("java.beans.*"); + staticImports.add("java.io.*"); + staticImports.add("java.lang.*"); + staticImports.add("java.util.*"); + staticImports.add("javax.swing.*"); + staticImports.add("javax.swing.border.*"); + staticImports.add("javax.swing.event.*"); + staticImports.add("jaxx.runtime.swing.JAXXButtonGroup"); + staticImports.add("jaxx.runtime.swing.HBox"); + staticImports.add("jaxx.runtime.swing.VBox"); + staticImports.add("jaxx.runtime.swing.Table"); + staticImports.add("static org.codelutin.i18n.I18n._"); + staticImports.add("static org.codelutin.jaxx.util.UIHelper.createImageIcon"); + } + + private static DefaultObjectHandler firstPassClassTagHandler = new DefaultObjectHandler(ClassDescriptorLoader.getClassDescriptor(Object.class)); + + /** + * A list of Runnables which will be run after the first compilation pass. This is primarily used + * to trigger the creation of CompiledObjects, which cannot be created during the first pass and must be + * created in document order. + */ + private List<Runnable> initializers = new ArrayList<Runnable>(); + + /** Files being compiled during the compilation session, may be modified as compilation progresses and additional dependencies are found. */ + private static List<File> jaxxFiles = new ArrayList<File>(); + + /** Class names corresponding to the files in the jaxxFiles list. */ + private static List<String> jaxxFileClassNames = new ArrayList<String>(); + + /** Maps the names of classes being compiled to the compiler instance handling the compilation. */ + private static Map<String, JAXXCompiler> compilers = new HashMap<String, JAXXCompiler>(); + + /** Maps the names of classes being compiled to their symbol tables (created after the first compiler pass). */ + private static Map<File, SymbolTable> symbolTables = new HashMap<File, SymbolTable>(); + + private CompilerOptions options; + + /** Used for error reporting purposes, so we can report the right line number. */ + private Stack<Element> tagsBeingCompiled = new Stack<Element>(); + + /** Used for error reporting purposes, so we can report the right source file. */ + private Stack<File> sourceFiles = new Stack<File>(); + + /** Maps object ID strings to the objects themselves. These are created during the second compilation pass. */ + private Map<String, CompiledObject> objects = new LinkedHashMap<String, CompiledObject>(); + + /** Maps objects to their ID strings. These are created during the second compilation pass. */ + private Map<CompiledObject, String> ids = new LinkedHashMap<CompiledObject, String>(); + + private static int errorCount; + private static int warningCount; + + private boolean failed; + + /** Object corresponding to the root tag in the document. */ + private CompiledObject root; + + /** Contains strings of the form "javax.swing." */ + private Set<String> importedPackages = new HashSet<String>(); + + /** Contains strings of the form "javax.swing.Timer" */ + private Set<String> importedClasses = new HashSet<String>(); + + /** Keeps track of open components (components still having children added). */ + private Stack<CompiledObject> openComponents = new Stack<CompiledObject>(); + + /** Sequence number used to create automatic variable names. */ + private int autogenID = 0; + + private List<DataBinding> dataBindings = new ArrayList<DataBinding>(); + + private JavaFile javaFile = new JavaFile(); + + // true if a main() method has been declared in a script + boolean mainDeclared; + + private SymbolTable symbolTable = new SymbolTable(); + + // TODO: replace these public StringBuffers with something a little less stupid + + /** Extra code to be added to the instance initializer. */ + public StringBuffer initializer = new StringBuffer(); + + /** Extra code to be added at the end of the instance initializer. */ + public StringBuffer lateInitializer = new StringBuffer(); + + /** Extra code to be added to the class body. */ + public StringBuffer bodyCode = new StringBuffer(); + + /** Code to initialize data bindings. */ + public StringBuffer initDataBindings = new StringBuffer(); + + /** Body of the applyDataBinding method. */ + public StringBuffer applyDataBinding = new StringBuffer(); + + /** Body of the removeDataBinding method. */ + public StringBuffer removeDataBinding = new StringBuffer(); + + /** Body of the processDataBinding method. */ + public StringBuffer processDataBinding = new StringBuffer(); + + /** Base directory used for path resolution (normally the directory in which the .jaxx file resides). */ + private File baseDir; + + /** .jaxx file being compiled. */ + private File src; + + /** Generated .java file. */ + private File dest; + + /** Parsed XML of src file. */ + private Document document; + + /** Name of class being compiled. */ + private String outputClassName; + + private ScriptManager scriptManager = new ScriptManager(this); + + /** Combination of all stylesheets registered using {@link #registerStylesheet}. */ + private Stylesheet stylesheet; + + /** Contains all attributes defined inline on class tags. */ + private List<Rule> inlineStyles = new ArrayList<Rule>(); + + /** + * Maps objects (expressed in Java code) to event listener classes (e.g. MouseListener) to Lists of EventHandlers. The final list + * contains all event handlers of a particular type attached to a particular object (again, as represented by a Java expression). + */ + private Map<String, Map<ClassDescriptor, List<EventHandler>>> eventHandlers = new HashMap<String, Map<ClassDescriptor, List<EventHandler>>>(); + + private Map<Object, String> uniqueIds = new HashMap<Object, String>(); + + private Map<EventHandler, String> eventHandlerMethodNames = new HashMap<EventHandler, String>(); + + /** ClassLoader which searches the user-specified class path in addition to the normal class path */ + private ClassLoader classLoader; + + private static final int PASS_1 = 0; + private static final int PASS_2 = 1; + private static int currentPass; + + static { + try { + // fixme beware, this is a very dangerous thing to use a static block + //loadLibraries(); + // fixeme for the moment the compiler is init in maven plugin, not here + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void init() { + // forces static initializer to run if it hasn't yet + } + + public static void loadLibraries(boolean verbose) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException { + ClassLoader classloader = Thread.currentThread().getContextClassLoader(); + if (verbose) { + log.info("with cl " + classloader); + } + ServiceLoader<Initializer> loader = ServiceLoader.load(Initializer.class, classloader); + for (Initializer initializer : loader) { + if (verbose) { + log.info("load initializer " + initializer); + } + initializer.initialize(); + } + } + + private JAXXCompiler(ClassLoader classLoader) { + this.options = new CompilerOptions(); + this.classLoader = classLoader; + addImport("java.lang.*"); + } + + + /** + * Creates a new JAXXCompiler. + * + * @param baseDir classpath location + * @param options options to pass to javac + * @param src location of file to compile + * @param outputClassName the out file name + */ + protected JAXXCompiler(File baseDir, File src, String outputClassName, CompilerOptions options) { + this.baseDir = baseDir; + this.src = src; + sourceFiles.push(src); + this.outputClassName = outputClassName; + this.options = options; + addImport(outputClassName.substring(0, outputClassName.lastIndexOf(".") + 1) + "*"); + for (Object staticImport : staticImports) + addImport((String) staticImport); + } + + + /** + * Creates a dummy JAXXCompiler for use in unit testing. + * + * @return the compiler + */ + public static JAXXCompiler createDummyCompiler() { + return createDummyCompiler(JAXXCompiler.class.getClassLoader()); + } + + + /** + * Creates a dummy JAXXCompiler for use in unit testing. + * + * @param classLoader class loader to use + * @return the compiler + */ + public static JAXXCompiler createDummyCompiler(ClassLoader classLoader) { + return new JAXXCompiler(classLoader); + } + + + public CompilerOptions getOptions() { + return options; + } + + + public JavaFile getJavaFile() { + return javaFile; + } + + + private void compileFirstPass() throws IOException { + try { + InputStream in = new FileInputStream(src); + document = parseDocument(in); + in.close(); + compileFirstPass(document.getDocumentElement()); + } + catch (SAXParseException e) { + reportError(e.getLineNumber(), "Invalid XML: " + e.getMessage()); + } + catch (SAXException e) { + reportError(null, "Error parsing XML document: " + e); + } + } + + + private void runInitializers() { + for (Runnable runnable : initializers) { + if (log.isDebugEnabled()) { + log.debug(runnable); + } + runnable.run(); + } + initializers.clear(); + } + + + /** + * Registers a <code>Runnable</code> which will be executed after the first + * compilation pass is complete. + * + * @param r runnable to register + */ + public void registerInitializer(Runnable r) { + initializers.add(r); + } + + + private void compileSecondPass() throws IOException { + if (!tagsBeingCompiled.isEmpty()) { + throw new RuntimeException("Internal error: starting pass two, but tagsBeingCompiled is not empty: " + tagsBeingCompiled); + } + compileSecondPass(document.getDocumentElement()); + } + + + private void applyStylesheets() { + for (Object o : new ArrayList<CompiledObject>(objects.values())) { + CompiledObject object = (CompiledObject) o; + TagManager.getTagHandler(object.getObjectClass()).applyStylesheets(object, this); + } + } + + + private void generateCode() throws IOException { + if (options.getTargetDirectory() != null) { + dest = new File(options.getTargetDirectory(), outputClassName.replace('.', File.separatorChar) + ".java"); + } else { + dest = new File(baseDir, outputClassName.substring(outputClassName.lastIndexOf(".") + 1) + ".java"); + } + PrintWriter out = new PrintWriter(new FileWriter(dest)); + createJavaSource(out); + out.close(); + } + + private void createJavaSource(PrintWriter out) throws IOException { + int dotPos = outputClassName.lastIndexOf("."); + String packageName = dotPos != -1 ? outputClassName.substring(0, dotPos) : null; + String simpleClassName = outputClassName.substring(dotPos + 1); + outputClass(packageName, simpleClassName, out); + } + + + public String getOutputClassName() { + return outputClassName; + } + + public static SAXParser getSAXParser() { + try { + SAXParserFactory factory = SAXParserFactory.newInstance(); + factory.setNamespaceAware(true); + SAXParser parser; + parser = factory.newSAXParser(); + return parser; + } + catch (SAXException e) { + throw new RuntimeException(e); + } + catch (ParserConfigurationException e) { + throw new RuntimeException(e); + } + } + + public static Document parseDocument(InputStream in) throws IOException, SAXException { + try { + TransformerFactory factory = TransformerFactory.newInstance(); + Transformer transformer = factory.newTransformer(); + transformer.setErrorListener(new ErrorListener() { + public void warning(TransformerException ex) throws TransformerException { + throw ex; + } + + public void error(TransformerException ex) throws TransformerException { + throw ex; + } + + public void fatalError(TransformerException ex) throws TransformerException { + throw ex; + } + }); + + DOMResult result = new DOMResult(); + transformer.transform(new SAXSource(new XMLFilterImpl(getSAXParser().getXMLReader()) { + Locator locator; + + @Override + public void setDocumentLocator(Locator locator) { + this.locator = locator; + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { + AttributesImpl resultAtts = new AttributesImpl(atts); + resultAtts.addAttribute(JAXX_INTERNAL_NAMESPACE, "line", "internal:line", "CDATA", String.valueOf(locator.getLineNumber())); + getContentHandler().startElement(uri, localName, qName, resultAtts); + } + }, new InputSource(in)), result); + return (Document) result.getNode(); + } + catch (TransformerConfigurationException e) { + throw new RuntimeException(e); + } + catch (TransformerException e) { + Throwable ex = e; + while (ex.getCause() != null) + ex = ex.getCause(); + if (ex instanceof IOException) + throw (IOException) ex; + if (ex instanceof SAXException) + throw (SAXException) ex; + if (ex instanceof RuntimeException) + throw (RuntimeException) ex; + throw new RuntimeException(ex); + } + } + + + public File getBaseDir() { + return baseDir; + } + + + public Set<String> getImportedClasses() { + return importedClasses; + } + + + public Set<String> getImportedPackages() { + return importedPackages; + } + + + private boolean inlineCreation(CompiledObject object) { + return object.getId().startsWith("$") && object.getInitializationCode(this).length() < INLINE_THRESHOLD; + } + + + public void checkOverride(CompiledObject object) throws CompilerException { + if (object.getId().startsWith("$")) { + return; + } + ClassDescriptor ancestor = root.getObjectClass(); + if (ancestor == object.getObjectClass()) { + return; + } + while (ancestor != null) { + try { + FieldDescriptor f = ancestor.getDeclaredFieldDescriptor(object.getId()); + if (!f.getType().isAssignableFrom(object.getObjectClass())) { + reportError("attempting to redefine superclass member '" + object.getId() + "' as incompatible type (was " + f.getType() + ", redefined as " + object.getObjectClass() + ")"); + } + object.setOverride(true); + break; + } + catch (NoSuchFieldException e) { + ancestor = ancestor.getSuperclass(); + } + } + } + + + private Iterator<CompiledObject> getObjectCreationOrder() { + return objects.values().iterator(); + } + + + protected JavaMethod createConstructor(String className) throws CompilerException { + StringBuffer code = new StringBuffer(); + String constructorParams = root.getConstructorParams(); + if (constructorParams != null) { + code.append(" super(").append(constructorParams).append(");"); + code.append(getLineSeparator()); + } + code.append("$initialize();"); + code.append(getLineSeparator()); + return new JavaMethod(Modifier.PUBLIC, null, className, null, null, code.toString()); + } + + + protected JavaMethod createInitializer(String className) throws CompilerException { + StringBuffer code = new StringBuffer(); + code.append("$objectMap.put(").append(TypeManager.getJavaCode(root.getId())).append(", this);"); + code.append(getLineSeparator()); + + Iterator<CompiledObject> i = getObjectCreationOrder(); + boolean lastWasMethodCall = false; + while (i.hasNext()) { + CompiledObject object = i.next(); + if (object != root && !object.isOverride()) { + if (inlineCreation(object)) { + if (lastWasMethodCall) { + lastWasMethodCall = false; + code.append(getLineSeparator()); + } + code.append(getCreationCode(object)); + code.append(getLineSeparator()); + } else { + code.append(object.getCreationMethodName()).append("();"); + code.append(getLineSeparator()); + lastWasMethodCall = true; + } + } + } + String rootCode = root.getInitializationCode(this); + if (rootCode != null && rootCode.length() > 0) { + code.append(rootCode); + code.append(getLineSeparator()); + } + code.append(getLineSeparator()); + if (initializer.length() > 0) { + code.append(initializer); + code.append(getLineSeparator()); + } + code.append("$completeSetup();"); + code.append(getLineSeparator()); + return new JavaMethod(Modifier.PRIVATE, "void", "$initialize", null, null, code.toString()); + } + + + protected JavaMethod createCompleteSetupMethod() { + StringBuffer code = new StringBuffer(); + code.append("allComponentsCreated = true;"); + code.append(getLineSeparator()); + for (CompiledObject object : objects.values()) { + if (object.getId().startsWith("$")) { + code.append(object.getAdditionCode()); + } else { + code.append(object.getAdditionMethodName()).append("();").append(getLineSeparator()); + String additionCode = object.getAdditionCode(); + if (additionCode.length() > 0) { + additionCode = "if (allComponentsCreated) {" + getLineSeparator() + additionCode + "}"; + } + javaFile.addMethod(new JavaMethod(Modifier.PROTECTED, "void", object.getAdditionMethodName(), null, null, additionCode)); + } + code.append(getLineSeparator()); + } + + code.append(initDataBindings); + + if (lateInitializer.length() > 0) { + code.append(lateInitializer); + code.append(getLineSeparator()); + } + return new JavaMethod(Modifier.PRIVATE, "void", "$completeSetup", null, null, code.toString()); + } + + + protected JavaMethod createProcessDataBindingMethod() { + StringBuffer code = new StringBuffer(); + boolean superclassIsJAXXObject = ClassDescriptorLoader.getClassDescriptor(JAXXObject.class).isAssignableFrom(root.getObjectClass()); + // the force parameter forces the update to happen even if it is already in activeBindings. This + // is used on superclass invocations b/c by the time the call gets to the superclass, it is already + // marked active and would otherwise be skipped + if (processDataBinding.length() > 0) { + code.append(" if (!$force && $activeBindings.contains($dest)) return;"); + code.append(getLineSeparator()); + code.append(" $activeBindings.add($dest);"); + code.append(getLineSeparator()); + code.append(" try {"); + code.append(getLineSeparator()); + if (processDataBinding.length() > 0) { + code.append(processDataBinding); + code.append(getLineSeparator()); + } + if (superclassIsJAXXObject) { + code.append(" else"); + code.append(getLineSeparator()); + code.append(" super.processDataBinding($dest, true);"); + code.append(getLineSeparator()); + } + code.append(" }"); + code.append(getLineSeparator()); + code.append(" finally {"); + code.append(getLineSeparator()); + code.append(" $activeBindings.remove($dest);"); + code.append(getLineSeparator()); + code.append(" }"); + code.append(getLineSeparator()); + } else if (superclassIsJAXXObject) { + code.append(" super.processDataBinding($dest, true);"); + code.append(getLineSeparator()); + } + return new JavaMethod(Modifier.PUBLIC, "void", "processDataBinding", + new JavaArgument[]{new JavaArgument("String", "$dest"), new JavaArgument("boolean", "$force")}, + null, code.toString()); + } + + + protected void createJavaFile(String packageName, String className) throws CompilerException { + String fullClassName = packageName != null ? packageName + "." + className : className; + if (root == null) { + throw new CompilerException("root tag must be a class tag"); + } + ClassDescriptor superclass = root.getObjectClass(); + boolean superclassIsJAXXObject = ClassDescriptorLoader.getClassDescriptor(JAXXObject.class).isAssignableFrom(superclass); + javaFile.setModifiers(Modifier.PUBLIC); + javaFile.setClassName(fullClassName); + javaFile.setSuperClass(getCanonicalName(superclass)); + javaFile.setInterfaces(new String[]{getCanonicalName(JAXXObject.class)}); + + for (CompiledObject object : objects.values()) { + if (!object.isOverride() && !(object instanceof ScriptInitializer)) { + int access = object.getId().startsWith("$") ? Modifier.PRIVATE : Modifier.PROTECTED; + if (object == root) { + javaFile.addField(new JavaField(access, fullClassName, object.getId(), "this")); + } else { + javaFile.addField(new JavaField(access, getCanonicalName(object.getObjectClass()), object.getId())); + } + } + } + + if (!superclassIsJAXXObject) { + javaFile.addField(new JavaField(Modifier.PROTECTED, "java.util.List<Object>", "$activeBindings", "new ArrayList<Object>()")); + javaFile.addField(new JavaField(Modifier.PROTECTED, "java.util.Map<String,Object>", "$bindingSources", "new HashMap<String,Object>()")); + } + + if (stylesheet != null) { + javaFile.addField(new JavaField(0, "java.util.Map", "$previousValues", "new java.util.HashMap()")); + } + + javaFile.addMethod(createConstructor(className)); + javaFile.addMethod(createInitializer(className)); + + for (DataBinding dataBinding : dataBindings) { + if (dataBinding.compile(true)) { + initDataBindings.append("applyDataBinding(").append(TypeManager.getJavaCode(dataBinding.getId())).append(");").append(JAXXCompiler.getLineSeparator()); + } + } + + javaFile.addBodyCode(bodyCode.toString()); + + for (CompiledObject object : objects.values()) { + if (!inlineCreation(object) && object != root) { + javaFile.addMethod(new JavaMethod(Modifier.PROTECTED, "void", object.getCreationMethodName(), null, null, getCreationCode(object))); + } + } + + javaFile.addField(new JavaField(Modifier.PRIVATE, "boolean", "allComponentsCreated")); + + javaFile.addMethod(createCompleteSetupMethod()); + + javaFile.addMethod(new JavaMethod(Modifier.PUBLIC, "void", "applyDataBinding", new JavaArgument[]{new JavaArgument("String", "$binding")}, + null, applyDataBinding.toString() + getLineSeparator() + " processDataBinding($binding);")); + + javaFile.addMethod(new JavaMethod(Modifier.PUBLIC, "void", "removeDataBinding", new JavaArgument[]{new JavaArgument("String", "$binding")}, + null, removeDataBinding.toString())); + + javaFile.addMethod(new JavaMethod(Modifier.PUBLIC, "void", "processDataBinding", new JavaArgument[]{new JavaArgument("String", "dest")}, + null, "processDataBinding(dest, false);")); + + javaFile.addMethod(createProcessDataBindingMethod()); + + if (!superclassIsJAXXObject) { + javaFile.addField(createObjectMap()); + javaFile.addMethod(createGetObjectByIdMethod()); + } + + javaFile.addField(createJAXXObjectDescriptorField()); + javaFile.addMethod(createGetJAXXObjectDescriptorMethod()); + +/* + * Gestion du context + */ + javaFile.addField(createContextField()); + javaFile.addMethod(createSetContextValueMethod()); + javaFile.addMethod(createSetContextValueNameMethod()); + javaFile.addMethod(createGetContextValueMethod()); + javaFile.addMethod(createGetContextValueNameMethod()); + javaFile.addMethod(createGetParentContainer()); + javaFile.addMethod(createGetParentContainerMore()); + ClassDescriptor currentClass = root.getObjectClass(); + MethodDescriptor firePropertyChange = null; + while (firePropertyChange == null && currentClass != null) { + try { + firePropertyChange = currentClass.getDeclaredMethodDescriptor("firePropertyChange", new ClassDescriptor[]{ + ClassDescriptorLoader.getClassDescriptor(String.class), + ClassDescriptorLoader.getClassDescriptor(Object.class), + ClassDescriptorLoader.getClassDescriptor(Object.class) + }); + + } + catch (NoSuchMethodException e) { + currentClass = currentClass.getSuperclass(); + } + } + + int modifiers = firePropertyChange != null ? firePropertyChange.getModifiers() : 0; + if (Modifier.isPublic(modifiers)) { + // we have all the support we need + } + if (Modifier.isProtected(modifiers)) { + // there is property change support but the firePropertyChange method is protected + javaFile.addMethod(new JavaMethod(Modifier.PUBLIC, "void", "firePropertyChange", new JavaArgument[]{ + new JavaArgument("java.lang.String", "propertyName"), new JavaArgument("java.lang.Object", "oldValue"), new JavaArgument("java.lang.Object", "newValue")}, + null, "super.firePropertyChange(propertyName, oldValue, newValue);")); + } else { + // either no support at all or firePropertyChange isn't accessible + addPropertyChangeSupport(javaFile); + } + + addEventHandlers(javaFile); + + if (ClassDescriptorLoader.getClassDescriptor(Application.class).isAssignableFrom(root.getObjectClass()) && !mainDeclared) { + // TODO: check for existing main method first + javaFile.addMethod(new JavaMethod(Modifier.PUBLIC | Modifier.STATIC, "void", "main", + new JavaArgument[]{new JavaArgument("String[]", "arg")}, null, + "SwingUtilities.invokeLater(new Runnable() { public void run() { new " + className + "().setVisible(true); } });")); + } + } + + + protected void outputClass(String packageName, String className, PrintWriter out) throws CompilerException { + createJavaFile(packageName, className); + out.println(javaFile.toString()); + } + + + public void addImport(String text) { + if (text.endsWith("*")) { + importedPackages.add(text.substring(0, text.length() - 1)); + } else { + importedClasses.add(text); + } + + if (!text.equals("*")) { + getJavaFile().addImport(text); + } + } + +/* + * Gestion du context + */ + private JavaField createContextField() { + return new JavaField(Modifier.PROTECTED, "Map<Object,String>", "$contextMap", "new HashMap<Object,String>()"); + } + private JavaMethod createSetContextValueMethod() { + + return new JavaMethod(Modifier.PUBLIC, "void", "setContextValue", + new JavaArgument[]{new JavaArgument("Object", "clazz")}, null, getSetContextValueMethodCode()); + } + private JavaMethod createSetContextValueNameMethod() { + return new JavaMethod(Modifier.PUBLIC, "void", "setContextValue", + new JavaArgument[]{new JavaArgument("Object", "clazz"),new JavaArgument("String", "name")}, null, getSetContextValueNameMethodCode()); + } + private JavaMethod createGetContextValueMethod() { + return new JavaMethod(Modifier.PUBLIC, "<T> T", "getContextValue", + new JavaArgument[]{new JavaArgument("Class<T>", "clazz")}, null, getGetContextValueMethodCode()); + } + private JavaMethod createGetContextValueNameMethod() { + return new JavaMethod(Modifier.PUBLIC, "<T> T", "getContextValue", + new JavaArgument[]{new JavaArgument("Class<T>", "clazz"),new JavaArgument("String", "name")}, null, getGetContextValueNameMethodCode()); + } + private JavaMethod createGetParentContainer(){ + return new JavaMethod(Modifier.PUBLIC, "<O extends Container> O", "getParentContainer", + new JavaArgument[]{new JavaArgument("Class<O>", "clazz")}, null, getGetParentContenerMethodCode()); + } + private JavaMethod createGetParentContainerMore(){ + return new JavaMethod(Modifier.PUBLIC, "<O extends Container> O", "getParentContainer", + new JavaArgument[]{new JavaArgument("Container", "top"),new JavaArgument("Class<O>", "clazz")}, null, getGetParentContenerMethodMoreCode()); + } + + private String getSetContextValueMethodCode() { + return "this.setContextValue(clazz, null);"; + } + + private String getSetContextValueNameMethodCode() { + StringBuffer result = new StringBuffer(); + result.append("$contextMap.put(clazz, name);"); + return result.toString(); + } + + private String getGetContextValueMethodCode() { + return "return this.getContextValue(clazz, null);"; + } + + private String getGetContextValueNameMethodCode() { + StringBuffer result = new StringBuffer(); + result.append("for (Map.Entry<Object,String> entry : $contextMap.entrySet()) {"); + result.append("if (clazz.isAssignableFrom(entry.getKey().getClass()) && (name == null || name == entry.getValue())) {"); + result.append("return (T) entry.getKey();}}"); + result.append("return null;"); + return result.toString(); + } + + private String getGetParentContenerMethodCode(){ + StringBuffer result = new StringBuffer(); + result.append("return this.getParentContainer(this, clazz);"); + return result.toString(); + } + + private String getGetParentContenerMethodMoreCode(){ + StringBuffer result = new StringBuffer(); + result.append("Container parent = top.getParent();"); + result.append("if (parent != null && !clazz.isAssignableFrom(parent.getClass())){parent = getParentContainer(parent, clazz);}"); + result.append("return (O)parent;"); + return result.toString(); + } + + private JavaField createObjectMap() { + return new JavaField(Modifier.PROTECTED, "Map<String,Object>", "$objectMap", "new HashMap<String,Object>()"); + } + + + protected JavaMethod createGetObjectByIdMethod() { + return new JavaMethod(Modifier.PUBLIC, "java.lang.Object", "getObjectById", + new JavaArgument[]{new JavaArgument("String", "id")}, null, + "return $objectMap.get(id);"); + } + + + public JAXXObjectDescriptor getJAXXObjectDescriptor() { + runInitializers(); + CompiledObject[] components = new ArrayList<CompiledObject>(objects.values()).toArray(new CompiledObject[objects.size()]); + assert initializers.isEmpty() : "there are pending initializers remaining"; + assert root != null : "root object has not been defined"; + assert Arrays.asList(components).contains(root) : "root object is not registered"; + ComponentDescriptor[] descriptors = new ComponentDescriptor[components.length]; + // as we print, sort the array so that component's parents are always before the components themselves + for (int i = 0; i < components.length; i++) { + CompiledObject parent = components[i].getParent(); + while (parent != null) { + boolean found = false; + for (int j = i + 1; j < components.length; j++) { // found parent after component, swap them + if (components[j] == parent) { + components[j] = components[i]; + components[i] = parent; + found = true; + break; + } + } + if (!found) { + break; + } + parent = components[i].getParent(); + } + int parentIndex = -1; + if (parent != null) { + for (int j = 0; j < i; j++) { + if (components[j] == parent) { + parentIndex = j; + break; + } + } + } + descriptors[i] = new ComponentDescriptor(components[i].getId(), components[i] == root ? outputClassName : components[i].getObjectClass().getName(), + components[i].getStyleClass(), parentIndex != -1 ? descriptors[parentIndex] : null); + } + + Stylesheet stylesheet = getStylesheet(); + if (stylesheet == null) + stylesheet = new Stylesheet(); + + return new JAXXObjectDescriptor(descriptors, stylesheet); + } + + + protected JavaField createJAXXObjectDescriptorField() { + try { + JAXXObjectDescriptor descriptor = getJAXXObjectDescriptor(); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(new GZIPOutputStream(buffer)); + out.writeObject(descriptor); + out.close(); + // the use of the weird deprecated constructor is deliberate -- we need to store the data as a String + // in the compiled class file, since byte array initialization is horribly inefficient compared to + // String initialization. So we store the bytes in the String, and we quite explicitly want a 1:1 + // mapping between bytes and chars, with the high byte of the char set to zero. We can then safely + // reconstitute the original byte[] at a later date. This is unquestionably an abuse of the String + // type, but if we could efficiently store a byte[] we wouldn't have to do this. + String data = new String(buffer.toByteArray(), 0); + + int sizeLimit = 65000; // constant strings are limited to 64K, and I'm not brave enough to push right up to the limit + if (data.length() < sizeLimit) { + return new JavaField(Modifier.PRIVATE | Modifier.STATIC, "java.lang.String", "$jaxxObjectDescriptor", TypeManager.getJavaCode(data)); + } else { + StringBuffer initializer = new StringBuffer(); + for (int i = 0; i < data.length(); i += sizeLimit) { + String name = "$jaxxObjectDescriptor" + i; + javaFile.addField(new JavaField(Modifier.PRIVATE | Modifier.STATIC, "java.lang.String", name, + TypeManager.getJavaCode(data.substring(i, Math.min(i + sizeLimit, data.length()))))); + if (initializer.length() > 0) { + initializer.append(" + "); + } + initializer.append("String.valueOf(").append(name).append(")"); + } + return new JavaField(Modifier.PRIVATE | Modifier.STATIC, "java.lang.String", "$jaxxObjectDescriptor", initializer.toString()); + } + } + catch (IOException e) { + throw new RuntimeException("Internal error: can't-happen error", e); + } + } + + + protected JavaMethod createGetJAXXObjectDescriptorMethod() { + return new JavaMethod(Modifier.PUBLIC | Modifier.STATIC, "jaxx.runtime.JAXXObjectDescriptor", "$getJAXXObjectDescriptor", + null, null, "return jaxx.runtime.Util.decodeCompressedJAXXObjectDescriptor($jaxxObjectDescriptor);"); + } + + + public String getEventHandlerMethodName(EventHandler handler) { + String result = eventHandlerMethodNames.get(handler); + if (result == null) { + result = "$ev" + eventHandlerMethodNames.size(); + eventHandlerMethodNames.put(handler, result); + } + return result; + } + + protected void addEventHandlers(JavaFile javaFile) { + for (Map.Entry<String, Map<ClassDescriptor, List<EventHandler>>> e1 : eventHandlers.entrySet()) { + // outer loop is iterating over different objects (well, technically, different Java expressions) + for (Map.Entry<ClassDescriptor, List<EventHandler>> e2 : e1.getValue().entrySet()) { + // iterate over different types of listeners for this particular object (MouseListener, ComponentListener, etc.) + for (EventHandler handler : e2.getValue()) { + // iterate over individual event handlers of a single type + String methodName = getEventHandlerMethodName(handler); + MethodDescriptor listenerMethod = handler.getListenerMethod(); + if (listenerMethod.getParameterTypes().length != 1) { + throw new CompilerException("Expected event handler " + listenerMethod.getName() + " of class " + handler.getListenerClass() + " to have exactly one argument"); + } + javaFile.addMethod(new JavaMethod(Modifier.PUBLIC, "void", methodName, + new JavaArgument[]{new JavaArgument(getCanonicalName(listenerMethod.getParameterTypes()[0]), "event")}, null, + handler.getJavaCode())); + + } + } + } + } + + protected String getCreationCode(CompiledObject object) throws CompilerException { + if (object instanceof ScriptInitializer) { + return object.getInitializationCode(this); + } + StringBuffer result = new StringBuffer(); + result.append(object.getId()); + result.append(" = "); + String constructorParams = object.getConstructorParams(); + if (constructorParams != null) { + result.append(" new ").append(getCanonicalName(object.getObjectClass())).append("(").append(constructorParams).append(");"); + //result.append("(").append(getCanonicalName(object.getObjectClass())).append(") new ").append(getCanonicalName(object.getObjectClass())).append("(").append(constructorParams).append(");"); + } else { + result.append("new ").append(getCanonicalName(object.getObjectClass())).append("();"); + } + result.append(getLineSeparator()); + String initCode = object.getInitializationCode(this); + if (initCode != null && initCode.length() > 0) { + result.append(initCode); + } + result.append("$objectMap.put(").append(TypeManager.getJavaCode(object.getId())).append(", ").append(object.getId()).append(");"); + + return result.toString(); + } + + + protected void addPropertyChangeSupport(JavaFile javaFile) throws CompilerException { + javaFile.addField(new JavaField(0, "java.beans.PropertyChangeSupport", "$propertyChangeSupport")); + + javaFile.addMethod(new JavaMethod(0, "java.beans.PropertyChangeSupport", "$getPropertyChangeSupport", null, null, + "if ($propertyChangeSupport == null)\n" + + " $propertyChangeSupport = new PropertyChangeSupport(this);\n" + + "return $propertyChangeSupport;")); + + javaFile.addMethod(new JavaMethod(Modifier.PUBLIC, "void", "addPropertyChangeListener", new JavaArgument[]{ + new JavaArgument("java.beans.PropertyChangeListener", "listener")}, null, + "$getPropertyChangeSupport().addPropertyChangeListener(listener);")); + + javaFile.addMethod(new JavaMethod(Modifier.PUBLIC, "void", "addPropertyChangeListener", new JavaArgument[]{ + new JavaArgument("java.lang.String", "property"), new JavaArgument("java.beans.PropertyChangeListener", "listener")}, null, + "$getPropertyChangeSupport().addPropertyChangeListener(property, listener);")); + + javaFile.addMethod(new JavaMethod(Modifier.PUBLIC, "void", "removePropertyChangeListener", new JavaArgument[]{ + new JavaArgument("java.beans.PropertyChangeListener", "listener")}, null, + "$getPropertyChangeSupport().removePropertyChangeListener(listener);")); + + javaFile.addMethod(new JavaMethod(Modifier.PUBLIC, "void", "removePropertyChangeListener", new JavaArgument[]{ + new JavaArgument("java.lang.String", "property"), new JavaArgument("java.beans.PropertyChangeListener", "listener")}, null, + "$getPropertyChangeSupport().removePropertyChangeListener(property, listener);")); + + javaFile.addMethod(new JavaMethod(Modifier.PUBLIC, "void", "firePropertyChange", new JavaArgument[]{ + new JavaArgument("java.lang.String", "propertyName"), new JavaArgument("java.lang.Object", "oldValue"), new JavaArgument("java.lang.Object", "newValue")}, + null, "$getPropertyChangeSupport().firePropertyChange(propertyName, oldValue, newValue);")); + } + + + public void compileFirstPass(final Element tag) throws IOException { + tagsBeingCompiled.push(tag); + + String namespace = tag.getNamespaceURI(); + String fullClassName; + String localName = tag.getLocalName(); + boolean namespacePrefix = tag.getPrefix() != null; + // resolve class tags into fully-qualified class name + if (namespace != null && namespace.endsWith("*")) { + String packageName = namespace.substring(0, namespace.length() - 1); + if (localName.startsWith(packageName)) // class name is fully-qualified already + { + fullClassName = TagManager.resolveClassName(localName, this); + } else { // namespace not included in class name, probably need the namespace to resolve + fullClassName = TagManager.resolveClassName(packageName + localName, this); + if (fullClassName == null && !namespacePrefix) // it was just a default namespace, try again without using the namespace + { + fullClassName = TagManager.resolveClassName(localName, this); + } + } + } else { + fullClassName = TagManager.resolveClassName(localName, this); + } + + if (fullClassName != null) { // we are definitely dealing with a class tag + addDependencyClass(fullClassName); + namespace = fullClassName.substring(0, fullClassName.lastIndexOf(".") + 1) + "*"; + if (symbolTable.getSuperclassName() == null) { + symbolTable.setSuperclassName(fullClassName); + } + String id = tag.getAttribute("id"); + if (id.length() > 0) { + symbolTable.getClassTagIds().put(id, fullClassName); + } + } + // during the first pass, we can't create ClassDescriptors for JAXX files because they may not have been processed yet + // (and we can't wait until they have been processed because of circular dependencies). So we don't do any processing + // during the first pass which requires having a ClassDescriptor; here we determine whether we have a class tag or not + // (class tag namespaces end in "*") and use a generic handler if so. The real handler is used during the second pass. + TagHandler handler = (namespace != null && namespace.endsWith("*")) ? firstPassClassTagHandler : TagManager.getTagHandler(tag.getNamespaceURI(), localName, namespacePrefix, this); + if (handler != firstPassClassTagHandler && handler instanceof DefaultObjectHandler) { + fullClassName = ((DefaultObjectHandler) handler).getBeanClass().getName(); + //namespace = fullClassName.substring(0, fullClassName.lastIndexOf(".") + 1) + "*"; + handler = firstPassClassTagHandler; + } + if (handler == firstPassClassTagHandler) { + final String finalClassName = fullClassName; + registerInitializer(new Runnable() { // register an initializer which will create the CompiledObject after pass 1 + + public void run() { + DefaultObjectHandler handler = (DefaultObjectHandler) TagManager.getTagHandler(null, finalClassName, JAXXCompiler.this); + if (handler == null) { + throw new CompilerException("Internal error: missing TagHandler for '" + finalClassName + "'"); + } + handler.registerCompiledObject(tag, JAXXCompiler.this); + } + }); + } + if (handler != null) { + try { + handler.compileFirstPass(tag, this); + } + catch (CompilerException e) { + reportError(e); + } + } else { + reportError("Could not find a Java class corresponding to: <" + tag.getTagName() + ">"); + failed = true; + } + + Element finished = tagsBeingCompiled.pop(); + if (finished != tag) { + throw new RuntimeException("internal error: just finished compiling " + tag + ", but top of tagsBeingCompiled stack is " + finished); + } + } + + + public void compileSecondPass(Element tag) throws IOException { + tagsBeingCompiled.push(tag); + + TagHandler handler = TagManager.getTagHandler(tag.getNamespaceURI(), tag.getLocalName(), tag.getPrefix() != null, this); + if (handler != null) { + handler.compileSecondPass(tag, this); + } else { + reportError("Could not find a Java class corresponding to: <" + tag.getTagName() + ">"); + assert false : "can't-happen error: error should have been reported during the fast pass and caused an abort"; + failed = true; + } + + Element finished = tagsBeingCompiled.pop(); + if (finished != tag) { + throw new RuntimeException("internal error: just finished compiling " + tag + ", but top of tagsBeingCompiled stack is " + finished); + } + + } + + + // 1.5 adds getCanonicalName; unfortunately we can't depend on 1.5 features yet + public static String getCanonicalName(Class clazz) { + if (clazz.isArray()) { + String canonicalName = getCanonicalName(clazz.getComponentType()); + if (canonicalName != null) { + return canonicalName + "[]"; + } + return null; + } + return clazz.getName().replace('$', '.'); + } + + + public static String getCanonicalName(ClassDescriptor clazz) { + if (clazz.isArray()) { + String canonicalName = getCanonicalName(clazz.getComponentType()); + if (canonicalName != null) { + return canonicalName + "[]"; + } + return null; + } + return clazz.getName().replace('$', '.'); + } + + + public static String capitalize(String s) { + if (s.length() == 0) { + return s; + } + return Character.toUpperCase(s.charAt(0)) + s.substring(1); + } + + + public String[] parseParameterList(String parameters) throws CompilerException { + List<String> result = new ArrayList<String>(); + StringBuffer current = new StringBuffer(); + int state = 0; // normal + for (int i = 0; i < parameters.length(); i++) { + char c = parameters.charAt(i); + switch (state) { + case 0: // normal + switch (c) { + case '"': + current.append(c); + state = 1; + break; // in quoted string + case '\\': + current.append(c); + state = 2; + break; // immediately after backslash + case ',': + if (current.length() > 0) { + result.add(current.toString()); + current.setLength(0); + break; + } else + reportError("error parsing parameter list: " + parameters); + default: + current.append(c); + } + break; + case 1: // in quoted string + switch (c) { + case '"': + current.append(c); + state = 0; + break; // normal + case '\\': + current.append(c); + state = 3; + break; // immediate after backslash in quoted string + default: + current.append(c); + } + break; + case 2: // immediately after backslash + current.append(c); + state = 0; // normal + break; + case 3: // immediately after backslash in quoted string + current.append(c); + state = 1; // in quoted string + break; + } + } + if (current.length() > 0) { + result.add(current.toString()); + } + return result.toArray(new String[result.size()]); + } + + + public void openComponent(CompiledObject component) throws CompilerException { + openComponent(component, null); + } + + + public void openComponent(CompiledObject component, String constraints) throws CompilerException { + CompiledObject parent = getOpenComponent(); + openInvisibleComponent(component); + if (parent != null && !component.isOverride()) { + parent.addChild(component, constraints, this); + } + } + + + public void openInvisibleComponent(CompiledObject component) { + if (!ids.containsKey(component)) { + registerCompiledObject(component); + } + openComponents.push(component); + } + + + public CompiledObject getOpenComponent() { + if (openComponents.isEmpty()) { + return null; + } + return openComponents.peek(); + } + + + public void closeComponent(CompiledObject component) { + if (openComponents.pop() != component) { + throw new IllegalArgumentException("can only close the topmost open object"); + } + } + + + public CompiledObject getRootObject() { + return root; + } + + + public void registerCompiledObject(CompiledObject object) { + assert symbolTables.values().contains(symbolTable) : "attempting to register CompiledObject before pass 1 is complete"; + if (root == null) { + root = object; + } + + String id = object.getId(); + if (ids.containsKey(object)) { + reportError("object '" + object + "' is already registered with id '" + ids.get(object) + "', cannot re-register as '" + id + "'"); + } + if (objects.containsKey(id) && !(objects.get(id) instanceof Element)) { + reportError("id '" + id + "' is already registered to component " + objects.get(id)); + } + objects.put(id, object); + ids.put(object, id); + } + + + public String getAutoId(ClassDescriptor objectClass) { + if (options.getOptimize()) { + return "$" + Integer.toString(autogenID++, 36); + } else { + String name = objectClass.getName(); + name = name.substring(name.lastIndexOf(".") + 1); + return "$" + name + autogenID++; + } + } + + + public String getUniqueId(Object object) { + String result = uniqueIds.get(object); + if (result == null) { + result = "$u" + uniqueIds.size(); + uniqueIds.put(object, result); + } + return result; + } + + + public SymbolTable getSymbolTable() { + return symbolTable; + } + + + public CompiledObject getCompiledObject(String id) { + runInitializers(); + assert symbolTables.values().contains(symbolTable) : "attempting to retrieve CompiledObject before pass 1 is complete"; + return objects.get(id); + } + + + private Matcher leftBraceMatcher = Pattern.compile("^(\\{)|[^\\\\](\\{)").matcher(""); + + private int getNextLeftBrace(String string, int pos) { + leftBraceMatcher.reset(string); + return leftBraceMatcher.find(pos) ? Math.max(leftBraceMatcher.start(1), leftBraceMatcher.start(2)) : -1; + } + + + private Matcher rightBraceMatcher = Pattern.compile("^(\\})|[^\\\\](\\})").matcher(""); + + private int getNextRightBrace(String string, int pos) { + leftBraceMatcher.reset(string); + rightBraceMatcher.reset(string); + int openCount = 1; + int rightPos; + while (openCount > 0) { + pos++; + int leftPos = leftBraceMatcher.find(pos) ? Math.max(leftBraceMatcher.start(1), leftBraceMatcher.start(2)) : -1; + rightPos = rightBraceMatcher.find(pos) ? Math.max(rightBraceMatcher.start(1), rightBraceMatcher.start(2)) : -1; + assert leftPos == -1 || leftPos >= pos; + assert rightPos == -1 || rightPos >= pos; + if (leftPos != -1 && leftPos < rightPos) { + pos = leftPos; + openCount++; + } else if (rightPos != -1) { + pos = rightPos; + openCount--; + } else + openCount = 0; + } + return pos; + } + + + /** + * Examine an attribute value for data binding expressions. Returns a 'cooked' expression which + * can be used to determine the resulting value. It is expected that this expression will be used + * as the source expression in a call to {@link #registerDataBinding}. + * If the attribute value does not invoke data binding, this method returns <code>null</code> + * + * @param stringValue the string value of the property from the XML + * @param type the type of the property, from the <code>JAXXPropertyDescriptor</code> + * @return a processed version of the expression + * @throws jaxx.CompilerException ? + */ + public String processDataBindings(String stringValue, ClassDescriptor type) throws CompilerException { + int pos = getNextLeftBrace(stringValue, 0); + if (pos != -1) { + StringBuffer expression = new StringBuffer(); + int lastPos = 0; + while (pos != -1 && pos < stringValue.length()) { + if (pos > lastPos) { + if (expression.length() > 0) { + expression.append(" + "); + } + expression.append('"'); + expression.append(JAXXCompiler.escapeJavaString(stringValue.substring(lastPos, pos))); + expression.append('"'); + } + + if (expression.length() > 0) { + expression.append(" + "); + } + expression.append('('); + int pos2 = getNextRightBrace(stringValue, pos + 1); + if (pos2 == -1) { + reportError("unmatched '{' in expression: " + stringValue); + return ""; + } + expression.append(stringValue.substring(pos + 1, pos2)); + expression.append(')'); + pos2++; + if (pos2 < stringValue.length()) { + pos = getNextLeftBrace(stringValue, pos2); + lastPos = pos2; + } else { + pos = stringValue.length(); + lastPos = pos; + } + } + if (lastPos < stringValue.length()) { + if (expression.length() > 0) { + expression.append(" + "); + } + expression.append('"'); + expression.append(JAXXCompiler.escapeJavaString(stringValue.substring(lastPos))); + expression.append('"'); + } + return type == ClassDescriptorLoader.getClassDescriptor(String.class) ? "String.valueOf(" + expression + ")" : expression.toString(); + } + return null; + } + + + public void registerDataBinding(String src, String dest, String assignment) { + try { + src = checkJavaCode(src); + dataBindings.add(new DataBinding(src, dest, assignment, this)); + } + catch (CompilerException e) { + reportError("While parsing data binding for '" + dest.substring(dest.lastIndexOf(".") + 1) + "': " + e.getMessage()); + } + } + + + public ScriptManager getScriptManager() { + return scriptManager; + } + + + /** + * Verifies that a snippet of Java code parses correctly. A warning is generated if the string has enclosing + * curly braces. + * + * @param javaCode the Java code snippet to test + * @return a "cooked" version of the string which has enclosing curly braces removed. + * @throws CompilerException if the code cannot be parsed + */ + public String checkJavaCode(String javaCode) { + javaCode = scriptManager.trimScript(javaCode); + scriptManager.checkParse(javaCode); + return javaCode; + } + + + public void registerEventHandler(EventHandler handler) { + String objectCode = handler.getObjectCode(); + Map<ClassDescriptor, List<EventHandler>> listeners = eventHandlers.get(objectCode); + if (listeners == null) { + listeners = new HashMap<ClassDescriptor, List<EventHandler>>(); + eventHandlers.put(objectCode, listeners); + } + ClassDescriptor listenerClass = handler.getListenerClass(); + List<EventHandler> handlerList = listeners.get(listenerClass); + if (handlerList == null) { + handlerList = new ArrayList<EventHandler>(); + listeners.put(listenerClass, handlerList); + } + handlerList.add(handler); + } + + + public FieldDescriptor[] getScriptFields() { + List<FieldDescriptor> scriptFields = symbolTable.getScriptFields(); + return scriptFields.toArray(new FieldDescriptor[scriptFields.size()]); + } + + + public void addScriptField(FieldDescriptor field) { + symbolTable.getScriptFields().add(field); + } + + + public MethodDescriptor[] getScriptMethods() { + List<MethodDescriptor> scriptMethods = symbolTable.getScriptMethods(); + return scriptMethods.toArray(new MethodDescriptor[scriptMethods.size()]); + } + + + public void addScriptMethod(MethodDescriptor method) { + if (method.getName().equals("main") && method.getParameterTypes().length == 1 && method.getParameterTypes()[0].getName().equals("[Ljava.lang.String;")) { + mainDeclared = true; + } + symbolTable.getScriptMethods().add(method); + } + + + public void registerScript(String script) throws CompilerException { + registerScript(script, null); + } + + + public void registerScript(String script, File sourceFile) throws CompilerException { + if (sourceFile != null) { + sourceFiles.push(sourceFile); + } + script = script.trim(); + if (!"".equals(script) && !script.endsWith("}") && !script.endsWith(";")) { + script += ";"; + } + scriptManager.registerScript(script); + + if (sourceFile != null) { + File pop = sourceFiles.pop(); + if (pop != sourceFile) { + throw new RuntimeException("leaving registerScript(), but " + sourceFile + " was not the top entry on the stack (found " + pop + " instead)"); + } + } + } + + + public String preprocessScript(String script) throws CompilerException { + return scriptManager.preprocessScript(script); + } + + + public void registerStylesheet(Stylesheet stylesheet) { + if (this.stylesheet == null) { + this.stylesheet = stylesheet; + } else { + this.stylesheet.add(stylesheet.getRules()); + } + } + + + public Stylesheet getStylesheet() { + Stylesheet merged = new Stylesheet(); + if (stylesheet != null) { + merged.add(stylesheet.getRules()); + } + merged.add(inlineStyles.toArray(new Rule[inlineStyles.size()])); + return merged; + } + + + public Stack<File> getSourceFiles() { + return sourceFiles; + } + + + public void addInlineStyle(CompiledObject object, String propertyName, boolean dataBinding) { + inlineStyles.add(Rule.inlineAttribute(object, propertyName, dataBinding)); + } + + + public void reportWarning(String warning) { + Element currentTag = null; + if (!tagsBeingCompiled.isEmpty()) { + currentTag = tagsBeingCompiled.peek(); + } + reportWarning(currentTag, warning, 0); + } + + + public void reportWarning(Element tag, String warning, int lineOffset) { + String lineNumber = null; + if (tag != null) { + String lineAttr = tag.getAttributeNS(JAXX_INTERNAL_NAMESPACE, "line"); + if (lineAttr.length() > 0) { + lineNumber = lineAttr; + } + } + File src = sourceFiles.peek(); + try { + src = src.getCanonicalFile(); + } + catch (IOException e) { + // ignore ? + } + + System.err.print(src); + if (lineNumber != null) { + System.err.print(":" + ((sourceFiles.size() == 1) ? Integer.parseInt(lineNumber) + lineOffset : lineOffset + 1)); + } + System.err.println(": Warning: " + warning); + warningCount++; + } + + + public void reportError(String error) { + Element currentTag = null; + if (!tagsBeingCompiled.isEmpty()) { + currentTag = tagsBeingCompiled.peek(); + } + reportError(currentTag, error); + } + + + public void reportError(CompilerException ex) { + reportError(null, ex); + } + + + public void reportError(String extraMessage, CompilerException ex) { + String message = ex.getMessage(); + if (ex.getClass() == UnsupportedAttributeException.class || ex.getClass() == UnsupportedTagException.class) { + message = ex.getClass().getName().substring(ex.getClass().getName().lastIndexOf(".") + 1) + ": " + message; + } + int lineOffset; + if (ex instanceof ParseException) { + lineOffset = Math.max(0, ((ParseException) ex).getLine() - 1); + } else { + lineOffset = 0; + } + Element currentTag = null; + if (!tagsBeingCompiled.isEmpty()) { + currentTag = tagsBeingCompiled.peek(); + } + reportError(currentTag, extraMessage != null ? extraMessage + message : message, lineOffset); + } + + + public void reportError(Element tag, String error) { + reportError(tag, error, 0); + } + + + public void reportError(Element tag, String error, int lineOffset) { + int lineNumber = 0; + if (tag != null) { + String lineAttr = tag.getAttributeNS(JAXX_INTERNAL_NAMESPACE, "line"); + if (lineAttr.length() > 0) { + lineNumber = Integer.parseInt(lineAttr); + } + } + lineNumber = Math.max(lineNumber, 1) + lineOffset; + reportError(lineNumber, error); + } + + + public void reportError(int lineNumber, String error) { + File src = sourceFiles.isEmpty() ? null : sourceFiles.peek(); + try { + if (src != null) { + src = src.getCanonicalFile(); + } + } + catch (IOException e) { + // ignore ? + } + + System.err.print(src != null ? src.getPath() : "<unknown source>"); + if (lineNumber > 0) { + System.err.print(":" + lineNumber); + } + System.err.println(": " + error); + errorCount++; + failed = true; + } + + + /** + * Escapes a string using standard Java escape sequences, generally in preparation to including it in a string literal + * in a compiled Java file. + * + * @param raw the raw string to be escape + * @return a string in which all 'dangerous' characters have been replaced by equivalent Java escape sequences + */ + public static String escapeJavaString(String raw) { + StringBuffer out = new StringBuffer(raw); + for (int i = 0; i < out.length(); i++) { + char c = out.charAt(i); + if (c == '\\' || c == '"') { + out.insert(i, '\\'); + i++; + } else if (c == '\n') { + out.replace(i, i + 1, "\\n"); + i++; + } else if (c == '\r') { + out.replace(i, i + 1, "\\r"); + i++; + } else if (c < 32 || c > 127) { + String value = Integer.toString((int) c, 16); + while (value.length() < 4) { + value = "0" + value; + } + out.replace(i, i + 1, "\\u" + value); + i += 5; + } + } + return out.toString(); + } + + + /** + * Returns the system line separator string. + * + * @return the string used to separate lines + */ + public static String getLineSeparator() { + return System.getProperty("line.separator", "\n"); + } + + + /** + * Returns a <code>ClassLoader</code> which searches the user-specified class path in addition + * to the normal system class path. + * + * @return <code>ClassLoader</code> to use while resolving class references + */ + public ClassLoader getClassLoader() { + if (classLoader == null) { + String classPath = options.getClassPath(); + if (classPath == null) { + classPath = "."; + } + String[] paths = classPath.split(File.pathSeparator); + URL[] urls = new URL[paths.length]; + for (int i = 0; i < paths.length; i++) { + try { + urls[i] = new File(paths[i]).toURI().toURL(); + } + catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + classLoader = new URLClassLoader(urls, getClass().getClassLoader()); + } + + return classLoader; + } + + + /** + * @param className the name of the class to use + * @return the compiler instance which is processing the specified JAXX class. Each class is compiled by a + * different compiler instance. + */ + public static JAXXCompiler getJAXXCompiler(String className) { + return compilers != null ? compilers.get(className) : null; + } + + + /** + * @param className the name of the class to use + * @return the symbol table for the specified JAXX class. Must be called during the second compiler pass. + * Returns <code>null</code> if no such symbol table could be found. + */ + public static SymbolTable getSymbolTable(String className) { + JAXXCompiler compiler = getJAXXCompiler(className); + if (compiler == null) { + return null; + } + return compiler.getSymbolTable(); + } + + public static File URLtoFile(URL url) { + return URLtoFile(url.toString()); + } + + public static File URLtoFile(String urlString) { + if (!urlString.startsWith("file:")) { + throw new IllegalArgumentException("url must start with 'file:'"); + } + urlString = urlString.substring("file:".length()); + if (urlString.startsWith("/") && System.getProperty("os.name").startsWith("Windows")) { + urlString = urlString.substring(1); + } + try { + return new File(URLDecoder.decode(urlString.replace('/', File.separatorChar), "utf-8")); + } + catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + public void addDependencyClass(String className) { + if (!jaxxFileClassNames.contains(className)) { + URL jaxxURL = getClassLoader().getResource(className.replace('.', '/') + ".jaxx"); + URL classURL = getClassLoader().getResource(className.replace('.', '/') + ".class"); + if (jaxxURL != null && classURL != null) { + try { + File jaxxFile = URLtoFile(jaxxURL); + File classFile = URLtoFile(classURL); + if (classFile.lastModified() > jaxxFile.lastModified()) { + return; // class file is newer, no need to recompile + } + } + catch (Exception e) { + // do nothing + } + } + + if (jaxxURL != null && jaxxURL.toString().startsWith("file:")) { + File jaxxFile = URLtoFile(jaxxURL); + try { + jaxxFile = jaxxFile.getCanonicalFile(); + } + catch (IOException ex) { + // ignore ? + } + assert jaxxFile.getName().equalsIgnoreCase(className.substring(className.lastIndexOf(".") + 1) + ".jaxx") : + "expecting file name to match " + className + ", but found " + jaxxFile.getName(); + if (jaxxFile.getName().equals(className.substring(className.lastIndexOf(".") + 1) + ".jaxx")) { // check case match + if (currentPass == PASS_2) { + throw new AssertionError("Internal error: adding dependency class " + className + " during second compilation pass"); + } + jaxxFileClassNames.add(className); + jaxxFiles.add(jaxxFile); + } + } + } + } + + + /** + * Compiled a set of files, expressed as paths relative to a base directory. The class names of the compiled files are derived + * from the relative path strings (e.g. "example/Foo.jaxx" compiles into a class named "example.Foo"). Returns <code>true</code> + * if compilation succeeds, <code>false</code> if it fails. Warning and error messages are sent to <code>System.err</code>. + * + * @param base the directory against which to resolve relative paths + * @param relativePaths a list of relative paths to .jaxx files being compiled + * @param options the compiler options to use + * @return <code>true</code> if compilation succeeds, <code>false</code> otherwise + */ + public static synchronized boolean compile(File base, String[] relativePaths, CompilerOptions options) { + File[] files = new File[relativePaths.length]; + String[] classNames = new String[relativePaths.length]; + for (int i = 0; i < files.length; i++) { + files[i] = new File(base, relativePaths[i]); + classNames[i] = relativePaths[i].substring(0, relativePaths[i].lastIndexOf(".")); + classNames[i] = classNames[i].replace(File.separatorChar, '.'); + classNames[i] = classNames[i].replace('/', '.'); + classNames[i] = classNames[i].replace('\\', '.'); + classNames[i] = classNames[i].replace(':', '.'); + } + return compile(files, classNames, options); + } + + + /** Resets all state in preparation for a new compilation session. */ + private static void reset() { + errorCount = 0; + warningCount = 0; + jaxxFiles.clear(); + jaxxFileClassNames.clear(); + symbolTables.clear(); + compilers.clear(); + } + + + /** + * Compiled a set of files, with the class names specified explicitly. The class compiled from files[i] will be named classNames[i]. + * Returns <code>true</code> if compilation succeeds, <code>false</code> if it fails. Warning and error messages are sent to + * <code>System.err</code>. + * + * @param files the .jaxx files to compile + * @param classNames the names of the classes being compiled + * @param options the compiler options to use + * @return <code>true</code> if compilation succeeds, <code>false</code> otherwise + */ + public static synchronized boolean compile(File[] files, String[] classNames, CompilerOptions options) { + reset(); // just to be safe... + jaxxFiles.addAll(Arrays.asList(files)); + jaxxFileClassNames.addAll(Arrays.asList(classNames)); + try { + boolean success = true; + + // pass 1 + currentPass = PASS_1; + boolean compiled; + do { + compiled = false; + assert jaxxFiles.size() == jaxxFileClassNames.size(); + java.util.Iterator<File> filesIterator = new ArrayList<File>(jaxxFiles).iterator(); // clone it so it can safely be modified while we're iterating + java.util.Iterator<String> classNamesIterator = new ArrayList<String>(jaxxFileClassNames).iterator(); + while (filesIterator.hasNext()) { + File file = filesIterator.next(); + String className = classNamesIterator.next(); + if (options.isVerbose()) { + log.info("compile first pass for " + className); + } + if (symbolTables.get(file) == null) { + compiled = true; + if (compilers.containsKey(className)) { + throw new CompilerException("Internal error: " + className + " is already being compiled, attempting to compile it again"); + } + + File destDir = options.getTargetDirectory(); + if (destDir != null) { + int dotPos = className.lastIndexOf("."); + if (dotPos != -1) { + destDir = new File(destDir, className.substring(0, dotPos).replace('.', File.separatorChar)); + } + destDir.mkdirs(); + } else { + //destDir = file.getParentFile(); + } + JAXXCompiler compiler = new JAXXCompiler(file.getParentFile(), file, className, options); + compilers.put(className, compiler); + compiler.compileFirstPass(); + assert !symbolTables.values().contains(compiler.getSymbolTable()) : "symbolTable is already registered"; + symbolTables.put(file, compiler.getSymbolTable()); + if (compiler.failed) { + success = false; + } + } + } + + } while (compiled); + + // pass 2 + currentPass = PASS_2; + if (success) { + assert jaxxFiles.size() == jaxxFileClassNames.size(); + List<File> jaxxFilesClone = new ArrayList<File>(jaxxFiles); + for (String className : jaxxFileClassNames) { + JAXXCompiler compiler = compilers.get(className); + if (compiler == null) + throw new CompilerException("Internal error: could not find compiler for " + className + " during second pass"); + if (options.isVerbose()) { + + log.info("runInitializers for " + className); + } + if (!compiler.failed) { + compiler.runInitializers(); + } + if (options.isVerbose()) { + + log.info("compile second pass for " + className); + } + compiler.compileSecondPass(); + if (options.isVerbose()) { + + log.info("done with result [" + !compiler.failed + "] for " + className); + } + if (!compiler.failed) { + + } else { + success = false; + } + } + if (!jaxxFilesClone.equals(jaxxFiles)) { + throw new AssertionError("Internal error: compilation set altered during pass 2 (was " + jaxxFilesClone + ", modified to " + jaxxFiles + ")"); + } + } + + // stylesheet application + if (success) { + assert jaxxFiles.size() == jaxxFileClassNames.size(); + for (String className : jaxxFileClassNames) { + JAXXCompiler compiler = compilers.get(className); + if (compiler == null) { + throw new CompilerException("Internal error: could not find compiler for " + className + " during stylesheet application"); + } + compiler.applyStylesheets(); + if (compiler.failed) { + success = false; + } + } + } + + // code generation + if (success) { + assert jaxxFiles.size() == jaxxFileClassNames.size(); + for (String className : jaxxFileClassNames) { + JAXXCompiler compiler = compilers.get(className); + if (compiler == null) { + throw new CompilerException("Internal error: could not find compiler for " + className + " during code generation"); + } + compiler.generateCode(); + if (compiler.failed) { + success = false; + } + } + } + + if (warningCount == 1) { + System.err.println("1 warning"); + } else if (warningCount > 0) { + System.err.println(warningCount + " warnings"); + } + if (errorCount == 1) { + System.err.println("1 error"); + } else if (errorCount > 0) { + System.err.println(errorCount + " errors"); + } + return success; + } + catch (CompilerException e) { + System.err.println(e.getMessage()); + e.printStackTrace(); + return false; + } + catch (Throwable e) { + e.printStackTrace(); + return false; + } + finally { + reset(); + } + } + + + private static void showUsage() { + System.out.println("Usage: jaxxc <options> <source files>"); + System.out.println(); + System.out.println("Source files must end in extension .jaxx"); + System.out.println("Use JAXX_OPTS environment variable to pass arguments to Java runtime"); + System.out.println(); + System.out.println("Supported options include:"); + System.out.println(" -classpath <paths> paths to search for user classes"); + System.out.println(" -cp <paths> same as -classpath"); + System.out.println(" -d <directory> target directory for generated class files"); + System.out.println(" -java or -j produce .java files, but do not compile them"); + System.out.println(" -keep or -k preserve generated .java files after compilation"); + System.out.println(" -optimize or -o optimize during compilation"); + System.out.println(" -version display version information"); + System.out.println(); + System.out.println("See http://www.jaxxframework.org/ for full documentation."); + } + + + public static String getVersion() { + return "1.0.4"; + } + + + public static void main(String[] arg) throws Exception { + boolean success = true; + + CompilerOptions options = new CompilerOptions(); + List<String> files = new ArrayList<String>(); + for (int i = 0; i < arg.length; i++) { + if (arg[i].endsWith(".jaxx")) { + files.add(arg[i]); + } else if (arg[i].equals("-d")) { + if (++i < arg.length) { + File targetDirectory = new File(arg[i]); + if (!targetDirectory.exists()) { + System.err.println("Error: could not find target directory: " + targetDirectory); + errorCount++; + success = false; + } + options.setTargetDirectory(targetDirectory); + } else { + success = false; + } + } else if (arg[i].equals("-cp") || arg[i].equals("-classpath")) { + if (++i < arg.length) { + options.setClassPath(arg[i]); + } else { + success = false; + } + } else if (arg[i].equals("-javac_opts")) { + if (++i < arg.length) { + options.setJavacOpts(arg[i]); + } else { + success = false; + } + } else if (arg[i].equals("-k") || arg[i].equals("-keep")) + options.setKeepJavaFiles(true); + else if (arg[i].equals("-j") || arg[i].equals("-java")) { + options.setKeepJavaFiles(true); + } else if (arg[i].equals("-o") || arg[i].equals("-optimize")) + options.setOptimize(true); + else if (arg[i].equals("-version")) { + System.err.println("jaxxc version " + getVersion() + " by Ethan Nicholas"); + System.err.println("http://www.jaxxframework.org/"); + System.exit(0); + } else if (arg[i].equals("-internalDumpVersion")) { // used by ant to extract the version info + System.out.println("jaxx.version=" + getVersion()); + return; + } else { + success = false; + } + } + + success &= (errorCount == 0 && files.size() > 0); + + if (success) { + success = compile(new File("."), files.toArray(new String[files.size()]), options); + } else { + showUsage(); + System.exit(1); + } + + System.exit(success ? 0 : 1); + } + + public File getDest() { + return dest; + } + + public void setFailed(boolean failed) { + this.failed = failed; + } +} Copied: lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/DataBindingListener.java (from rev 862, lutinjaxx/trunk/core/src/main/java/jaxx/runtime/DataBindingListener.java) =================================================================== --- lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/DataBindingListener.java (rev 0) +++ lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/DataBindingListener.java 2008-10-02 12:50:25 UTC (rev 865) @@ -0,0 +1,46 @@ +/* + * Copyright 2006 Ethan Nicholas. All rights reserved. + * Use is subject to license terms. + */ +package jaxx.runtime; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + + +/** + * A <code>PropertyChangeListener</code> which processes a data binding when it receives a + * <code>PropertyChangeEvent</code>. + */ +public class DataBindingListener implements PropertyChangeListener { + private JAXXObject object; + private String dest; + + /** + * Creates a new <code>DataBindingListener</code> which will run the given data binding + * when it receives a <code>PropertyChangeEvent</code>. + * + * @param object the object in which the data binding exists + * @param dest the name of the data binding to run + */ + public DataBindingListener(JAXXObject object, String dest) { + this.object = object; + this.dest = dest; + } + + + /** + * Processes the data binding in response to a <code>PropertyChangeEvent</code>. + * + * @param e the event which triggered the binding + */ + public void propertyChange(PropertyChangeEvent e) { + object.processDataBinding(dest); + + // for now, handle dependency changes by always removing & reapplying + // the binding. We should be more efficient and only do this when it's + // actually necessary + object.removeDataBinding(dest); + object.applyDataBinding(dest); + } +} \ No newline at end of file Copied: lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/DataBindingUpdateListener.java (from rev 862, lutinjaxx/trunk/core/src/main/java/jaxx/runtime/DataBindingUpdateListener.java) =================================================================== --- lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/DataBindingUpdateListener.java (rev 0) +++ lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/DataBindingUpdateListener.java 2008-10-02 12:50:25 UTC (rev 865) @@ -0,0 +1,46 @@ +/* + * Copyright 2006 Ethan Nicholas. All rights reserved. + * Use is subject to license terms. + */ +package jaxx.runtime; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + + +/** + * A <code>PropertyChangeListener</code> which removes and re-applies a data binding + * when it receives a <code>PropertyChangeEvent</code>. + */ +public class DataBindingUpdateListener implements PropertyChangeListener { + private JAXXObject object; + private String dest; + + /** + * Creates a new <code>DataBindingUpdateListener</code> which will remove and re-apply a + * data binding when it receives a <code>PropertyChangeEvent</code>. + * + * @param object the object in which the data binding exists + * @param dest the name of the data binding to reapply + */ + public DataBindingUpdateListener(JAXXObject object, String dest) { + this.object = object; + this.dest = dest; + } + + + public String getBindingName() { + return dest; + } + + + /** + * Updates the data binding in response to a <code>PropertyChangeEvent</code>. + * + * @param e the event which triggered the binding + */ + public void propertyChange(PropertyChangeEvent e) { + object.removeDataBinding(dest); + object.applyDataBinding(dest); + } +} \ No newline at end of file Copied: lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/JAXXContext.java (from rev 858, lutinjaxx/trunk/core/src/main/java/jaxx/runtime/JAXXContext.java) =================================================================== --- lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/JAXXContext.java (rev 0) +++ lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/JAXXContext.java 2008-10-02 12:50:25 UTC (rev 865) @@ -0,0 +1,65 @@ +package jaxx.runtime; + +import java.awt.Container; + +/** + * todo (make javadoc :) ) + * + * @author letellier + */ +interface JAXXContext { + + /** + * Map actions objects used by JAXX interface. + * + * @param o todo + */ + public void setContextValue(Object o); + + /** + * Map actions objects used by JAXX interface and their names. + * + * @param o todo + * @param name todo + */ + public void setContextValue(Object o, String name); + + /** + * Return action object used by JAXX interface. + * This is an exemple to call a method in JAXX : + * + * <code> + * <JButton onActionPerformed='{getContextValue(Action.class).method(args[])}'/> + * </code> + * + * @param clazz todo + * @return todo + */ + public <T> T getContextValue(Class<T> clazz); + + /** + * Return action object used by JAXX interface. + * + * @param clazz todo + * @param name todo + * @return todo + */ + public <T> T getContextValue(Class<T> clazz, String name); + + /** + * Return parent's container corresponding to the Class clazz + * + * @param clazz clazz desired + * @return parent's container + */ + public <O extends Container> O getParentContainer(Class<O> clazz); + + /** + * Return parent's container corresponding to the Class clazz + * + * @param top todo + * @param clazz desired + * @return parent's container + */ + public <O extends Container> O getParentContainer(Container top, Class<O> clazz); +} Deleted: lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/JAXXObject.java =================================================================== --- lutinjaxx/trunk/core/src/main/java/jaxx/runtime/JAXXObject.java 2008-09-24 19:58:26 UTC (rev 857) +++ lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/JAXXObject.java 2008-10-02 12:50:25 UTC (rev 865) @@ -1,50 +0,0 @@ -/* - * Copyright 2006 Ethan Nicholas. All rights reserved. - * Use is subject to license terms. - */ -package jaxx.runtime; - -/** The <code>JAXXObject</code> interface is implemented by all classes produced by the JAXX compiler. */ -public interface JAXXObject { - /** - * Retrieves an object defined in an XML tag by its ID. - * - * @param id the id of the component to retrieve - * @return the object - */ - public Object getObjectById(String id); - - /** - * Pretrieves the dictonary of knwon objects indexed by their ids. - * @return the dictonary of objects. - */ - public java.util.Map<String,Object> get$objectMap(); - - public void applyDataBinding(String id); - - - public void removeDataBinding(String id); - - - /** - * Processes a data binding by name. Data binding names are comprised of an object ID and a property name: - * for example, the data binding in the tag <code><JLabel id='label' text='{foo.getText()}'/></code> is - * named <code>"label.text"</code>. Processing a data binding causes it to reevaluate its expression, in this - * case <code>foo.getText()</code>. - * - * @param dest the name of the data binding to run - */ - public void processDataBinding(String dest); - - - /** - * All <code>JAXXObject</code> implements are capable of broadcasting <code>PropertyChangeEvent</code>, and - * furthermore (for technical reasons) must allow code in outside packages, specifically the JAXX runtime, - * to trigger these events. - * - * @param name the name of the property which changed - * @param oldValue the old value of the property - * @param newValue the new value of the property - */ - public void firePropertyChange(String name, Object oldValue, Object newValue); -} \ No newline at end of file Copied: lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/JAXXObject.java (from rev 858, lutinjaxx/trunk/core/src/main/java/jaxx/runtime/JAXXObject.java) =================================================================== --- lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/JAXXObject.java (rev 0) +++ lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/JAXXObject.java 2008-10-02 12:50:25 UTC (rev 865) @@ -0,0 +1,50 @@ +/* + * Copyright 2006 Ethan Nicholas. All rights reserved. + * Use is subject to license terms. + */ +package jaxx.runtime; + +/** The <code>JAXXObject</code> interface is implemented by all classes produced by the JAXX compiler. */ +public interface JAXXObject extends JAXXContext{ + /** + * Retrieves an object defined in an XML tag by its ID. + * + * @param id the id of the component to retrieve + * @return the object + */ + public Object getObjectById(String id); + + /** + * Pretrieves the dictonary of knwon objects indexed by their ids. + * @return the dictonary of objects. + */ + public java.util.Map<String,Object> get$objectMap(); + + public void applyDataBinding(String id); + + + public void removeDataBinding(String id); + + + /** + * Processes a data binding by name. Data binding names are comprised of an object ID and a property name: + * for example, the data binding in the tag <code><JLabel id='label' text='{foo.getText()}'/></code> is + * named <code>"label.text"</code>. Processing a data binding causes it to reevaluate its expression, in this + * case <code>foo.getText()</code>. + * + * @param dest the name of the data binding to run + */ + public void processDataBinding(String dest); + + + /** + * All <code>JAXXObject</code> implements are capable of broadcasting <code>PropertyChangeEvent</code>, and + * furthermore (for technical reasons) must allow code in outside packages, specifically the JAXX runtime, + * to trigger these events. + * + * @param name the name of the property which changed + * @param oldValue the old value of the property + * @param newValue the new value of the property + */ + public void firePropertyChange(String name, Object oldValue, Object newValue); +} \ No newline at end of file Copied: lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/Util.java (from rev 862, lutinjaxx/trunk/core/src/main/java/jaxx/runtime/Util.java) =================================================================== --- lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/Util.java (rev 0) +++ lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/Util.java 2008-10-02 12:50:25 UTC (rev 865) @@ -0,0 +1,253 @@ +package jaxx.runtime; + +import javax.swing.JComponent; +import java.awt.Component; +import java.awt.Dimension; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.lang.ref.WeakReference; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.EventListener; +import java.util.zip.GZIPInputStream; + +public class Util { + // Maps root objects to lists of event listeners + private static Map<Object, WeakReference<List<EventListenerDescriptor>>> eventListeners = new WeakHashMap<Object, WeakReference<List<EventListenerDescriptor>>>(); + private static Map<JAXXObject, WeakReference<List<DataBindingUpdateListener>>> dataBindingUpdateListeners = new WeakHashMap<JAXXObject, WeakReference<List<DataBindingUpdateListener>>>(); + + + private static class EventListenerDescriptor { + Class listenerClass; + String listenerMethodName; + String methodName; + Object eventListener; + } + + + /** + * Decodes the serialized representation of a JAXXObjectDescriptor. The string must be a byte-to-character mapping + * of the binary serialization data for a JAXXObjectDescriptor. See the comments in JAXXCompiler.createJAXXObjectDescriptorField + * for the rationale behind this (admittedly ugly) approach. + * + * @param descriptor descriptor to decode + * @return the dedoced descriptor + */ + public static JAXXObjectDescriptor decodeJAXXObjectDescriptor(String descriptor) { + try { + byte[] data = new byte[descriptor.length()]; + // copy low-order bytes into the array. The high-order bytes should all be zero. + System.arraycopy(descriptor.getBytes(),0,data,0,data.length); + //descriptor.getBytes(0, descriptor.length(), data, 0); + ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(data)); + return (JAXXObjectDescriptor) in.readObject(); + } + catch (IOException e) { + throw new RuntimeException("Internal error: can't-happen error", e); + } + catch (ClassNotFoundException e) { + throw new RuntimeException("Internal error: can't-happen error", e); + } + } + + + public static JAXXObjectDescriptor decodeCompressedJAXXObjectDescriptor(String descriptor) { + try { + byte[] data = new byte[descriptor.length()]; + // copy low-order bytes into the array. The high-order bytes should all be zero. + System.arraycopy(descriptor.getBytes(),0,data,0,data.length); + //descriptor.getBytes(0, descriptor.length(), data, 0); + ObjectInputStream in = new ObjectInputStream(new GZIPInputStream(new ByteArrayInputStream(data))); + return (JAXXObjectDescriptor) in.readObject(); + } + catch (IOException e) { + throw new RuntimeException("Internal error: can't-happen error", e); + } + catch (ClassNotFoundException e) { + throw new RuntimeException("Internal error: can't-happen error", e); + } + } + + + public static Object getEventListener(Class<? extends EventListener> listenerClass, final String listenerMethodName, final Object methodContainer, final String methodName) { + WeakReference<List<EventListenerDescriptor>> ref = eventListeners.get(methodContainer); + List<EventListenerDescriptor> descriptors = ref != null ? ref.get() : null; + if (descriptors == null) { + descriptors = new ArrayList<EventListenerDescriptor>(); + eventListeners.put(methodContainer, new WeakReference<List<EventListenerDescriptor>>(descriptors)); + } else { + for (EventListenerDescriptor descriptor : descriptors) { + if (listenerClass == descriptor.listenerClass && + (listenerMethodName == null ? descriptor.listenerMethodName == null : listenerMethodName.equals(descriptor.listenerMethodName)) && + methodName.equals(descriptor.methodName)) { + return descriptor.eventListener; + } + } + } + + // else we need to create a new listener + final EventListenerDescriptor descriptor = new EventListenerDescriptor(); + descriptor.listenerClass = listenerClass; + descriptor.listenerMethodName = listenerMethodName; + descriptor.methodName = methodName; + try { + final List<Method> listenerMethods = Arrays.asList(listenerClass.getMethods()); + Method listenerMethod = null; + if (listenerMethodName != null) { + for (Method listenerMethod1 : listenerMethods) { + if ((listenerMethod1).getName().equals(listenerMethodName)) { + listenerMethod = listenerMethod1; + break; + } + } + } + if (listenerMethodName != null && listenerMethod == null) + throw new IllegalArgumentException("no method named " + listenerMethodName + " found in class " + listenerClass.getName()); + Class[] parameterTypes = listenerMethods.get(0).getParameterTypes(); + Class<?> methodContainerClass = methodContainer.getClass(); + final Method targetMethod = methodContainerClass.getMethod(methodName, parameterTypes); + descriptor.eventListener = Proxy.newProxyInstance(listenerClass.getClassLoader(), + new Class[]{listenerClass}, + new InvocationHandler() { + public Object invoke(Object proxy, Method method, Object[] args) { + String methodName = method.getName(); + if ((listenerMethodName == null && listenerMethods.contains(method)) || methodName.equals(listenerMethodName)) { + try { + return targetMethod.invoke(methodContainer, args); + } + catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + } + if (methodName.equals("toString")) { + return toString(); + } + if (methodName.equals("equals")) { + return descriptor.eventListener == args[0]; + } + if (methodName.equals("hashCode")) { + return hashCode(); + } + return null; + } + }); + descriptors.add(descriptor); + return descriptor.eventListener; + } + catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + + public static Object getEventListener(Class<? extends EventListener> listenerClass, final Object methodContainer, final String methodName) { + return getEventListener(listenerClass, null, methodContainer, methodName); + } + + + public static DataBindingUpdateListener getDataBindingUpdateListener(JAXXObject object, String bindingName) { + WeakReference<List<DataBindingUpdateListener>> ref = dataBindingUpdateListeners.get(object); + List<DataBindingUpdateListener> listeners = ref == null ? null : ref.get(); + if (listeners == null) { + listeners = new ArrayList<DataBindingUpdateListener>(); + dataBindingUpdateListeners.put(object, new WeakReference<List<DataBindingUpdateListener>>(listeners)); + } else { + for (DataBindingUpdateListener listener : listeners) { + if (bindingName.equals(listener.getBindingName())) + return listener; + } + } + DataBindingUpdateListener listener = new DataBindingUpdateListener(object, bindingName); + listeners.add(listener); + return listener; + } + + + public static void setComponentWidth(Component component, int width) { + component.setSize(width, component.getHeight()); + if (component instanceof JComponent) { + JComponent jcomponent = (JComponent) component; + jcomponent.setPreferredSize(new Dimension(width, jcomponent.getPreferredSize().height)); + jcomponent.setMinimumSize(new Dimension(width, jcomponent.getPreferredSize().height)); + if (jcomponent.isDisplayable()) + jcomponent.revalidate(); + } + } + + + public static void setComponentHeight(Component component, int height) { + component.setSize(component.getWidth(), height); + if (component instanceof JComponent) { + JComponent jcomponent = (JComponent) component; + jcomponent.setPreferredSize(new Dimension(jcomponent.getPreferredSize().width, height)); + jcomponent.setMinimumSize(new Dimension(jcomponent.getPreferredSize().width, height)); + if (jcomponent.isDisplayable()) + jcomponent.revalidate(); + } + } + + + public static boolean assignment(boolean value, String name, JAXXObject src) { + src.firePropertyChange(name.trim(), null, "dummy value"); + return value; + } + + + public static byte assignment(byte value, String name, JAXXObject src) { + src.firePropertyChange(name.trim(), null, "dummy value"); + return value; + } + + + public static short assignment(short value, String name, JAXXObject src) { + src.firePropertyChange(name.trim(), null, "dummy value"); + return value; + } + + + public static int assignment(int value, String name, JAXXObject src) { + src.firePropertyChange(name.trim(), null, "dummy value"); + return value; + } + + + public static long assignment(long value, String name, JAXXObject src) { + src.firePropertyChange(name.trim(), null, "dummy value"); + return value; + } + + + public static float assignment(float value, String name, JAXXObject src) { + src.firePropertyChange(name.trim(), null, "dummy value"); + return value; + } + + + public static double assignment(double value, String name, JAXXObject src) { + src.firePropertyChange(name.trim(), null, "dummy value"); + return value; + } + + + public static char assignment(char value, String name, JAXXObject src) { + src.firePropertyChange(name.trim(), null, "dummy value"); + return value; + } + + + public static java.lang.Object assignment(java.lang.Object value, String name, JAXXObject src) { + src.firePropertyChange(name.trim(), null, "dummy value"); + return value; + } +} Copied: lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/css (from rev 862, lutinjaxx/trunk/core/src/main/java/jaxx/runtime/css) Copied: lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/Item.java (from rev 862, lutinjaxx/trunk/core/src/main/java/jaxx/runtime/swing/Item.java) =================================================================== --- lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/Item.java (rev 0) +++ lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/Item.java 2008-10-02 12:50:25 UTC (rev 865) @@ -0,0 +1,192 @@ +/* + * Copyright 2006 Ethan Nicholas. All rights reserved. + * Use is subject to license terms. + */ +package jaxx.runtime.swing; + +import javax.swing.event.SwingPropertyChangeSupport; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.util.ArrayList; +import java.util.List; + +// This needs to be split into two classes, Item and TreeItem +/** + * An item in a component such as <code>JComboBox</code> or <code>JTree</code>. The <code>Item</code> + * class corresponds to the <code><item></code> tag in JAXX source files. + */ +public class Item { + public static final String LABEL_PROPERTY = "label"; + public static final String VALUE_PROPERTY = "value"; + public static final String SELECTED_PROPERTY = "selected"; + + private String id; + private String label; + private Object value; + private boolean selected; + private List<Item> children; + private Item parent; + private PropertyChangeSupport propertyChangeSupport; + + /** + * Creates a new Item. This should only be called from compiled JAXX files. + * + * @param id the item's ID + * @param label the string that should be used to represent the item visually + * @param value the item's actual value + * @param selected <code>true</code> if the item should be selected by default + */ + public Item(String id, String label, Object value, boolean selected) { + this.id = id; + this.label = label; + this.value = value; + this.selected = selected; + } + + /** + * Returns this item's ID. + * + * @return the JAXX ID attribute + */ + public String getId() { + return id; + } + + /** + * Returns the string that should be used to represent the item at display time. If <code>null</code>, + * <code>String.valueOf(getValue())</code> will be used instead. + * + * @return this item's display string + * @see #setLabel + */ + public String getLabel() { + return label; + } + + /** + * Sets the item's display string. If <code>null, String.valueOf(getValue())</code> will be used instead. + * + * @param label the new display string + * @see #getLabel + */ + public void setLabel(String label) { + String oldLabel = this.label; + this.label = label; + firePropertyChange(LABEL_PROPERTY, oldLabel, label); + } + + /** + * Returns the item's actual value as it appears in the component's model. The <code>Item</code> itself is not + * visible from the model, only the value. + * + * @return the item's value + * @see #setValue + */ + public Object getValue() { + return value; + } + + /** + * Sets the item's value as it appears in the component's model. The <code>Item</code> itself is not + * visible from the model, only the value. + * + * @param value the new value + * @see #getValue + */ + public void setValue(Object value) { + Object oldValue = this.value; + this.value = value; + firePropertyChange(VALUE_PROPERTY, oldValue, value); + } + + /** + * Returns <code>true</code> if this item is currently selected. This is a bound property. + * + * @return <code>true</code> if item is selected + * @see #setSelected + */ + public boolean isSelected() { + return selected; + } + + /** + * Sets the item's selection state. This is a bound property. + * + * @param selected the new selection state + * @see #isSelected + */ + public void setSelected(boolean selected) { + boolean oldSelected = this.selected; + this.selected = selected; + firePropertyChange(SELECTED_PROPERTY, oldSelected, selected); + } + + /** + * Adds a new child node (Items can be nested in trees). + * + * @param item the new child item + */ + public void addChild(Item item) { + if (children == null) { + children = new ArrayList<Item>(); + } + children.add(item); + item.parent = this; + } + + /** + * Returns a list of this item's children. + * + * @return a list of all nested child nodes + */ + public List<Item> getChildren() { + if (children == null) { + children = new ArrayList<Item>(); + } + return children; + } + + /** + * Returns the <code>Item</code> containing this <code>Item</code>, or <code>null</code> for a top-level + * <code>Item</code>. + * + * @return the item parent (or null) + */ + public Item getParent() { + return parent; + } + + private PropertyChangeSupport getPropertyChangeSupport() { + if (propertyChangeSupport == null) { + propertyChangeSupport = new SwingPropertyChangeSupport(this); + } + return propertyChangeSupport; + } + + public void addPropertyChangeListener(PropertyChangeListener listener) { + getPropertyChangeSupport().addPropertyChangeListener(listener); + } + + public void addPropertyChangeListener(String property, PropertyChangeListener listener) { + getPropertyChangeSupport().addPropertyChangeListener(property, listener); + } + + public void removePropertyChangeListener(PropertyChangeListener listener) { + getPropertyChangeSupport().removePropertyChangeListener(listener); + } + + public void removePropertyChangeListener(String property, PropertyChangeListener listener) { + getPropertyChangeSupport().removePropertyChangeListener(property, listener); + } + + protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) { + if (propertyChangeSupport != null) { + getPropertyChangeSupport().firePropertyChange(propertyName, oldValue, newValue); + } + } + + @Override + public String toString() { + return getClass().getName() + "[" + value + "]"; + } +} Copied: lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/JAXXButtonGroup.java (from rev 862, lutinjaxx/trunk/core/src/main/java/jaxx/runtime/swing/JAXXButtonGroup.java) =================================================================== --- lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/JAXXButtonGroup.java (rev 0) +++ lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/JAXXButtonGroup.java 2008-10-02 12:50:25 UTC (rev 865) @@ -0,0 +1,103 @@ +/* + * Copyright 2006 Ethan Nicholas. All rights reserved. + * Use is subject to license terms. + */ +package jaxx.runtime.swing; + +import javax.swing.AbstractButton; +import javax.swing.ButtonGroup; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.util.Enumeration; + +public class JAXXButtonGroup extends ButtonGroup { + public static final String SELECTED_VALUE_PROPERTY = "selectedValue"; + + private PropertyChangeSupport propertyChangeSupport; + private Object selectedValue; + private ChangeListener changeListener = new ChangeListener() { + public void stateChanged(ChangeEvent e) { + updateSelectedValue(); + } + }; + private static final long serialVersionUID = -2096340516687228691L; + + + @Override + public void add(AbstractButton button) { + super.add(button); + button.addChangeListener(changeListener); + updateSelectedValue(); + } + + @Override + public void remove(AbstractButton button) { + super.remove(button); + button.removeChangeListener(changeListener); + updateSelectedValue(); + } + + + public void updateSelectedValue() { + Enumeration<AbstractButton> e = getElements(); + while (e.hasMoreElements()) { + AbstractButton button = e.nextElement(); + if (button.isSelected()) { + Object selectedValue = button.getClientProperty("$value"); + if (selectedValue != getSelectedValue()) { + setSelectedValue(selectedValue); + } + } + } + } + + + public Object getSelectedValue() { + return selectedValue; + } + + + public void setSelectedValue(Object value) { + Object oldValue = getSelectedValue(); + this.selectedValue = value; + firePropertyChange(oldValue); + } + + + protected PropertyChangeSupport getPropertyChangeSupport() { + if (propertyChangeSupport == null) { + propertyChangeSupport = new PropertyChangeSupport(this); + } + return propertyChangeSupport; + } + + + public void addPropertyChangeListener(PropertyChangeListener listener) { + getPropertyChangeSupport().addPropertyChangeListener(listener); + } + + + public void addPropertyChangeListener(String property, PropertyChangeListener listener) { + getPropertyChangeSupport().addPropertyChangeListener(property, listener); + } + + + public void removePropertyChangeListener(PropertyChangeListener listener) { + getPropertyChangeSupport().removePropertyChangeListener(listener); + } + + + public void removePropertyChangeListener(String property, PropertyChangeListener listener) { + getPropertyChangeSupport().removePropertyChangeListener(property, listener); + } + + + private void firePropertyChange(Object oldValue) { + if (propertyChangeSupport != null) { + getPropertyChangeSupport().firePropertyChange(SELECTED_VALUE_PROPERTY, + oldValue, getSelectedValue()); + } + } +} Copied: lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/JAXXComboBox.java (from rev 862, lutinjaxx/trunk/core/src/main/java/jaxx/runtime/swing/JAXXComboBox.java) =================================================================== --- lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/JAXXComboBox.java (rev 0) +++ lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/JAXXComboBox.java 2008-10-02 12:50:25 UTC (rev 865) @@ -0,0 +1,176 @@ +/* + * Copyright 2006 Ethan Nicholas. All rights reserved. + * Use is subject to license terms. + */ +package jaxx.runtime.swing; + +import javax.swing.AbstractListModel; +import javax.swing.ComboBoxModel; +import javax.swing.DefaultListCellRenderer; +import javax.swing.JComboBox; +import javax.swing.JList; +import javax.swing.ListModel; +import java.awt.Component; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; +import java.util.List; + +public class JAXXComboBox extends JComboBox { + public class JAXXComboBoxModel extends AbstractListModel implements ComboBoxModel { + private List<Item> items; + private Object selectedItem; + private static final long serialVersionUID = -8940733376638766414L; + + public JAXXComboBoxModel(List<Item> items) { + this.items = items; + + PropertyChangeListener listener = new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent e) { + if (e.getPropertyName().equals(Item.SELECTED_PROPERTY)) { + Item item = (Item) e.getSource(); + int itemIndex = JAXXComboBoxModel.this.items.indexOf(item); + // TODO: fix cut-and-pasting badness + int[] oldSelection = new int[]{getSelectedIndex()}; + int[] newSelection; + int index = -1; + for (int i = 0; i < oldSelection.length; i++) { + if (oldSelection[i] == itemIndex) { + index = i; + break; + } + } + if (item.isSelected()) { + if (index != -1) // it was already selected + { + return; + } + newSelection = new int[oldSelection.length + 1]; + System.arraycopy(oldSelection, 0, newSelection, 0, oldSelection.length); + newSelection[newSelection.length - 1] = itemIndex; + } else { + if (index == -1) // it already wasn't selected + { + return; + } + newSelection = new int[oldSelection.length - 1]; + System.arraycopy(oldSelection, 0, newSelection, 0, index); + System.arraycopy(oldSelection, index + 1, newSelection, index, oldSelection.length - 1 - index); + } + setSelectedIndex(newSelection[0]); + } else { + // TODO: more cut-and-pasting badness + for (int i = 0; i < getSize(); i++) { + if (getElementAt(i) == ((Item) e.getSource()).getValue()) { + fireContentsChanged(JAXXComboBoxModel.this, i, i); + if (getSelectedIndex() == i) { + fireItemStateChanged(new ItemEvent(JAXXComboBox.this, ItemEvent.ITEM_STATE_CHANGED, getElementAt(i), ItemEvent.DESELECTED)); + } + return; + } + } + } + } + }; + for (Item item : items) { + item.addPropertyChangeListener(listener); + } + } + + + public Object getElementAt(int i) { + return items.get(i).getValue(); + } + + + public int getSize() { + return items.size(); + } + + + public Object getSelectedItem() { + return selectedItem; + } + + + public void setSelectedItem(Object selectedItem) { + if ((this.selectedItem != null && !this.selectedItem.equals(selectedItem)) || + this.selectedItem == null && selectedItem != null) { + this.selectedItem = selectedItem; + fireContentsChanged(this, -1, -1); + } + } + } + + + public JAXXComboBox() { + setRenderer(new DefaultListCellRenderer() { + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + ListModel model = list.getModel(); + if (model instanceof JAXXComboBoxModel) { + List/*<Item>*/ items = ((JAXXComboBoxModel) model).items; + Item item = null; + if (index == -1) { + for (Object item1 : items) { + Item testItem = (Item) item1; + if (testItem.getValue() == value) { + item = testItem; + break; + } + } + } else + item = (Item) items.get(index); + + if (item != null) { + String label = item.getLabel(); + if (label != null) { + value = label; + } + } + } + return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + } + }); + + addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent e) { + ListModel model = getModel(); + if (model instanceof JAXXComboBoxModel) { + List<Item> items = ((JAXXComboBoxModel) model).items; + for (int i = items.size() - 1; i >= 0; i--) { + boolean selected = getSelectedIndex() == i; + Item item = items.get(i); + if (selected != item.isSelected()) { + item.setSelected(selected); + } + } + } + } + }); + } + + + // this way we can keep it marked protected and still allow code in this file to call it + @Override + protected void fireItemStateChanged(ItemEvent e) { + super.fireItemStateChanged(e); + } + + public void setItems(List<Item> items) { + setModel(new JAXXComboBoxModel(items)); + List<Integer> selectedIndexList = new ArrayList<Integer>(); + for (int i = 0; i < items.size(); i++) + if (items.get(i).isSelected()) { + selectedIndexList.add(i); + } + int[] selectedIndices = new int[selectedIndexList.size()]; + for (int i = 0; i < selectedIndexList.size(); i++) { + selectedIndices[i] = selectedIndexList.get(i); + } + if (selectedIndices.length > 0) { + setSelectedIndex(selectedIndices[0]); + } + } +} \ No newline at end of file Copied: lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/JAXXList.java (from rev 862, lutinjaxx/trunk/core/src/main/java/jaxx/runtime/swing/JAXXList.java) =================================================================== --- lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/JAXXList.java (rev 0) +++ lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/JAXXList.java 2008-10-02 12:50:25 UTC (rev 865) @@ -0,0 +1,148 @@ +/* + * Copyright 2006 Ethan Nicholas. All rights reserved. + * Use is subject to license terms. + */ +package jaxx.runtime.swing; + +import javax.swing.AbstractListModel; +import javax.swing.DefaultListCellRenderer; +import javax.swing.JList; +import javax.swing.ListModel; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import java.awt.Component; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; +import java.util.List; + +public class JAXXList extends JList { + public class JAXXListModel extends AbstractListModel { + private List<Item> items; + private static final long serialVersionUID = -1598924187490122036L; + + public JAXXListModel(List<Item> items) { + this.items = items; + + PropertyChangeListener listener = new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent e) { + if (e.getPropertyName().equals(Item.SELECTED_PROPERTY)) { + Item item = (Item) e.getSource(); + int itemIndex = JAXXListModel.this.items.indexOf(item); + int[] oldSelection = getSelectedIndices(); + int[] newSelection; + int index = -1; + for (int i = 0; i < oldSelection.length; i++) { + if (oldSelection[i] == itemIndex) { + index = i; + break; + } + } + if (item.isSelected()) { + if (index != -1) // it was already selected + { + return; + } + newSelection = new int[oldSelection.length + 1]; + System.arraycopy(oldSelection, 0, newSelection, 0, oldSelection.length); + newSelection[newSelection.length - 1] = itemIndex; + } else { + if (index == -1) // it already wasn't selected + { + return; + } + newSelection = new int[oldSelection.length - 1]; + System.arraycopy(oldSelection, 0, newSelection, 0, index); + System.arraycopy(oldSelection, index + 1, newSelection, index, oldSelection.length - 1 - index); + } + setSelectedIndices(newSelection); + } else { + for (int i = 0; i < getSize(); i++) { + if (getElementAt(i) == ((Item) e.getSource()).getValue()) { + fireContentsChanged(JAXXListModel.this, i, i); + if (isSelectedIndex(i)) { + fireSelectionValueChanged(i, i, false); + } + return; + } + } + } + } + }; + for (Item item : items) { + item.addPropertyChangeListener(listener); + } + } + + + public Object getElementAt(int i) { + return items.get(i).getValue(); + } + + + public int getSize() { + return items.size(); + } + } + + public JAXXList() { + setCellRenderer(new DefaultListCellRenderer() { + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + ListModel model = list.getModel(); + if (model instanceof JAXXListModel) { + Item item = ((JAXXListModel) model).items.get(index); + String label = item.getLabel(); + if (label != null) { + value = label; + } + } + return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + } + }); + + addListSelectionListener(new ListSelectionListener() { + public void valueChanged(ListSelectionEvent e) { + ListModel model = getModel(); + if (model instanceof JAXXListModel) { + List<Item> items = ((JAXXListModel) model).items; + for (int i = items.size() - 1; i >= 0; i--) { + boolean selected = isSelectedIndex(i); + Item item = items.get(i); + if (selected != item.isSelected()) { + item.setSelected(selected); + } + } + } + } + }); + } + + + // this way we can keep it marked protected and still allow code in this file to call it + @Override + protected void fireSelectionValueChanged(int firstIndex, int lastIndex, boolean isAdjusting) { + super.fireSelectionValueChanged(firstIndex, lastIndex, isAdjusting); + } + + + public void setSelectedValue(Object value) { + super.setSelectedValue(value, true); + } + + + public void setItems(List<Item> items) { + setModel(new JAXXListModel(items)); + List<Integer> selectedIndexList = new ArrayList<Integer>(); + for (int i = 0; i < items.size(); i++) { + if (items.get(i).isSelected()) { + selectedIndexList.add(i); + } + } + int[] selectedIndices = new int[selectedIndexList.size()]; + for (int i = 0; i < selectedIndexList.size(); i++) { + selectedIndices[i] = selectedIndexList.get(i); + } + setSelectedIndices(selectedIndices); + } +} \ No newline at end of file Copied: lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/JAXXTab.java (from rev 862, lutinjaxx/trunk/core/src/main/java/jaxx/runtime/swing/JAXXTab.java) =================================================================== --- lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/JAXXTab.java (rev 0) +++ lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/JAXXTab.java 2008-10-02 12:50:25 UTC (rev 865) @@ -0,0 +1,23 @@ +/* +* ##% Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008 Code Lutin, +* Tony Chemit +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +* ##% */ +package jaxx.runtime.swing; + +/** @author chemit */ +public class JAXXTab extends Table { +} Copied: lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/JAXXToggleButton.java (from rev 862, lutinjaxx/trunk/core/src/main/java/jaxx/runtime/swing/JAXXToggleButton.java) =================================================================== --- lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/JAXXToggleButton.java (rev 0) +++ lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/JAXXToggleButton.java 2008-10-02 12:50:25 UTC (rev 865) @@ -0,0 +1,94 @@ +package jaxx.runtime.swing; + +public class JAXXToggleButton extends javax.swing.JToggleButton { + + protected String glueText; + protected String normalText; + protected String glueTooltipText; + protected String normalTooltipText; + protected int normalMnemonic; + protected int glueMnemonic; + + protected boolean _init; + + + public String getGlueText() { + return glueText; + } + + public String getNormalText() { + return normalText; + } + + public String getGlueTooltipText() { + return glueTooltipText; + } + + public String getNormalTooltipText() { + return normalTooltipText; + } + + public void setGlueText(String glueText) { + this.glueText = glueText; + + } + + public void setNormalText(String normalText) { + this.normalText = normalText; + + } + + public void setGlueTooltipText(String glueTooltipText) { + this.glueTooltipText = glueTooltipText; + } + + public int getNormalMnemonic() { + return normalMnemonic; + } + + public void setNormalMnemonic(int normalMnemonic) { + this.normalMnemonic = normalMnemonic; + } + + public int getGlueMnemonic() { + return glueMnemonic; + } + + public void setGlueMnemonic(int glueMnemonic) { + this.glueMnemonic = glueMnemonic; + } + + public void setNormalTooltipText(String normalTooltipText) { + this.normalTooltipText = normalTooltipText; + if (!_init) { + init(); + _init = true; + } + } + + @Override + public void setSelected(boolean b) { + super.setSelected(b); + if (isSelected()) { + setText(getGlueText()); + setToolTipText(getGlueTooltipText()); + setMnemonic(getGlueMnemonic()); + } else { + setText(getNormalText()); + setToolTipText(getNormalTooltipText()); + setMnemonic(getNormalMnemonic()); + } + revalidate(); + } + + public void init() { + setSelected(false); + } + + /* end raw body code */ + public JAXXToggleButton() { + super(); + _init = false; + } + +} \ No newline at end of file Copied: lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/JAXXTree.java (from rev 862, lutinjaxx/trunk/core/src/main/java/jaxx/runtime/swing/JAXXTree.java) =================================================================== --- lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/JAXXTree.java (rev 0) +++ lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/JAXXTree.java 2008-10-02 12:50:25 UTC (rev 865) @@ -0,0 +1,213 @@ +/* + * Copyright 2006 Ethan Nicholas. All rights reserved. + * Use is subject to license terms. + */ +package jaxx.runtime.swing; + +import javax.swing.JTree; +import javax.swing.event.TreeModelEvent; +import javax.swing.event.TreeModelListener; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.DefaultTreeCellRenderer; +import javax.swing.tree.TreeModel; +import javax.swing.tree.TreePath; +import java.awt.Component; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; +import java.util.List; + +public class JAXXTree extends JTree { + private static final String SYNTHETIC = "<synthetic root node>"; + + public class JAXXTreeModel implements TreeModel { + private Item root; + private List<TreeModelListener> listeners = new ArrayList<TreeModelListener>(); + + public JAXXTreeModel(List<Item> items) { + if (items.size() == 1) { + this.root = items.get(0); + } + else { + this.root = new Item(null, null, SYNTHETIC, false); + for (Item item : items) { + root.addChild(item); + } + } + + PropertyChangeListener listener = new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent e) { + if (e.getPropertyName().equals(Item.SELECTED_PROPERTY)) { + Item item = (Item) e.getSource(); + if (item.isSelected()) { + addSelectionPath(getTreePath(item)); + } + else { + removeSelectionPath(getTreePath(item)); + } + } else { + Item item = (Item) e.getSource(); + boolean root = item.getParent() == null; + TreePath path = !root ? getTreePath(item.getParent()) : null; + fireTreeNodesChanged(new TreeModelEvent(JAXXTreeModel.this, path, + !root ? new int[]{item.getParent().getChildren().indexOf(item)} : null, + new Object[]{item.getValue()})); + } + } + }; + addPropertyChangeListener(root, listener); + } + + + private void addPropertyChangeListener(Item item, PropertyChangeListener listener) { + item.addPropertyChangeListener(listener); + List<Item> children = item.getChildren(); + for (Item aChildren : children) { + addPropertyChangeListener(aChildren, listener); + } + } + + + public void addTreeModelListener(TreeModelListener listener) { + listeners.add(listener); + } + + + /* This is an inefficient implementation, but hand-coded tree structures are unlikely to contain +enough nodes for that to really matter. This could be sped up with caching. */ + private Item findItem(Object value) { + return findItem(root, value); + } + + + private Item findItem(Item node, Object value) { + if (node.getValue() == value) + return node; + else { + List<Item> children = node.getChildren(); + for (Item aChildren : children) { + Item result = findItem(aChildren, value); + if (result != null) + return result; + } + return null; + } + } + + + private TreePath getTreePath(Item node) { + List<Object> path = new ArrayList<Object>(); + while (node != null) { + path.add(0, node.getValue()); + node = node.getParent(); + } + return new TreePath(path.toArray()); + } + + + public Object getChild(Object parent, int index) { + Item node = findItem(parent); + return node.getChildren().get(index).getValue(); + } + + + public int getChildCount(Object parent) { + Item node = findItem(parent); + return node.getChildren().size(); + } + + + public int getIndexOfChild(Object parent, Object child) { + Item node = findItem(parent); + List<Item> children = node.getChildren(); + for (int i = 0,j = children.size();i<j; i++) + if (children.get(i).getValue() == child) { + return i; + } + return -1; + } + + + public Object getRoot() { + return root.getValue(); + } + + + public boolean isLeaf(Object node) { + Item item = findItem(node); + return item != null && item.getChildren().size() == 0; + } + + + public void removeTreeModelListener(TreeModelListener listener) { + listeners.remove(listener); + } + + + public void fireTreeNodesChanged(TreeModelEvent e) { + for (TreeModelListener listener : listeners) { + listener.treeNodesChanged(e); + } + } + + + public void valueForPathChanged(TreePath path, Object newValue) { + } + } + + public JAXXTree() { + setCellRenderer(new DefaultTreeCellRenderer() { + @Override + public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { + TreeModel model = tree.getModel(); + if (model instanceof JAXXTreeModel) { + Item item = ((JAXXTreeModel) model).findItem(value); + if (item != null) { + String label = item.getLabel(); + if (label != null) { + value = label; + } + } + } + return super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus); + } + }); + + addTreeSelectionListener(new TreeSelectionListener() { + public void valueChanged(TreeSelectionEvent e) { + TreeModel model = getModel(); + if (model instanceof JAXXTreeModel) { + scan((JAXXTreeModel) model, ((JAXXTreeModel) model).root); + } + } + + + private void scan(JAXXTreeModel model, Item item) { + TreePath path = model.getTreePath(item); + if (item.isSelected() != isPathSelected(path)) { + item.setSelected(!item.isSelected()); + } + List<Item> children = item.getChildren(); + for (Item aChildren : children) { + scan(model, aChildren); + } + } + }); + } + + + public void setItems(List<Item> items) { + JAXXTreeModel model = new JAXXTreeModel(items); + if (model.getRoot() != null) { + setRootVisible(model.getRoot() != SYNTHETIC); + } + setModel(model); + } + + + public Object getSelectionValue() { + TreePath selectionPath = getSelectionPath(); + return selectionPath != null ? selectionPath.getLastPathComponent() : null; + } +} \ No newline at end of file Copied: lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/Spacer.java (from rev 862, lutinjaxx/trunk/core/src/main/java/jaxx/runtime/swing/Spacer.java) =================================================================== --- lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/Spacer.java (rev 0) +++ lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/Spacer.java 2008-10-02 12:50:25 UTC (rev 865) @@ -0,0 +1,10 @@ +/* + * Copyright 2006 Ethan Nicholas. All rights reserved. + * Use is subject to license terms. + */ +package jaxx.runtime.swing; + +import javax.swing.JComponent; + +public class Spacer extends JComponent { +} \ No newline at end of file Copied: lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/TabInfo.java (from rev 862, lutinjaxx/trunk/core/src/main/java/jaxx/runtime/swing/TabInfo.java) =================================================================== --- lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/TabInfo.java (rev 0) +++ lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/TabInfo.java 2008-10-02 12:50:25 UTC (rev 865) @@ -0,0 +1,165 @@ +/* + * Copyright 2006 Ethan Nicholas. All rights reserved. + * Use is subject to license terms. + */ +package jaxx.runtime.swing; + +import javax.swing.Icon; +import javax.swing.event.SwingPropertyChangeSupport; +import java.awt.Color; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; + +public class TabInfo { + public static String BACKGROUND_PROPERTY = "background"; + public static String DISABLED_ICON_PROPERTY = "disabledIcon"; + public static String DISPLAYED_MNEMONIC_INDEX_PROPERTY = "displayedMnemonicIndex"; + public static String ENABLED_PROPERTY = "enabled"; + public static String FOREGROUND_PROPERTY = "foreground"; + public static String ICON_PROPERTY = "icon"; + public static String MNEMONIC_PROPERTY = "mnemonic"; + public static String TITLE_PROPERTY = "title"; + public static String TOOL_TIP_TEXT_PROPERTY = "toolTipText"; + + private String id; + private Color background; + private Icon disabledIcon; + private int displayedMnemonicIndex = -1; + private boolean enabled = true; + private Color foreground; + private Icon icon; + private int mnemonic = -1; + private String title; + private String toolTipText; + + private PropertyChangeSupport propertyChangeSupport; + + public TabInfo() { + } + + public TabInfo(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public Color getBackground() { + return background; + } + + public void setBackground(Color background) { + Color oldValue = this.background; + this.background = background; + firePropertyChange(BACKGROUND_PROPERTY, oldValue, background); + } + + public Icon getDisabledIcon() { + return disabledIcon; + } + + public void setDisabledIcon(Icon disabledIcon) { + Icon oldValue = this.disabledIcon; + this.disabledIcon = disabledIcon; + firePropertyChange(DISABLED_ICON_PROPERTY, oldValue, disabledIcon); + } + + public int getDisplayedMnemonicIndex() { + return displayedMnemonicIndex; + } + + public void setDisplayedMnemonicIndex(int displayedMnemonicIndex) { + int oldValue = this.displayedMnemonicIndex; + this.displayedMnemonicIndex = displayedMnemonicIndex; + firePropertyChange(DISPLAYED_MNEMONIC_INDEX_PROPERTY, oldValue, displayedMnemonicIndex); + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + boolean oldValue = this.enabled; + this.enabled = enabled; + firePropertyChange(ENABLED_PROPERTY, oldValue, enabled); + } + + public Color getForeground() { + return foreground; + } + + public void setForeground(Color foreground) { + Color oldValue = this.foreground; + this.foreground = foreground; + firePropertyChange(FOREGROUND_PROPERTY, oldValue, foreground); + } + + public Icon getIcon() { + return icon; + } + + public void setIcon(Icon icon) { + Icon oldValue = this.icon; + this.icon = icon; + firePropertyChange(ICON_PROPERTY, oldValue, icon); + } + + public int getMnemonic() { + return mnemonic; + } + + public void setMnemonic(int mnemonic) { + int oldValue = this.mnemonic; + this.mnemonic = mnemonic; + firePropertyChange(MNEMONIC_PROPERTY, oldValue, mnemonic); + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + String oldValue = this.title; + this.title = title; + firePropertyChange(TITLE_PROPERTY, oldValue, title); + } + + public String getToolTipText() { + return toolTipText; + } + + public void setToolTipText(String toolTipText) { + String oldValue = this.toolTipText; + this.toolTipText = toolTipText; + firePropertyChange(TOOL_TIP_TEXT_PROPERTY, oldValue, toolTipText); + } + + private PropertyChangeSupport getPropertyChangeSupport() { + if (propertyChangeSupport == null) { + propertyChangeSupport = new SwingPropertyChangeSupport(this); + } + return propertyChangeSupport; + } + + public void addPropertyChangeListener(PropertyChangeListener listener) { + getPropertyChangeSupport().addPropertyChangeListener(listener); + } + + public void addPropertyChangeListener(String property, PropertyChangeListener listener) { + getPropertyChangeSupport().addPropertyChangeListener(property, listener); + } + + public void removePropertyChangeListener(PropertyChangeListener listener) { + getPropertyChangeSupport().removePropertyChangeListener(listener); + } + + public void removePropertyChangeListener(String property, PropertyChangeListener listener) { + getPropertyChangeSupport().removePropertyChangeListener(property, listener); + } + + protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) { + if (propertyChangeSupport != null) + getPropertyChangeSupport().firePropertyChange(propertyName, oldValue, newValue); + } +} Copied: lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/TabInfoPropertyChangeListener.java (from rev 862, lutinjaxx/trunk/core/src/main/java/jaxx/runtime/swing/TabInfoPropertyChangeListener.java) =================================================================== --- lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/TabInfoPropertyChangeListener.java (rev 0) +++ lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/TabInfoPropertyChangeListener.java 2008-10-02 12:50:25 UTC (rev 865) @@ -0,0 +1,41 @@ +/* + * Copyright 2006 Ethan Nicholas. All rights reserved. + * Use is subject to license terms. + */ +package jaxx.runtime.swing; + +import javax.swing.Icon; +import javax.swing.JTabbedPane; +import java.awt.Color; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +public class TabInfoPropertyChangeListener implements PropertyChangeListener { + private JTabbedPane tabs; + private int tabIndex; + + public TabInfoPropertyChangeListener(JTabbedPane tabs, int tabIndex) { + this.tabs = tabs; + this.tabIndex = tabIndex; + } + + public void propertyChange(PropertyChangeEvent e) { + String name = e.getPropertyName(); + if (name.equals(TabInfo.TITLE_PROPERTY)) + tabs.setTitleAt(tabIndex, (String) e.getNewValue()); + else if (name.equals(TabInfo.TOOL_TIP_TEXT_PROPERTY)) + tabs.setToolTipTextAt(tabIndex, (String) e.getNewValue()); + else if (name.equals(TabInfo.FOREGROUND_PROPERTY)) + tabs.setForegroundAt(tabIndex, (Color) e.getNewValue()); + else if (name.equals(TabInfo.BACKGROUND_PROPERTY)) + tabs.setBackgroundAt(tabIndex, (Color) e.getNewValue()); + else if (name.equals(TabInfo.MNEMONIC_PROPERTY)) + tabs.setMnemonicAt(tabIndex, (Integer) e.getNewValue()); + else if (name.equals(TabInfo.DISPLAYED_MNEMONIC_INDEX_PROPERTY)) + tabs.setDisplayedMnemonicIndexAt(tabIndex, (Integer) e.getNewValue()); + else if (name.equals(TabInfo.ICON_PROPERTY)) + tabs.setIconAt(tabIndex, (Icon) e.getNewValue()); + else if (name.equals(TabInfo.DISABLED_ICON_PROPERTY)) + tabs.setDisabledIconAt(tabIndex, (Icon) e.getNewValue()); + } +} Copied: lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/Utils.java (from rev 862, lutinjaxx/trunk/core/src/main/java/jaxx/runtime/swing/Utils.java) =================================================================== --- lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/Utils.java (rev 0) +++ lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/swing/Utils.java 2008-10-02 12:50:25 UTC (rev 865) @@ -0,0 +1,59 @@ +/* + * Copyright 2006 Ethan Nicholas. All rights reserved. + * Use is subject to license terms. + */ +package jaxx.runtime.swing; + +import javax.swing.SwingUtilities; +import javax.swing.text.AbstractDocument; +import javax.swing.text.JTextComponent; +import java.lang.reflect.Field; + +public class Utils { + private static Field numReaders; + private static Field notifyingListeners; + + public static void setText(final JTextComponent c, final String text) { + try { + // AbstractDocument deadlocks if we try to acquire a write lock while a read lock is held by the current thread + // If there are any readers, dispatch an invokeLater. This should only happen in the event of circular bindings. + // Similarly, circular bindings can result in an "Attempt to mutate in notification" error, which we deal with + // by checking for the 'notifyingListeners' property. + AbstractDocument document = (AbstractDocument) c.getDocument(); + if (numReaders == null) { + numReaders = AbstractDocument.class.getDeclaredField("numReaders"); + numReaders.setAccessible(true); + } + if (notifyingListeners == null) { + notifyingListeners = AbstractDocument.class.getDeclaredField("notifyingListeners"); + notifyingListeners.setAccessible(true); + } + + if (notifyingListeners.get(document).equals(Boolean.TRUE)) + return; + + if (((Integer) numReaders.get(document)).intValue() > 0) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + if (!c.getText().equals(text)) + c.setText(text); + } + }); + return; + } + + String oldText = c.getText(); + if (oldText == null || !oldText.equals(text)) + c.setText(text); + } + catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } + catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + catch (SecurityException e) { + c.setText(text); + } + } +} Copied: lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/tags/swing (from rev 862, lutinjaxx/trunk/core/src/main/java/jaxx/tags/swing) Deleted: lutinjaxx/trunk/jaxx-core/src/main/resources/META-INF/services/jaxx.spi.Initializer =================================================================== --- lutinjaxx/trunk/core/src/main/resources/META-INF/services/jaxx.spi.Initializer 2008-09-24 19:58:26 UTC (rev 857) +++ lutinjaxx/trunk/jaxx-core/src/main/resources/META-INF/services/jaxx.spi.Initializer 2008-10-02 12:50:25 UTC (rev 865) @@ -1 +0,0 @@ -jaxx.DefaultInitializer \ No newline at end of file Copied: lutinjaxx/trunk/jaxx-core/src/main/resources/META-INF/services/jaxx.spi.Initializer (from rev 863, lutinjaxx/trunk/core/src/main/resources/META-INF/services/jaxx.spi.Initializer) =================================================================== --- lutinjaxx/trunk/jaxx-core/src/main/resources/META-INF/services/jaxx.spi.Initializer (rev 0) +++ lutinjaxx/trunk/jaxx-core/src/main/resources/META-INF/services/jaxx.spi.Initializer 2008-10-02 12:50:25 UTC (rev 865) @@ -0,0 +1,2 @@ +jaxx.DefaultInitializer +jaxx.tags.swing.SwingInitializer \ No newline at end of file Copied: lutinjaxx/trunk/jaxx-core/src/test/java/jaxx/runtime/UtilTest.java (from rev 862, lutinjaxx/trunk/core/src/test/java/jaxx/runtime/UtilTest.java) =================================================================== --- lutinjaxx/trunk/jaxx-core/src/test/java/jaxx/runtime/UtilTest.java (rev 0) +++ lutinjaxx/trunk/jaxx-core/src/test/java/jaxx/runtime/UtilTest.java 2008-10-02 12:50:25 UTC (rev 865) @@ -0,0 +1,25 @@ +package jaxx.runtime; + +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +public class UtilTest extends junit.framework.TestCase { + private int count; + + + public void testGetEventListener() { + count = 0; + DocumentListener listener = (DocumentListener) jaxx.runtime.Util.getEventListener(javax.swing.event.DocumentListener.class, this, "incCount"); + listener.insertUpdate(null); + assertEquals(count, 1); + DocumentListener listener2 = (DocumentListener) jaxx.runtime.Util.getEventListener(javax.swing.event.DocumentListener.class, this, "incCount"); + listener2.removeUpdate(null); + assertEquals(count, 2); + //assertTrue("Received two different event listeners despite using identical parameters", listener == listener2); + } + + + public void incCount(DocumentEvent e) { + count++; + } +} \ No newline at end of file Modified: lutinjaxx/trunk/jaxx-swing-action/pom.xml =================================================================== --- lutinjaxx/trunk/jaxx-swing-action/pom.xml 2008-10-02 12:48:26 UTC (rev 864) +++ lutinjaxx/trunk/jaxx-swing-action/pom.xml 2008-10-02 12:50:25 UTC (rev 865) @@ -5,16 +5,16 @@ <modelVersion>4.0.0</modelVersion> <parent> - <groupId>org.codelutin.jaxx</groupId> - <artifactId>pom</artifactId> - <version>0.4</version> + <groupId>org.codelutin</groupId> + <artifactId>lutinjaxx</artifactId> + <version>0.5-SNAPSHOT</version> </parent> <artifactId>jaxx-swing-action</artifactId> <name>jaxx-swing-action</name> <packaging>jar</packaging> - <version>0.4</version> + <version>0.5-SNAPSHOT</version> <description>Jaxx lutin library swing actions extension</description> <build> @@ -38,8 +38,8 @@ <dependencies> <dependency> - <groupId>org.codelutin.jaxx</groupId> - <artifactId>runtime</artifactId> + <groupId>org.codelutin</groupId> + <artifactId>jaxx-core</artifactId> <scope>compile</scope> </dependency> Modified: lutinjaxx/trunk/jaxx-swing-tab/pom.xml =================================================================== --- lutinjaxx/trunk/jaxx-swing-tab/pom.xml 2008-10-02 12:48:26 UTC (rev 864) +++ lutinjaxx/trunk/jaxx-swing-tab/pom.xml 2008-10-02 12:50:25 UTC (rev 865) @@ -5,16 +5,16 @@ <modelVersion>4.0.0</modelVersion> <parent> - <groupId>org.codelutin.jaxx</groupId> - <artifactId>pom</artifactId> - <version>0.4</version> + <groupId>org.codelutin</groupId> + <artifactId>lutinjaxx</artifactId> + <version>0.5-SNAPSHOT</version> </parent> <artifactId>jaxx-swing-tab</artifactId> <name>jaxx-swing-tab</name> <packaging>jar</packaging> - <version>0.4</version> + <version>0.5-SNAPSHOT</version> <description>Jaxx lutin library swing tab extension</description> <build> @@ -26,8 +26,8 @@ <dependencies> <dependency> - <groupId>org.codelutin.jaxx</groupId> - <artifactId>runtime</artifactId> + <groupId>org.codelutin</groupId> + <artifactId>jaxx-core</artifactId> <scope>compile</scope> </dependency> Modified: lutinjaxx/trunk/pom.xml =================================================================== --- lutinjaxx/trunk/pom.xml 2008-10-02 12:48:26 UTC (rev 864) +++ lutinjaxx/trunk/pom.xml 2008-10-02 12:50:25 UTC (rev 865) @@ -12,34 +12,35 @@ <parent> <groupId>org.codelutin</groupId> <artifactId>lutinproject</artifactId> - <version>2.4</version> + <version>3.0</version> </parent> - <groupId>org.codelutin.jaxx</groupId> - <artifactId>pom</artifactId> + <!--groupId>org.codelutin</groupId--> + <artifactId>lutinjaxx</artifactId> <modules> - <module>util</module> - <module>core</module> - <module>runtime</module> - <module>jaxx-swing</module> + <module>jaxx-util</module> + <module>jaxx-core</module> + <!--module>runtime</module> + <module>jaxx-swing</module--> <module>jaxx-swing-action</module> <module>jaxx-swing-tab</module> - <module>maven</module> + <module>maven-jaxx-plugin</module> </modules> <dependencies> <dependency> <groupId>org.codelutin</groupId> <artifactId>lutinutil</artifactId> + <version>0.30</version> </dependency> </dependencies> <!-- ************************************************************* --> <!-- *** Project Information ************************************* --> <!-- ************************************************************* --> - <name>pom</name> - <version>0.4</version> + <name>lutinjaxx</name> + <version>0.5-SNAPSHOT</version> <description>Jaxx lutin library main pom</description> <inceptionYear>2008</inceptionYear> @@ -52,20 +53,25 @@ <properties> <!-- current version --> - <current.version>0.4</current.version> + <current.version>0.5-SNAPSHOT</current.version> <!-- id du projet du labs --> <labs.id>38</labs.id> <labs.project>buix</labs.project> - <!-- maven will suffix with /${pom.artifactId} --> - <maven.scm.developerConnection>scm:svn:svn+ssh://${username}@${labs.host}/svnroot/buix/trunk - </maven.scm.developerConnection> + <!-- override this property to define scm url property --> + <maven.scm.url>http://${labs.host}/plugins/scmsvn/viewcvs.php/${pom.artifactId}/trunk/?root=${labs.project} + </maven.scm.url> - <!-- maven will suffix with /${pom.artifactId} --> - <maven.scm.connection>scm:svn:svn:anonymous@${labs.host}/svnroot/buix/trunk</maven.scm.connection> + <!-- BEWARE, will be suffixed by /${pom.artifactId} by inheritance --> + <maven.scm.developerConnection>scm:svn:svn+ssh://${username}@${labs.host}/svnroot/${labs.project}/${pom.artifactId}/trunk + </maven.scm.developerConnection> + <!-- BEWARE, will be suffixed by /${pom.artifactId} by inheritance --> + <maven.scm.connection>scm:svn:svn://anonymous@${labs.host}/svnroot/${labs.project}/${pom.artifactId}/trunk</maven.scm.connection> + + </properties> <build> @@ -103,38 +109,27 @@ <dependency> <groupId>org.codelutin</groupId> <artifactId>lutinutil</artifactId> - <version>0.30-SNAPSHOT</version> + <version>0.30</version> </dependency> <dependency> - <groupId>org.codelutin.jaxx</groupId> - <artifactId>util</artifactId> + <groupId>org.codelutin</groupId> + <artifactId>jaxx-util</artifactId> <version>${current.version}</version> </dependency> <dependency> - <groupId>org.codelutin.jaxx</groupId> - <artifactId>core</artifactId> + <groupId>org.codelutin</groupId> + <artifactId>jaxx-core</artifactId> <version>${current.version}</version> </dependency> - <dependency> - <groupId>org.codelutin.jaxx</groupId> - <artifactId>runtime</artifactId> - <version>${current.version}</version> - </dependency> - <dependency> - <groupId>org.codelutin.jaxx</groupId> - <artifactId>jaxx-swing</artifactId> - <version>${current.version}</version> - </dependency> - <dependency> - <groupId>org.codelutin.jaxx</groupId> + <groupId>org.codelutin</groupId> <artifactId>jaxx-swing-action</artifactId> <version>${current.version}</version> </dependency> <dependency> - <groupId>org.codelutin.jaxx</groupId> + <groupId>org.codelutin</groupId> <artifactId>jaxx-swing-tab</artifactId> <version>${current.version}</version> </dependency> @@ -173,9 +168,10 @@ <!-- the project is a module in a labs project (lutinutil), so we have to override this property (see in the parent pom for more explanation) --> - <scm> + <scm> + <connection>${maven.scm.connection}</connection> <developerConnection>${maven.scm.developerConnection}</developerConnection> - <!--url>${maven.scm.url}</url--> + <url>${maven.scm.url}</url> </scm> </project>
participants (1)
-
tchemit@users.labs.libre-entreprise.org