/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 1997-2011 Oracle and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development * and Distribution License("CDDL") (collectively, the "License"). You * may not use this file except in compliance with the License. You can * obtain a copy of the License at * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html * or packager/legal/LICENSE.txt. See the License for the specific * language governing permissions and limitations under the License. * * When distributing the software, include this License Header Notice in each * file and include the License file at packager/legal/LICENSE.txt. * * GPL Classpath Exception: * Oracle designates this particular file as subject to the "Classpath" * exception as provided by Oracle in the GPL Version 2 section of the License * file that accompanied this code. * * Modifications: * If applicable, add the following below the License Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyright [year] [name of copyright owner]" * * Contributor(s): * If you wish your version of this file to be governed by only the CDDL or * only the GPL Version 2, indicate your decision by adding "[Contributor] * elects to include this software in this distribution under the [CDDL or GPL * Version 2] license." If you don't indicate a single choice of license, a * recipient has the option to distribute your version of this file under * either the CDDL, the GPL Version 2 or to extend the choice of license to * its licensees as provided above. However, if you add GPL Version 2 code * and therefore, elected the GPL Version 2 license, then the option applies * only if the new code is made subject to such option by the copyright * holder. */ package com.sun.tools; import com.sun.codemodel.CodeWriter; import java.io.File; import java.io.FileFilter; import java.io.FilenameFilter; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.Comparator; import java.util.Enumeration; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import com.sun.codemodel.JClassAlreadyExistsException; import com.sun.codemodel.JCodeModel; import com.sun.codemodel.JDefinedClass; import com.sun.codemodel.JExpr; import com.sun.codemodel.JFieldVar; import com.sun.codemodel.JMethod; import com.sun.codemodel.JMod; import com.sun.codemodel.JPackage; import com.sun.codemodel.JArray; import com.sun.codemodel.writer.FileCodeWriter; import com.sun.istack.tools.MaskingClassLoader; import com.sun.xml.bind.v2.model.annotation.Locatable; import com.sun.xml.bind.v2.model.annotation.Quick; import java.io.FileReader; import java.io.LineNumberReader; import java.net.URL; import java.net.URLClassLoader; import org.apache.tools.ant.AntClassLoader; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.apache.tools.ant.Task; import org.apache.tools.ant.types.Path; import org.apache.tools.ant.types.Reference; /** * Ant task that generates implementations of {@link Quick} classes * for annotations. * * @author Kohsuke Kawaguchi */ public class GeneratorTask extends Task { /** * Used to load additional user-specified classes. */ private final Path classpath; private final List<Pattern> patterns = new ArrayList<Pattern>(); private final List<URL> endorsedJars = new ArrayList<URL>(); private File licence = null; /** * Used during the build to load annotation classes. */ private ClassLoader userLoader; /** * Generated interfaces go into this codeModel. */ private JCodeModel codeModel = new JCodeModel(); /** * The writers will be generated into this package. */ private JPackage pkg = codeModel.rootPackage(); /** * Output directory */ private File output = new File("."); /** * Generate a {@link Quick} implementation of the specified annotation * in the specified package. * * @param ann * annotation type to */ void process(Class<? extends Annotation> ann, JDefinedClass c) { // [RESULT] // public final class XmlAttributeQuick extends Quick implements XmlAttribute { c._extends(Quick.class); c._implements(ann); // [RESULT] // private final XmlAttribute core; JFieldVar $core = c.field(JMod.PRIVATE | JMod.FINAL, ann, "core"); // [RESULT] // public XmlAttributeQuick(Locatable upstream, XmlAttribute core) { // super(upstream); // this.core = core; // } { JMethod m = c.constructor(JMod.PUBLIC); m.body().invoke("super").arg(m.param(Locatable.class, "upstream")); m.body().assign(JExpr._this().ref($core), m.param(ann, "core")); } // [RESULT] // protected Annotation getAnnotation() { // return core; // } { JMethod m = c.method(JMod.PROTECTED, Annotation.class, "getAnnotation"); m.body()._return($core); } // [RESULT] // protected Quick newInstance(Locatable upstream,Annotation core) { // return new XmlAttributeQuick(upstream,(XmlAttribute)core); // } { JMethod m = c.method(JMod.PROTECTED, Quick.class, "newInstance"); m.body()._return(JExpr._new(c).arg(m.param(Locatable.class, "upstream")).arg(JExpr.cast(codeModel.ref(ann), m.param(Annotation.class, "core")))); } // [RESULT] // public Class<XmlAttribute> annotationType() { // return XmlAttribute.class; // } { JMethod m = c.method(JMod.PUBLIC, codeModel.ref(Class.class).narrow(ann), "annotationType"); m.body()._return(codeModel.ref(ann).dotclass()); } // then for each annotation parameter just generate a delegation method for (Method method : ann.getDeclaredMethods()) { // [RESULT] // public String name() { // return core.name(); //} JMethod m = c.method(JMod.PUBLIC, method.getReturnType(), method.getName()); m.body()._return($core.invoke(method.getName())); } } /** * Gets the short name from a fully-qualified name. */ private static String getShortName(String className) { int idx = className.lastIndexOf('.'); return className.substring(idx + 1); } /** * Map from annotation classes to their writers. */ private final Map<Class, JDefinedClass> queue = new TreeMap<Class, JDefinedClass>(new Comparator<Object>() { public int compare(Object o1, Object o2) { if (o1 == o2) { return 0; } if (o1 == null) { return -1; } if (o2 == null) { return 1; } if (!(o1 instanceof Class) || !(o2 instanceof Class)) { throw new ClassCastException(); } return ((Class) o1).getName().compareTo(((Class) o2).getName()); } }); public GeneratorTask() { classpath = new Path(null); } public void setLicence(File licence) { this.licence = licence; } @Override public void setProject(Project project) { super.setProject(project); classpath.setProject(project); } public void setPackage(String pkgName) { pkg = codeModel._package(pkgName); } /** Nested <classpath> element. */ public void setClasspath(Path cp) { classpath.createPath().append(cp); } /** Nested <classpath> element. */ public Path createClasspath() { return classpath.createPath(); } public void setClasspathRef(Reference r) { classpath.createPath().setRefid(r); } public void setDestdir(File output) { this.output = output; } /** * Nested <classes> elements. */ public static class Classes { Pattern p; public void setIncludes(String pattern) { try { p = Pattern.compile(convertToRegex(pattern)); } catch (PatternSyntaxException e) { throw new BuildException(e); } } private String convertToRegex(String pattern) { StringBuilder regex = new StringBuilder(); char nc = ' '; if (pattern.length() > 0) { for (int i = 0; i < pattern.length(); i++) { char c = pattern.charAt(i); int j = i; nc = ' '; if ((j + 1) != pattern.length()) { nc = pattern.charAt(j + 1); } //escape single '.' if ((c == '.') && (nc != '.')) { regex.append('\\'); regex.append('.'); //do not allow patterns like a..b } else if ((c == '.') && (nc == '.')) { continue; // "**" gets replaced by ".*" } else if ((c == '*') && (nc == '*')) { regex.append(".*"); break; //'*' replaced by anything but '.' i.e [^\\.]+ } else if (c == '*') { regex.append("[^\\.]+"); continue; //'?' replaced by anything but '.' i.e [^\\.] } else if (c == '?') { regex.append("[^\\.]"); //else leave the chars as they occur in the pattern } else { regex.append(c); } } } return regex.toString(); } } /** * Nested <endorse> elements. */ public static class Endorse { URL endorsedJar; public void setPath(String jar) throws MalformedURLException { endorsedJar = new File(jar).toURI().toURL(); } } /** * List of classes to be handled */ public void addConfiguredClasses(Classes c) { patterns.add(c.p); } /** * List of endorsed jars */ public void addConfiguredEndorse(Endorse e) { endorsedJars.add(e.endorsedJar); } @Override public void execute() throws BuildException { if ((endorsedJars != null) && (!endorsedJars.isEmpty())) { ClassLoader maskedLoader = new MaskingClassLoader(new AntClassLoader(project, classpath), "javax.xml.bind"); URL[] jars = new URL[endorsedJars.size()]; userLoader = new URLClassLoader(endorsedJars.toArray(jars), maskedLoader); } else { userLoader = new AntClassLoader(project, classpath); } try { // find clsses to be bound for (String path : classpath.list()) { File f = new File(path); if (f.isDirectory()) { processDir(f, ""); } else { processJar(f); } } // [RESULT] // class Init { // static Quick[] getAll() { // ... return all Quick classes ... // } // } try { JArray all = JExpr.newArray(codeModel.ref(Quick.class)); JDefinedClass init = pkg._class(0, "Init"); init.method(JMod.STATIC, Quick[].class, "getAll").body()._return(all); for (Map.Entry<Class, JDefinedClass> e : queue.entrySet()) { process(e.getKey(), e.getValue()); all.add(JExpr._new(e.getValue()).arg(JExpr._null()).arg(JExpr._null())); } } catch (JClassAlreadyExistsException e) { throw new BuildException(e); } CodeWriter fcw = new FileCodeWriter(output); if (licence != null) { String licenceHeader = null; LineNumberReader lnr = null; FileReader fr = null; try { fr = new FileReader(licence); lnr = new LineNumberReader(fr); licenceHeader = lnr.readLine(); do { licenceHeader += "\n"; String line = lnr.readLine(); if (line == null) { break; } else { licenceHeader += line; } } while (true); } catch (IOException ex) { if (lnr != null) { try { lnr.close(); } catch (IOException ex1) { Logger.getLogger(GeneratorTask.class.getName()).log(Level.SEVERE, null, ex1); } } if (fr != null) { try { fr.close(); } catch (IOException ex1) { Logger.getLogger(GeneratorTask.class.getName()).log(Level.SEVERE, null, ex1); } } Logger.getLogger(GeneratorTask.class.getName()).log(Level.SEVERE, null, ex); } fcw = new LicenceCodeWriter(fcw, licenceHeader); } codeModel.build(fcw); } catch (IOException ex) { Logger.getLogger(GeneratorTask.class.getName()).log(Level.SEVERE, null, ex); } finally { userLoader = null; } } /** * Visits a jar file and looks for classes that match the specified pattern. */ private void processJar(File jarfile) { try { JarFile jar = new JarFile(jarfile); for (Enumeration<JarEntry> en = jar.entries(); en.hasMoreElements();) { JarEntry e = en.nextElement(); process(e.getName(), e.getTime()); } } catch (IOException e) { throw new BuildException("Unable to process " + jarfile, e); } } /** * Visits a directory and looks for classes that match the specified pattern. * * @param prefix * the package name prefix like "" or "foo/bar/" */ private void processDir(File dir, String prefix) { // look for class files String[] classes = dir.list(new FilenameFilter() { public boolean accept(File dir, String name) { return name.endsWith(".class"); } }); for (String c : classes) { process(prefix + c, new File(dir, c).lastModified()); } // look for subdirectories File[] subdirs = dir.listFiles(new FileFilter() { public boolean accept(File path) { return path.isDirectory(); } }); for (File f : subdirs) { processDir(f, prefix + f.getName() + '/'); } } /** * Process a file. * * @param name such as "javax/xml/bind/Abc.class" */ private void process(String name, long timestamp) { if (!name.endsWith(".class")) { return; // not a class } name = name.substring(0, name.length() - 6); name = name.replace('/', '.'); // make it a class naem // find a match for (Pattern p : patterns) { if (p.matcher(name).matches()) { queue(name, timestamp); return; } } } /** * Queues a file for generation. */ private void queue(String className, long timestamp) { log("Processing " + className, Project.MSG_VERBOSE); Class ann; try { ann = userLoader.loadClass(className); } catch (ClassNotFoundException e) { throw new BuildException(e); } if (!Annotation.class.isAssignableFrom(ann)) { log("Skipping " + className + ". Not an annotation", Project.MSG_WARN); return; } JDefinedClass w; try { w = pkg._class(JMod.FINAL, getShortName(ann.getName()) + "Quick"); } catch (JClassAlreadyExistsException e) { throw new BuildException("Class name collision on " + className, e); } // up to date check String name = pkg.name(); if (name.length() == 0) { name = getShortName(className); } else { name += '.' + getShortName(className); } File dst = new File(output, name.replace('.', File.separatorChar) + "Quick.java"); if (dst.exists() && dst.lastModified() > timestamp) { log("Skipping " + className + ". Up to date.", Project.MSG_VERBOSE); w.hide(); } queue.put(ann, w); } }