Index: maven-commandline-plugin/src/java/org/codelutin/util/Generate.java diff -u /dev/null maven-commandline-plugin/src/java/org/codelutin/util/Generate.java:1.1 --- /dev/null Wed Dec 12 21:03:22 2007 +++ maven-commandline-plugin/src/java/org/codelutin/util/Generate.java Wed Dec 12 21:03:16 2007 @@ -0,0 +1,320 @@ +/** + * ##% Copyright (C) 2002, 2007 Code Lutin 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 org.codelutin.util; + +import javassist.CannotCompileException; +import javassist.ClassClassPath; +import javassist.ClassPath; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.NotFoundException; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugin.logging.Log; +import org.codelutin.i18n.I18n; +import static org.codelutin.i18n.I18n._; +import org.codelutin.util.OptionParserAnnotationHelper.ApplicationA; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; + +/** + * Permet de générer la factory de définitions d'options de ligne de commande, + * pour un fichier de propriété contenant la definition formelle des options. + * + * @author chemit + * @goal generate + * @phase compile + */ +public class Generate extends AbstractMojo { + + static { + // init I18N + I18n.init(System.getProperty("user.language"), + System.getProperty("user.country")); + } + + protected Log log = getLog(); + + /** + * @description Dépendance du projet. + * @parameter default-value="${project.name}" + * @readonly + */ + protected String projectName; + /** + * @description Répertoire de sortie des classes. + * @parameter expression="${commandline.out}" default-value="${project.build.outputDirectory}" + */ + protected File out; + /** + * @description nom du paquetage utilisé pour la génération des classes + * @parameter expression="${commandline.parserFullyQualifiedName}" + * @required + */ + protected String parserFullyQualifiedName; + /** + * @description type de source à utiliser par le parser + * @parameter expression="${commandline.typeSource}" default-value="properties" + * @required + */ + protected String typeSource; + /** + * @description Chemin du fichier de propriétés contenant les définitions d'options à utiliser pour générer la factory de définitions d'options. + * @parameter expression="${commandline.source}" default-value="${basedir}/src/resources/${project.name}_options.properties" + */ + protected File source; + /** + * @description Target rst file. + * @parameter expression="${commandLine.rstFilePath}" default-value="${basedir}/src/site/fr/rst/${project.name}_usage.rst" + */ + protected File rstFilePath; + /** + * @description flag to generate rst file. + * @parameter expression="${commandLine.generateRst}" default-value="true" + */ + protected boolean generateRst; + /** + * @description flag to generate or not specialized Option + * @parameter expression="${commandLine.generateOptionImplementation}" default-value="false" + */ + protected boolean generateOptionImplementation; + /** + * @description flag to show errors of parsing. + * @parameter expression="${commandLine.showError}" default-value="false" + */ + protected boolean showErrors; + /** + * @description flag to backup already generated class. + * @parameter expression="${commandLine.backupClass}" default-value="false" + */ + protected boolean backupClass; + + + public void execute() throws MojoExecutionException, MojoFailureException { + // get source (make sure to be on a absolute path) + source = new File(source.getAbsolutePath()); + + /*if (isUpToDate()) { + log.info(_("commandline.parser.uptodate",parserFullyQualifiedName)); + return; + }*/ + + CtClass clazz = null; + try { + // parse source + OptionDefinitionParser parser = doParse(); + + // show errors (@see showErrors) + showErrors(parser); + + if (parser.hasFailed()) { + throw new RuntimeException(_("commandline.parser.parsing.error", parser, parser.errors.length)); + } + + // init Class pool and make backups of generated class (@see backupClass) + ClassPool pool = initClassPool(); + + // instanciate a OptionParser generator + OptionParserGenerator parserGenerator = new OptionParserGenerator( + out.getAbsolutePath(), parserFullyQualifiedName, + generateOptionImplementation + ); + ApplicationA anno; + try { + // generate OptionParser implementation and return applicationA + anno = parserGenerator.generateOptionParser(pool, parser); + } catch (Exception e) { + throw new MojoExecutionException(e.getMessage(), e); + } + + // build usage rst file (@see generateRst) + generateRst(anno); + + } finally { + detachClazz(clazz); + } + } + + protected OptionDefinitionParser doParse() throws MojoFailureException { + + // make sure out exists + if (!out.exists()) { + out.mkdirs(); + } + // get type of source for parser + OptionDefinitionParser.TypeSource typeP = null; + try { + typeP = OptionDefinitionParser.TypeSource.valueOf(typeSource); + } catch (Exception e) { + // ignore here (will be treate by parser) + } + // do parse definitions + try { + OptionDefinitionParser parser = OptionDefinitionParser.doParse(typeP, source); + log.info(_("commandline.parser.result.info", parser, parser.options.length)); + log.info(_("commandline.parser.result2.info", parser, parser.arguments.length)); + return parser; + } catch (IOException e) { + throw new MojoFailureException(e.getMessage()); + } + } + + protected boolean isUpToDate() { + //TOOD check if parser exists, and is not older than source + long newtimestamp = source.lastModified(); + int lastIndex = parserFullyQualifiedName.lastIndexOf('.'); + String path; + if (lastIndex > -1) { + String c = File.separator; + if (c.equals("\\")) { + c = c + c; + } + path = parserFullyQualifiedName.replaceAll("\\.", c); + + } else { + path = parserFullyQualifiedName; + } + File f = new File(out, path+".class"); + return f.exists() && newtimestamp <= f.lastModified(); + } + + protected ClassPool initClassPool() throws MojoFailureException { + // create new root class pool + ClassPool pool = new ClassPool(null); + + // backup previously generated classes if asked + //TODO Backup also OptionImpl ? + try { + backupPreviousGeneratedClass(pool, parserFullyQualifiedName); + // we need the classpath of OptionParser, since we load it as super + // class of generated classe, make sure pool know it. + pool.appendClassPath(new ClassClassPath(OptionParser.class)); + // add also where classes are generated to make possible hot later load + // (while tests for example). + pool.appendClassPath(out.getAbsolutePath()); + return pool; + } catch (Exception e) { + throw new MojoFailureException(e.getMessage()); + } + } + + protected void generateRst(ApplicationA applicationA) throws MojoFailureException { + if (rstFilePath == null) { + return; + } + if (!rstFilePath.getParentFile().exists()) { + rstFilePath.getParentFile().mkdirs(); + } + Writer w; + try { + w = new BufferedWriter(new FileWriter(rstFilePath)); + } catch (IOException e) { + throw new MojoFailureException(e.getMessage()); + } + log.info(_("commandline.generateRstFile.info", rstFilePath)); + try { + String head = _("commandline.generateRstFile.head", projectName); + String prefixO = _("commandline.generateRstFile.options.head", projectName); + String prefixA = _("commandline.generateRstFile.arguments.head", projectName); + String prefix = _("commandline.generateRstFile.prefix") + ' '; + w.append(OptionParserUtil.toString(applicationA, head, prefixO, prefixA, prefix)); + } catch (IOException e) { + throw new MojoFailureException(e.getMessage()); + } finally { + if (w != null) { + try { + w.flush(); + w.close(); + } catch (IOException e) { + throw new MojoFailureException(e.getMessage()); + } + } + } + } + + protected void showErrors(OptionDefinitionParser parser) throws MojoFailureException { + if (showErrors) { + if (parser.hasFailed()) { + try { + Writer writer = new StringWriter(); + parser.printErrors(writer); + String lines = writer.toString(); + for (String s : lines.split("\n")) { + log.info(_("commandline.showErrors.info") + ' ' + s); + } + writer.flush(); + writer.close(); + } catch (IOException e) { + throw new MojoFailureException(e.getMessage()); + } + } else { + log.info(_("commandline.showErrors.no.error.info")); + } + } + } + + protected String buildMethodName(boolean booleanValueType, String methodName) { + methodName = (booleanValueType ? "is" : "get") + methodName; + return methodName; + } + + protected void addMethod(CtClass impl, String prefix, String methodName, String body) throws CannotCompileException { + CtMethod method; + String b = prefix + " " + methodName + "() {" + body + "}"; + //log.user(b); + method = CtMethod.make(b, impl); + impl.addMethod(method); + } + + protected void detachClazz(CtClass... impl) { + for (CtClass ctClass : impl) { + if (ctClass != null) { + ctClass.detach(); + } + } + } + + protected void backupPreviousGeneratedClass(ClassPool pool, String... classNames) throws NotFoundException, CannotCompileException, IOException { + if (!backupClass) { + return; + } + ClassPath cp = pool.insertClassPath(out.getAbsolutePath()); + String SUFFIX = "_" + System.currentTimeMillis(); + for (String name : classNames) { + try { + CtClass clazz = pool.get(name); + if (clazz != null) { + // rename this clazz to be safe + String newClassName = name + SUFFIX; + clazz.replaceClassName(name, newClassName); + log.info(_("commandline.backupClass", name, newClassName)); + clazz.writeFile(out.getAbsolutePath()); + } + } catch (NotFoundException e) { + // we are safe : class does not exists in class path, + // so no collision is possible :) + } + } + cp.close(); + pool.removeClassPath(cp); + } + +}