[Buix-commits] r1315 - in guix/trunk/guix-compiler: . src/main/java/org/codelutin/guix src/main/java/org/codelutin/guix/compiler src/main/java/org/codelutin/guix/model src/main/java/org/codelutin/guix/tags
Author: kmorin Date: 2009-04-15 15:52:09 +0000 (Wed, 15 Apr 2009) New Revision: 1315 Added: guix/trunk/guix-compiler/src/main/java/org/codelutin/guix/compiler/ guix/trunk/guix-compiler/src/main/java/org/codelutin/guix/compiler/GuixCompiler.java guix/trunk/guix-compiler/src/main/java/org/codelutin/guix/compiler/GuixCompilerLaunchor.java guix/trunk/guix-compiler/src/main/java/org/codelutin/guix/model/ guix/trunk/guix-compiler/src/main/java/org/codelutin/guix/model/AttributeDescriptor.java guix/trunk/guix-compiler/src/main/java/org/codelutin/guix/model/ClassDescriptor.java guix/trunk/guix-compiler/src/main/java/org/codelutin/guix/model/ModelObject.java guix/trunk/guix-compiler/src/main/java/org/codelutin/guix/tags/ guix/trunk/guix-compiler/src/main/java/org/codelutin/guix/tags/TagManager.java Removed: guix/trunk/guix-compiler/src/main/java/org/codelutin/guix/App.java Modified: guix/trunk/guix-compiler/ guix/trunk/guix-compiler/pom.xml Log: Ajout de la d?\195?\169pendance XPP3 Ajout de GuixCompiler, GuixCompilerLaunchor, ModelObject, ClassDescriptor, AttributeDescriptor et TagManager Property changes on: guix/trunk/guix-compiler ___________________________________________________________________ Name: svn:ignore + target Modified: guix/trunk/guix-compiler/pom.xml =================================================================== --- guix/trunk/guix-compiler/pom.xml 2009-04-15 15:50:22 UTC (rev 1314) +++ guix/trunk/guix-compiler/pom.xml 2009-04-15 15:52:09 UTC (rev 1315) @@ -15,11 +15,13 @@ <artifactId>guix-compiler</artifactId> - <dependencyManagement> - <dependencies> - - </dependencies> - </dependencyManagement> + <dependencies> + <dependency> + <groupId>xpp3</groupId> + <artifactId>xpp3</artifactId> + <version>1.1.3.3</version> + </dependency> + </dependencies> <!-- ************************************************************* --> <!-- *** Project Information ************************************* --> Deleted: guix/trunk/guix-compiler/src/main/java/org/codelutin/guix/App.java =================================================================== --- guix/trunk/guix-compiler/src/main/java/org/codelutin/guix/App.java 2009-04-15 15:50:22 UTC (rev 1314) +++ guix/trunk/guix-compiler/src/main/java/org/codelutin/guix/App.java 2009-04-15 15:52:09 UTC (rev 1315) @@ -1,13 +0,0 @@ -package org.codelutin.guix; - -/** - * Hello world! - * - */ -public class App -{ - public static void main( String[] args ) - { - System.out.println( "Hello World!" ); - } -} Added: guix/trunk/guix-compiler/src/main/java/org/codelutin/guix/compiler/GuixCompiler.java =================================================================== --- guix/trunk/guix-compiler/src/main/java/org/codelutin/guix/compiler/GuixCompiler.java (rev 0) +++ guix/trunk/guix-compiler/src/main/java/org/codelutin/guix/compiler/GuixCompiler.java 2009-04-15 15:52:09 UTC (rev 1315) @@ -0,0 +1,199 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.codelutin.guix.compiler; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.Stack; +import org.codelutin.guix.model.ClassDescriptor; +import org.codelutin.guix.model.ModelObject; +import org.codelutin.guix.tags.TagManager; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; +import org.xmlpull.v1.builder.XmlPullElement; + +/** + * Compiles Guix files into Java classes + * + * @author morin + */ +public class GuixCompiler { + + /** flag to detec if an error occurs while compiling jaxx file */ + protected boolean failed; + /** Used for error reporting purposes, + * so we can report the right line number. */ + protected Stack<XmlPullElement> tagsBeingCompiled = + new Stack<XmlPullElement>(); + /** Used for error reporting purposes, + * so we can report the right source file. */ + protected Stack<File> sourceFiles = new Stack<File>(); + /** XML parser of src file. */ + protected XmlPullParser xpp; + private File baseDir; + private File src; + private String outputClassName; + + /*------------------------------------------------------------------------*/ + /*-- Constructor methods -------------------------------------------------*/ + /*------------------------------------------------------------------------*/ + /** + * Creates a new GuixCompiler. + * + * @param baseDir classpath location + * @param src location of file to compile + * @param outputClassName the out file name + */ + protected GuixCompiler(File baseDir, File src, String outputClassName) { + this.baseDir = baseDir; + this.src = src; + sourceFiles.push(src); + this.outputClassName = outputClassName; + } + + public void compile() + throws FileNotFoundException, IOException, XmlPullParserException { + //try { + XmlPullParserFactory factory = XmlPullParserFactory.newInstance( + System.getProperty(XmlPullParserFactory.PROPERTY_NAME), null); + factory.setNamespaceAware(true); + XmlPullParser xpp = factory.newPullParser(); + xpp.setInput(new FileReader(src)); + + if (xpp.getEventType() == xpp.START_DOCUMENT) { + xpp.next(); + + String nameSpace = xpp.getNamespace(); + String localName = xpp.getName(); + boolean namespacePrefix = xpp.getPrefix() != null; + String fullClassName = + getFullClassName(nameSpace,localName,namespacePrefix); + + ModelObject root = new ModelObject(xpp.getAttributeValue("", "id")); + root.setClassDescriptor(new ClassDescriptor( + src.getName(),src.getPath().replace('/', '.'))); + root.getClassDescriptor() + .setSuperClass(new ClassDescriptor(localName,fullClassName + .substring(0, fullClassName.lastIndexOf(".")))); + + xpp.next(); + compile(xpp); + } + //} + } + + /*------------------------------------------------------------------------*/ + /*-- Compile methods -----------------------------------------------------*/ + /*------------------------------------------------------------------------*/ + public void compile(final XmlPullParser xpp) throws IOException { + //tagsBeingCompiled.push(tag); + String nameSpace = xpp.getNamespace(); + String localName = xpp.getName(); + boolean namespacePrefix = xpp.getPrefix() != null; + String fullClassName = + getFullClassName(nameSpace,localName,namespacePrefix); + + + 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); + if (tag.getAttributeNode("javaBean") != null) { + // add java bean support for this property + String capitalizeName = + org.apache.commons.lang.StringUtils.capitalize(id); + // add method + symbolTable.getScriptMethods().add( + new MethodDescriptor("get" + capitalizeName, + Modifier.PUBLIC, fullClassName, new String[0], classLoader)); + if (Boolean.class.getName().equals(fullClassName)) { + symbolTable.getScriptMethods().add(new MethodDescriptor("is" + capitalizeName, Modifier.PUBLIC, fullClassName, new String[0], classLoader)); + } + symbolTable.getScriptMethods().add(new MethodDescriptor("set" + capitalizeName, Modifier.PUBLIC, "void", new String[]{fullClassName}, classLoader)); + } + } + } + // 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); + } + } + + private String getFullClassName(String nameSpace, String localName, + boolean namespacePrefix) { + // resolve class tags into fully-qualified class name + String fullClassName; + if (nameSpace != null && nameSpace.endsWith("*")) { + String packageName = nameSpace.substring(0, nameSpace.length() - 1); + // class name is fully-qualified already + if (localName.startsWith(packageName)) { + fullClassName = TagManager.resolveClassName(localName); + } + // namespace not included in class name, + //probably need the namespace to resolve + else { + fullClassName = TagManager + .resolveClassName(packageName + localName); + // it was just a default namespace, + // try again without using the namespace + if (fullClassName == null && !namespacePrefix) { + fullClassName = TagManager.resolveClassName(localName); + } + } + } + else { + fullClassName = TagManager.resolveClassName(localName); + } + return fullClassName; + } + + public boolean isFailed() { + return failed; + } + +} Added: guix/trunk/guix-compiler/src/main/java/org/codelutin/guix/compiler/GuixCompilerLaunchor.java =================================================================== --- guix/trunk/guix-compiler/src/main/java/org/codelutin/guix/compiler/GuixCompilerLaunchor.java (rev 0) +++ guix/trunk/guix-compiler/src/main/java/org/codelutin/guix/compiler/GuixCompilerLaunchor.java 2009-04-15 15:52:09 UTC (rev 1315) @@ -0,0 +1,213 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.codelutin.guix.compiler; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Launch Guix files compilation + * + * @author morin + */ +public class GuixCompilerLaunchor { + + /** log */ + protected static final Log log = + LogFactory.getLog(GuixCompilerLaunchor.class); + /** original list of files to compile */ + protected final File[] files = null; + /** original list of classes to compile */ + protected final String[] classNames = null; + /** Files to be treated while compilation. */ + protected List<File> jaxxFiles = new ArrayList<File>(); + /** Class names corresponding to the files in the jaxxFiles list. */ + protected List<String> jaxxFileClassNames = new ArrayList<String>(); + /** Maps the names of classes being compiled to the compiler instance + * handling the compilation. */ + protected Map<String, GuixCompiler> compilers = + new HashMap<String, GuixCompiler>(); + + protected int compilerCount; + private File targetDirectory = new File("destination"); + + + /** + * Compiled a set of files. + * + * @return <code>true</code> if compilation succeeds, + * <code>false</code> otherwise + */ + public synchronized boolean compile() { + //reset(); // just to be safe... + compilerCount = 0; + jaxxFiles.addAll(Arrays.asList(files)); + jaxxFileClassNames.addAll(Arrays.asList(classNames)); + try { + boolean success = true; + + // pass 1 + /*if (!nextStep(LifeCycle.compile_first_pass, success)) { + return false; + }*/ + boolean compiled; + do { + compiled = false; + assert jaxxFiles.size() == jaxxFileClassNames.size(); + // clone it so it can safely be modified while we're iterating + Iterator<File> filesIterator = + new ArrayList<File>(jaxxFiles).iterator(); + Iterator<String> classNamesIterator = + new ArrayList<String>(jaxxFileClassNames).iterator(); + + while (filesIterator.hasNext()) { + + File file = filesIterator.next(); + String className = classNamesIterator.next(); + if (log.isDebugEnabled()) { + log.debug("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 = targetDirectory; + if (destDir != null) { + int dotPos = className.lastIndexOf("."); + if (dotPos != -1) { + destDir = new File(destDir,className + .substring(0, dotPos) + .replace('.', File.separatorChar)); + } + if (!destDir.exists() && !destDir.mkdirs()) { + log.warn("couldn't create directory "+ destDir); + continue; + } + } else { + //destDir = file.getParentFile(); + } + GuixCompiler compiler = new GuixCompiler( + file.getParentFile(), file, className); + //addProfileTime(compiler, currentPass.name() + "_start"); + compilers.put(className, compiler); + compiler.compile(); + //addProfileTime(compiler, currentPass.name() + "_end"); + //assert !symbolTables.values().contains(compiler.getSymbolTable()) : "symbolTable is already registered"; + //symbolTables.put(file, compiler.getSymbolTable()); + if (compiler.isFailed()) { + success = false; + } + //} + } + + } while (compiled); + + /*// pass 2 + if (!nextStep(LifeCycle.compile_second_pass, success)) { + return false; + } + + assert jaxxFiles.size() == jaxxFileClassNames.size(); + List<File> jaxxFilesClone = new ArrayList<File>(jaxxFiles); + for (String className : jaxxFileClassNames) { + JAXXCompiler compiler = getCompiler(className, "Internal error: could not find compiler for " + className + " during second pass"); + addProfileTime(compiler, currentPass.name() + "_start"); + if (log.isDebugEnabled()) { + log.debug("runInitializers for " + className); + } + if (!compiler.isFailed()) { + compiler.runInitializers(); + } + if (log.isDebugEnabled()) { + log.debug("compile second pass for " + className); + } + compiler.compileSecondPass(); + addProfileTime(compiler, currentPass.name() + "_end"); + if (log.isDebugEnabled()) { + log.debug("done with result [" + !compiler.isFailed() + "] for " + className); + } + if (compiler.isFailed()) { + 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 (!nextStep(LifeCycle.stylesheet_pass, success)) { + return false; + } + assert jaxxFiles.size() == jaxxFileClassNames.size(); + for (String className : jaxxFileClassNames) { + JAXXCompiler compiler = getCompiler(className, "Internal error: could not find compiler for " + className + " during stylesheet application"); + addProfileTime(compiler, currentPass.name() + "_start"); + compiler.applyStylesheets(); + addProfileTime(compiler, currentPass.name() + "_end"); + if (compiler.isFailed()) { + success = false; + } + } + + // code generation + if (!nextStep(LifeCycle.generate_pass, success)) { + return false; + } + assert jaxxFiles.size() == jaxxFileClassNames.size(); + List<Generator> generators = new ArrayList<Generator>(); + for (Generator generator : ServiceLoader.load(Generator.class)) { + generators.add(generator); + } + for (String className : jaxxFileClassNames) { + JAXXCompiler compiler = getCompiler(className, "Internal error: could not find compiler for " + className + " during code generation"); + addProfileTime(compiler, currentPass.name() + "_start"); + compiler.generateCode(generators); + addProfileTime(compiler, currentPass.name() + "_end"); + //compiler.generateCode(); + if (compiler.isFailed()) { + success = false; + } + } + + if (options.isProfile()) { + // profile pass (only if succes compile) + if (!nextStep(LifeCycle.profile_pass, success)) { + return false; + } + StringBuilder buffer = profiler.computeProfileReport(); + log.info(buffer.toString()); + } + + return report(success); + + //FIXME : deal better the exception treatment... + } catch (CompilerException e) { + System.err.println(e.getMessage()); + e.printStackTrace(); + return false;*/ + + } catch (Throwable e) { + e.printStackTrace(); + return false; + } finally { + compilerCount = compilers.size(); + //TC - 20081018 only reset when no error was detected + //if (options.isResetAfterCompile() && errorCount == 0) { + // reset(); + //} + return false; + } + } +} Added: guix/trunk/guix-compiler/src/main/java/org/codelutin/guix/model/AttributeDescriptor.java =================================================================== --- guix/trunk/guix-compiler/src/main/java/org/codelutin/guix/model/AttributeDescriptor.java (rev 0) +++ guix/trunk/guix-compiler/src/main/java/org/codelutin/guix/model/AttributeDescriptor.java 2009-04-15 15:52:09 UTC (rev 1315) @@ -0,0 +1,32 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.codelutin.guix.model; + +/** + * + * @author morin + */ +public class AttributeDescriptor { + private String name; + private String value; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + +} Added: guix/trunk/guix-compiler/src/main/java/org/codelutin/guix/model/ClassDescriptor.java =================================================================== --- guix/trunk/guix-compiler/src/main/java/org/codelutin/guix/model/ClassDescriptor.java (rev 0) +++ guix/trunk/guix-compiler/src/main/java/org/codelutin/guix/model/ClassDescriptor.java 2009-04-15 15:52:09 UTC (rev 1315) @@ -0,0 +1,55 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.codelutin.guix.model; + +/** + * + * @author morin + */ +public class ClassDescriptor { + + private String name; + private String packageName; + private String script; + private ClassDescriptor superClass; + + public ClassDescriptor(String name, String packageName){ + this.name = name; + this.packageName = packageName; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPackageName() { + return packageName; + } + + public void setPackageName(String packageName) { + this.packageName = packageName; + } + + public String getScript() { + return script; + } + + public void setScript(String script) { + this.script = script; + } + + public ClassDescriptor getSuperClass() { + return superClass; + } + + public void setSuperClass(ClassDescriptor superClass) { + this.superClass = superClass; + } +} Added: guix/trunk/guix-compiler/src/main/java/org/codelutin/guix/model/ModelObject.java =================================================================== --- guix/trunk/guix-compiler/src/main/java/org/codelutin/guix/model/ModelObject.java (rev 0) +++ guix/trunk/guix-compiler/src/main/java/org/codelutin/guix/model/ModelObject.java 2009-04-15 15:52:09 UTC (rev 1315) @@ -0,0 +1,66 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.codelutin.guix.model; + +import java.util.List; + +/** + * + * @author morin + */ +public class ModelObject { + + private String id; + private ModelObject parent; + private List<ModelObject> children; + private ClassDescriptor classDescriptor; + private List<AttributeDescriptor> attributeDescriptors; + + public ModelObject(String id) { + this.id = id; + } + + public List<AttributeDescriptor> getAttributeDescriptors() { + return attributeDescriptors; + } + + public void setAttributeDescriptors( + List<AttributeDescriptor> attributeDescriptors) { + this.attributeDescriptors = attributeDescriptors; + } + + public ClassDescriptor getClassDescriptor() { + return classDescriptor; + } + + public void setClassDescriptor(ClassDescriptor classDescriptor) { + this.classDescriptor = classDescriptor; + } + + public List<ModelObject> getChildren() { + return children; + } + + public void setChildren(List<ModelObject> children) { + this.children = children; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public ModelObject getParent() { + return parent; + } + + public void setParent(ModelObject parent) { + this.parent = parent; + } +} Added: guix/trunk/guix-compiler/src/main/java/org/codelutin/guix/tags/TagManager.java =================================================================== --- guix/trunk/guix-compiler/src/main/java/org/codelutin/guix/tags/TagManager.java (rev 0) +++ guix/trunk/guix-compiler/src/main/java/org/codelutin/guix/tags/TagManager.java 2009-04-15 15:52:09 UTC (rev 1315) @@ -0,0 +1,402 @@ +/* + * Copyright 2006 Ethan Nicholas. All rights reserved. + * Use is subject to license terms. + */ +package org.codelutin.guix.tags; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** Manages TagHandlers, including automatically compiling .jaxx files corresponding to class tags. */ +public class TagManager { + /** log */ + protected static final Log log = LogFactory.getLog(TagManager.class); + + /** + * Namespace for JAXX's non-class tags, such as <script;>. The namespace normally does not + * need to be specified but can be used to resolve ambiguities. + */ + public static final String JAXX_NAMESPACE = "http://www.jaxxframework.org/"; + + /** Maps simple tag names to their default namespaces (package names). */ + private static Map<String, String> defaultNamespaces = new HashMap<String, String>(); + + /** Maps qualified tag names to the TagHandlers responsible for processing them. */ + private static Map<QName, TagHandler> registeredTags = new HashMap<QName, TagHandler>(); + + /** Keeps track of whether or not named classes exist. */ + private static Map<String, Boolean> classExistenceCache = new HashMap<String, Boolean>(); + + /** + * Maps bean classes to their TagHandler classes. The mapping is to TagHandler classes, rather than to + * TagHandler instances, because subclasses of the bean class should be handled by the same TagHandler + * (assuming no more specific mappings exist), which requires creating a new instance of the TagHandler. + */ + private static ClassMap<Class<? extends TagHandler>> registeredBeans = new ClassMap<Class<? extends TagHandler>>(); + + // still targeting 1.4, so I can't use javax.xml.namespace.QName + private static class QName { + private String namespaceURI; + private String localPart; + + public QName(String namespaceURI, String localPart) { + if (localPart == null) + throw new NullPointerException(); + this.namespaceURI = namespaceURI; + this.localPart = localPart; + } + + + public String getNamespaceURI() { + return namespaceURI; + } + + + public String getLocalPart() { + return localPart; + } + + @Override + public boolean equals(Object o) { + if (o == null || !(o instanceof QName)) + return false; + QName qname = (QName) o; + return qname.getNamespaceURI().equals(getNamespaceURI()) && qname.getLocalPart().equals(getLocalPart()); + } + + @Override + public int hashCode() { + return (namespaceURI != null ? namespaceURI.hashCode() : 0) ^ getLocalPart().hashCode(); + } + } + + + private TagManager() { /* not instantiable */ } + + + public static void reset(boolean verbose) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException { + registeredBeans.clear(); + registeredTags.clear(); + defaultNamespaces.clear(); + CompiledObjectDecorator.reset(); + JAXXCompilerLaunchor.loadLibraries(verbose); + } + + + /** + * Maps a class tag to a specific <code>TagHandler</code>. When a tag representing the bean class is + * encountered (either the class' simple name, if it is unambiguous, or its fully-qualified name), the specified + * <code>TagHandler</code> will be invoked to compile it. + * + * @param beanClass the class to associate with a <code>TagHandler</code> + * @param handler the <code>TagHandler</code> class, which must descend from <code>DefaultObjectHandler</code> + * @throws IllegalArgumentException if the handler class does not descend from <code>DefaultObjectHandler</code> + */ + public static <T extends TagHandler> void registerBean(ClassDescriptor beanClass, Class<T> handler) { + if (!DefaultObjectHandler.class.isAssignableFrom(handler)) { + throw new IllegalArgumentException("handler class must be a subclass of DefaultObjectHandler"); + } + registeredBeans.put(beanClass, handler); + if (log.isDebugEnabled()) { + log.debug(beanClass + " : " + handler); + } + String name = beanClass.getName(); + int dotPos = name.lastIndexOf("."); + String namespace = name.substring(0, dotPos + 1) + "*"; + name = name.substring(dotPos + 1); + registerDefaultNamespace(name, namespace); + } + + + /** + * Sets the default namespace for a tag. When the tag is encountered with no namespace specified, + * the specified namespace will be assumed. Mapping the same tag to two or more default namespaces + * removes the mapping and marks the entry as being ambiguous (by putting a <code>null</code> + * value into the map); this causes an error to be thrown if the tag is used without a namespace being + * specified. + * <p/> + * Java package names on tags are automatically converted into namespaces (e.g. <javax.swing.JButton/> + * and <JButton xmlns="javax.swing.*"/> are equivalent), so tags with package names are considered + * to have namespaces specified. + * + * @param tag tag name + * @param namespace namespace + */ + public static void registerDefaultNamespace(String tag, String namespace) { + if (defaultNamespaces.containsKey(tag) + && defaultNamespaces.get(tag) != null + && !defaultNamespaces.get(tag).equals(namespace)) { + defaultNamespaces.put(tag, null); // tag name is now ambiguous + } else { + defaultNamespaces.put(tag, namespace); + } + } + + + /** + * Registers a <code>TagHandler</code> for a tag. When a tag with the given name and namespace + * is encountered, the <code>TagHandler's compileFirstPass</code> and <code>compileSecondPass</code> + * methods will be invoked to handle it. + * <p/> + * It is not an error to register an already-registered tag and namespace combination. The new mapping + * will replace the old mapping. + * + * @param namespace the tag's namespace + * @param tag the simple name of the tag + * @param handler the <code>TagHandler</code> which should process the tag + */ + public static <T extends TagHandler> void registerTag(String namespace, String tag, T handler) { + if (namespace == null) { + namespace = "*"; + } + //System.out.println("registerTag "+namespace+" : "+tag+" : "+handler); + if (log.isDebugEnabled()) { + log.debug(tag + " : " + handler); + } + registeredTags.put(new QName(namespace, tag), handler); + registerDefaultNamespace(tag, namespace); + } + + + /** + * Returns the <code>TagHandler</code> that should be used to process the specified tag. + * If the tag represents the class name of an uncompiled <code>.jaxx</code> file, the + * <code>.jaxx</code> is first compiled. + * + * @param namespace the tag's namespace (may be <code>null</code>) + * @param tag the tag's simple name + * @param compiler the current <code>JAXXCompiler</code> + * @return the <code>TagHandler</code> for the tag + * @throws jaxx.CompilerException ? + */ + public static TagHandler getTagHandler(String namespace, String tag, JAXXCompiler compiler) throws CompilerException { + return getTagHandler(namespace, tag, false, compiler); + } + + + private static String getNamespace(ClassDescriptor beanClass) { + String packageName = beanClass.getPackageName(); + return packageName != null ? packageName + ".*" : "*"; + + } + + + private static String getSimpleName(ClassDescriptor beanClass) { + String packageName = beanClass.getPackageName(); + if (packageName != null) { + assert beanClass.getName().startsWith(packageName); + return beanClass.getName().substring(packageName.length() + 1); + } + return beanClass.getName(); + } + + + /** + * @param beanClass the tag class + * @return the <code>TagHandler</code> that should be used to process the specified class. + * Only <code>TagHandlers</code> previously registered with <code>registerBean</code> + * are considered. + * @throws jaxx.CompilerException ? + */ + public static DefaultObjectHandler getTagHandler(ClassDescriptor beanClass) throws CompilerException { + try { + if (beanClass == null) { + throw new NullPointerException(); + } + String namespace = getNamespace(beanClass); + String tag = getSimpleName(beanClass); + DefaultObjectHandler handler = (DefaultObjectHandler) registeredTags.get(new QName(namespace, tag)); + if (handler == null) { + Class<? extends TagHandler> handlerClass = registeredBeans.get(beanClass); + if (handlerClass == null) { + throw new CompilerException("unable to find handler for " + beanClass); + } + Constructor<? extends TagHandler> constructor = handlerClass.getConstructor(ClassDescriptor.class); + handler = (DefaultObjectHandler) constructor.newInstance(beanClass); + registerTag(namespace, tag, handler); + } + return handler; + } + catch (InstantiationException e) { + throw new RuntimeException(e); + } + catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + + private static boolean classExists(String className) { + if (classExistenceCache.containsKey(className)) { + return classExistenceCache.get(className); + } + + boolean found = false; + + try { + Class.forName(className); + found = true; + }catch (ClassNotFoundException e) { + // ignore ? + } + catch (NoClassDefFoundError e) { // we get this instead of ClassNotFoundException on case-insensitive file systems when + // looking up a class with the wrong case + } + + classExistenceCache.put(className, found); + + return found; + } + + + private static String determinePackage(String simpleClassName, String defaultPackage) { + String namespace = null; + Set<String> classes = compiler.getImportedClasses(); + for (String className : classes) { // search class imports (e.g. import java.util.Date;) + if (className.equals(simpleClassName) || className.endsWith("." + simpleClassName)) { + namespace = className.substring(0, className.lastIndexOf(".") + 1) + "*"; + } + } + if (namespace == null) { // search package imports (e.g. import java.util.*;) + Set<String> searchList = compiler.getImportedPackages(); + if (defaultPackage != null) { + if (!defaultPackage.endsWith("*")) { + throw new IllegalArgumentException("defaultPackage must end in '*', found '" + defaultPackage + "'"); + } + if (classExists(defaultPackage.substring(0, defaultPackage.length() - 1) + simpleClassName)) { + return defaultPackage; + } + } + for (String currentPackage : searchList) { + String className = currentPackage + simpleClassName; + if (classExists(className)) { + if (namespace != null) { // we've already found the same name in another package + //compiler.reportError("symbol '" + simpleClassName + "' is ambiguous, found matching classes " + namespace.substring(0, namespace.length() - 1) + simpleClassName + " and " + currentPackage + simpleClassName + ". Use fully-qualified name to disambiguate."); + return null; + } + namespace = currentPackage + "*"; + } + } + } + + return namespace; + } + + /** + * Resolves a simple class name (like <code>Object</code> or <code>String</code>) to its fully-qualified name. Inner + * classes should be represented as they would appear in Java source code (e.g. JPopupMenu.Separator). Fully-qualified names, + * such as <code>java.lang.Object</code> are legal and will be returned unmodified (and in fact it is generally impossible to + * even know whether a given reference is fully qualified until it has been resolved). Returns <code>null</code> if no matching + * class could be found. + * + * @param name name to resolve + * @param compiler compile to use + * @return the resolved fqn class name + */ + public static String resolveClassName(String name) { + if (name.endsWith("[]")) + return resolveClassName(name.substring(0, name.length() - 2)) + "[]"; + if (name.indexOf("<") != -1) + name = name.substring(0, name.indexOf("<")); // strip off generic types + + name = name.intern(); + if (name.equals("boolean") || name.equals("byte") || name.equals("short") || name.equals("int") || + name.equals("long") || name.equals("float") || name.equals("double") || name.equals("char")) + return name; + + String result = null; + String originalName = name; + String defaultNamespace = null; + if (defaultNamespaces.containsKey(name)) { + defaultNamespace = defaultNamespaces.get(name); + if (defaultNamespace == null) { // defaultNamespaces map contains a null value, which is put there to indicate ambiguity + //compiler.reportError("class '" + name + "' is ambiguous; specify fully-qualified name (package and class) to disambiguate"); + return null; + } + } + if (defaultNamespace != null && defaultNamespace.endsWith("*")) { + result = defaultNamespace.substring(0, defaultNamespace.length() - 1) + name; + } + + if (result == null) { + // Inner class names (like JPopupMenu.Separator) present a special challenge. The name before the dot might be + // a package name, or it might be a class name. If it's a class name, it might be fully qualified, or it might + // not. And it's also not actually the correct name of the class, as far as the JVM is concerned -- the correct + // name uses a dollar sign instead of a dot (javax.swing.JPopupMenu$Separator). And there could be more than + // one inner class -- it's possible to have com.mycompany.Outer$Inner$Innerer$Innerest. + // + // The basic strategy is to start by treating the part before the last dot as a package name, as that is by far + // the most likely case. If we don't find the class there, change the last dot to a dollar sign and try again. + // Suppose we have the tag <com.mycompany.Outer.Inner.Innerer.Innerest/>, matching the class above. Resolution + // proceeds like this: + // com.mycompany.Outer.Inner.Innerer.* : Innerest + // com.mycompany.Outer.Inner.* : Innerer$Innerest + // com.mycompany.Outer.* : Inner$Innerer$Innerest + // com.mycompany.* : Outer$Inner$Innerer$Innerest + // And at this point we have a match with the class Outer$Inner$Innerer$Innerest in package com.mycompany. + int dotPos = originalName.lastIndexOf('.'); + for (; ;) { + String namespace = dotPos != -1 ? originalName.substring(0, dotPos) + ".*" : "*"; + name = originalName.substring(dotPos + 1).replace('.', '$'); + String packageName = determinePackage(name, namespace); + if (packageName != null) { + assert packageName.endsWith("*"); + if (packageName.equals(namespace) || namespace.equals("*")) { + // check for an alias (like javax.swing.JComboBox actually being jaxx.runtime.swing.JAXXComboBox) + TagHandler handler = registeredTags.get(new QName(namespace, name)); + if (handler != null) { // determine alias by looking at handler + ClassDescriptor alias = ((DefaultObjectHandler) handler).getBeanClass(); + // make sure the same handler is used for both the aliased and non-aliased names, in order to avoid "no CompiledObject has been registered" error + // the line below doesn't bother to handle the case where the aliased class name doesn't have a package, since it's a pretty safe assumption that + // that will never happen + assert alias.getPackageName() != null && alias.getPackageName().length() > 0 : "aliasing with no package name has not been implemented"; + registeredTags.put(new QName(alias.getPackageName() + ".*", alias.getName().substring(alias.getPackageName().length() + 1)), handler); + result = alias.getName(); + break; + } else { // no alias + result = packageName.substring(0, packageName.length() - 1) + name; + break; + } + } + // else we found a class by the same name, but in the wrong package + } + + if (dotPos <= 0) + break; + dotPos = originalName.lastIndexOf('.', dotPos - 1); + } + } + + if (result != null && !result.equals(originalName)) + result = resolveClassName(result); // check for aliases against the new name as well + + return result; + } + + + public static ClassDescriptor resolveClass(String className, JAXXCompiler compiler) { + try { + className = resolveClassName(className, compiler); + if (className == null) + return null; + return ClassDescriptorLoader.getClassDescriptor(className, compiler.getClassLoader()); + } + catch (ClassNotFoundException e) { + return null; + } + } +} \ No newline at end of file
participants (1)
-
kmorin@users.labs.libre-entreprise.org