Index: maven-commandline-plugin/src/java/org/codelutin/util/Clean.java diff -u /dev/null maven-commandline-plugin/src/java/org/codelutin/util/Clean.java:1.1 --- /dev/null Thu Nov 29 10:32:30 2007 +++ maven-commandline-plugin/src/java/org/codelutin/util/Clean.java Thu Nov 29 10:32:25 2007 @@ -0,0 +1,100 @@ +/* *##% + * Copyright (C) 2007 + * I18nPlugin, 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 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 java.io.File; + +/** + * Permet de nettoyer les fichiers générés. + * + * @author chemit + * @goal clean + * @phase clean + */ +public class Clean extends AbstractMojo { + static { + String language = System.getProperty("user.language"); + String country = System.getProperty("user.country"); + if (language != null && !"".equals(language)) { + if (country != null && !"".equals(country)) { + I18n.init(language, country); + } else { + I18n.init(language); + } + } + } + + /** Log */ + 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.factoryFullyQualifiedName}" + * @required + */ + protected String factoryFullyQualifiedName; + + /** + * @description nom du paquetage utilisé pour la génération des classes + * @parameter expression="${commandline.parserFullyQualifiedName}" + * @required + */ + protected String parserFullyQualifiedName; + + /** + * @description Target rst file. + * @parameter expression="${commandLine.rstFilePath}" default-value="${basedir}/src/site/fr/rst/${project.name}_usage.rst" + */ + protected File rstFilePath; + + + public void execute() throws MojoExecutionException, MojoFailureException { + + if (!out.exists()) { + out.mkdirs(); + } + + if (rstFilePath != null) { + // build usage rst file + rstFilePath.delete(); + } + } + +} \ No newline at end of file Index: maven-commandline-plugin/src/java/org/codelutin/util/Generator.java diff -u /dev/null maven-commandline-plugin/src/java/org/codelutin/util/Generator.java:1.1 --- /dev/null Thu Nov 29 10:32:30 2007 +++ maven-commandline-plugin/src/java/org/codelutin/util/Generator.java Thu Nov 29 10:32:25 2007 @@ -0,0 +1,400 @@ +/* *##% + * Copyright (C) 2007 + * I18nPlugin, 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.CtField; +import javassist.Modifier; +import javassist.NotFoundException; +import javassist.bytecode.AnnotationsAttribute; +import javassist.bytecode.ClassFile; +import javassist.bytecode.ConstPool; +import javassist.bytecode.FieldInfo; +import javassist.bytecode.annotation.Annotation; +import javassist.bytecode.annotation.AnnotationMemberValue; +import javassist.bytecode.annotation.ArrayMemberValue; +import javassist.bytecode.annotation.EnumMemberValue; +import javassist.bytecode.annotation.IntegerMemberValue; +import javassist.bytecode.annotation.MemberValue; +import javassist.bytecode.annotation.StringMemberValue; +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.OptionArgumentDefinitionA; +import org.codelutin.util.OptionParserAnnotationHelper.OptionDefinitionA; +import org.codelutin.util.OptionParserAnnotationHelper.OptionParserA; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 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 Generator extends AbstractMojo { + static { + String language = System.getProperty("user.language"); + String country = System.getProperty("user.country"); + if (language != null && !"".equals(language)) { + if (country != null && !"".equals(country)) { + I18n.init(language, country); + } else { + I18n.init(language); + } + } + } + + /** Log */ + 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 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.propertiesFile}" default-value="${basedir}/src/resources/${project.name}_options.properties" + */ + protected File propertiesFile; + + /** + * @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 show details of parsing. + * @parameter expression="${commandLine.showDetails}" default-value="false" + */ + protected boolean showDetails; + + /** + * @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; + + /** le parseur de définition */ + protected OptionDefinitionParser parser; + + private CtClass optionParserAClazz; + private CtClass optionDefinitionAClazz; + private CtClass optionArgumentDefinitionAClazz; + private CtClass typeClazz; + private CtClass valueTypeClazz; + + public void execute() throws MojoExecutionException, MojoFailureException { + + CtClass clazz = null; + try { + propertiesFile = new File(propertiesFile.getAbsolutePath()); + + if (!out.exists()) { + out.mkdirs(); + } + + // do parse definitions + parser = OptionDefinitionParser.doParse(propertiesFile); + + log.info(_("commandline.parser.result.info", parser, parser.getDefinitions().length)); + + ClassPool pool = new ClassPool(null); + + if (backupClass) { + // backup previously generated classes + 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)); + + // obtain some required class from pool + optionParserAClazz = pool.get(OptionParserA.class.getName()); + optionDefinitionAClazz = pool.get(OptionDefinitionA.class.getName()); + optionArgumentDefinitionAClazz = pool.get(OptionArgumentDefinitionA.class.getName()); + typeClazz = pool.get(OptionArgumentType.class.getName()); + valueTypeClazz = pool.get(OptionArgumentValueType.class.getName()); + //TODO Make it works! + pool.importPackage(optionParserAClazz.getPackageName()); + + // generate OptionParser implementation + generateOptionParser(pool); + + if (rstFilePath != null) { + // build usage rst file + generateRst(); + } + + if (showErrors) { + if (parser.hasFailed()) { + 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(); + } else { + log.info(_("commandline.showErrors.no.error.info")); + } + } + if (showDetails) { + StringWriter sb; + parser.printDetails(sb = new StringWriter()); + String lines = sb.toString(); + for (String s : lines.split("\n")) { + log.info(_("commandline.showDetails.info") + ' ' + s); + } + } + if (parser.hasFailed()) { + throw new RuntimeException(_("commandline.parser.parsing.error", parser, parser.getErrors().size())); + } + } catch (Exception e) { + String s = _("commandline.parser.fatal.error", parser, e); + //log.error(s); + e.printStackTrace(); + throw new MojoFailureException(s); + } finally { + if (clazz != null) { + clazz.detach(); + } + } + } + + protected void generateOptionParser(ClassPool pool) throws NotFoundException, CannotCompileException, IOException { + + CtClass superClazz = pool.get(OptionParser.class.getName()); + + // create new implementation of OptionParser + CtClass clazz = pool.makeClass(parserFullyQualifiedName, superClazz); + try { + // no more abstract, but now public + clazz.setModifiers(Modifier.PUBLIC); + + ClassFile cf = clazz.getClassFile(); + ConstPool constPool = cf.getConstPool(); + + // add OptionParser annotation on implementation + AnnotationsAttribute attribute = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag); + Annotation annotation = new Annotation(constPool, optionParserAClazz); + ArrayMemberValue keyArray = new ArrayMemberValue(new StringMemberValue("", constPool), constPool); + List listKeys = new ArrayList(); + + for (OptionDefinition definition : parser.getDefinitions()) { + String name = definition.getName(); + listKeys.add(new StringMemberValue(name, constPool)); + generateOptionAnnotation(clazz, constPool, definition, name); + } + keyArray.setValue(listKeys.toArray(new StringMemberValue[listKeys.size()])); + annotation.addMemberValue("keys", keyArray); + attribute.setAnnotation(annotation); + cf.addAttribute(attribute); + //TODO why do we need this? + cf.setVersionToJava5(); + + // save it in out + clazz.writeFile(out.getAbsolutePath()); + + log.info(_("commandline.parser.build.info", clazz.getName())); + } finally { + if (clazz != null) { + clazz.detach(); + } + if (superClazz != null) { + superClazz.detach(); + } + } + } + + private void generateOptionAnnotation(CtClass clazz, ConstPool constPool, OptionDefinition definition, String name) throws CannotCompileException, NotFoundException { + String fieldName = StringUtil.convertToConstantName(name); + log.info(_("commandline.parser.field.build.info", fieldName, name, parser)); + CtField fld; + fld = CtField.make("public static final String " + fieldName + "= \"" + name + "\";", clazz); + clazz.addField(fld); + + FieldInfo fieldInfo = fld.getFieldInfo(); + ConstPool field_cp = fieldInfo.getConstPool(); + fieldInfo.addAttribute(new AnnotationsAttribute(field_cp, AnnotationsAttribute.visibleTag)); + AnnotationsAttribute attribute = (AnnotationsAttribute) fieldInfo.getAttribute(AnnotationsAttribute.visibleTag); + Annotation annotation = new Annotation(field_cp, optionDefinitionAClazz); + + annotation.addMemberValue("key", new StringMemberValue(name, field_cp)); + annotation.addMemberValue("description", new StringMemberValue(definition.getDescription(), field_cp)); + annotation.addMemberValue("definition", new StringMemberValue(definition.getDefinition(), field_cp)); + annotation.addMemberValue("min", new IntegerMemberValue(field_cp, definition.getMin())); + annotation.addMemberValue("max", new IntegerMemberValue(field_cp, definition.getMax())); + ArrayMemberValue value = new ArrayMemberValue(new StringMemberValue("", constPool), field_cp); + List tmp = new ArrayList(); + for (String alia : definition.getAlias()) { + tmp.add(new StringMemberValue(alia, field_cp)); + } + value.setValue(tmp.toArray(new MemberValue[tmp.size()])); + annotation.addMemberValue("alias", value); + + value = generateArgumentAnnotation(definition, fieldInfo, field_cp); + + annotation.addMemberValue("arguments", value); + attribute.setAnnotation(annotation); + } + + private ArrayMemberValue generateArgumentAnnotation(OptionDefinition definition, FieldInfo fieldInfo, ConstPool field_cp) throws NotFoundException { + ArrayMemberValue value; + value = new ArrayMemberValue(new AnnotationMemberValue(field_cp), field_cp); + List annotationMemberValues = new ArrayList(); + + for (OptionArgumentDefinition optionArgumentDefinition : definition.getArguments()) { + AnnotationsAttribute attribute; + Annotation annotation; + attribute = (AnnotationsAttribute) fieldInfo.getAttribute(AnnotationsAttribute.visibleTag); + annotation = new Annotation(field_cp, optionArgumentDefinitionAClazz); + annotation.addMemberValue("key", new StringMemberValue(optionArgumentDefinition.getKey(), field_cp)); + EnumMemberValue enumMemberValue; + enumMemberValue = new EnumMemberValue(field_cp); + enumMemberValue.setType(typeClazz.getName()); + enumMemberValue.setValue(optionArgumentDefinition.getType().name()); + annotation.addMemberValue("type", enumMemberValue); + enumMemberValue = new EnumMemberValue(field_cp); + OptionArgumentValueType valueType = optionArgumentDefinition.getValueType(); + enumMemberValue.setType(valueTypeClazz.getName()); + enumMemberValue.setValue(valueType.name()); + annotation.addMemberValue("valueType", enumMemberValue); + annotation.addMemberValue("min", new IntegerMemberValue(field_cp, optionArgumentDefinition.getMin())); + annotation.addMemberValue("max", new IntegerMemberValue(field_cp, optionArgumentDefinition.getMax())); + annotation.addMemberValue("pos", new IntegerMemberValue(field_cp, optionArgumentDefinition.getPos())); + annotationMemberValues.add(new AnnotationMemberValue(annotation, field_cp)); + attribute.setAnnotation(annotation); + } + value.setValue(annotationMemberValues.toArray(new MemberValue[annotationMemberValues.size()])); + return value; + } + + protected void generateRst() throws IOException { + // asume where coming here rstFilePath is not null + assert rstFilePath != null; + if (!rstFilePath.getParentFile().exists()) { + rstFilePath.getParentFile().mkdirs(); + } + Writer writer = new BufferedWriter(new FileWriter(rstFilePath)); + log.info(_("commandline.generateRstFile.info", rstFilePath)); + try { + String txt = _("commandline.generateRstFile.head", projectName); + writer.append(txt); + for (int i = 0, j = txt.length() - 1; i < j; i++) { + writer.append('='); + } + writer.append('\n'); + String prefix = _("commandline.generateRstFile.option.head") + ' '; + List keys = new ArrayList(); + Map map = new HashMap(); + for (OptionDefinition definition : parser.getDefinitions()) { + keys.add(definition.getName()); + map.put(definition.getName(), definition); + } + Collections.sort(keys); + + for (String key : keys) { + OptionDefinition definition = map.get(key); + writer.append(prefix); + definition.printUsage(writer); + } + } finally { + if (writer != null) { + writer.flush(); + writer.close(); + } + } + } + + protected void backupPreviousGeneratedClass(ClassPool pool, String... classNames) throws NotFoundException, CannotCompileException, IOException { + + ClassPath cp = pool.insertClassPath(out.getAbsolutePath()); + + String SUFFIX = "_" + System.currentTimeMillis(); + for (String className : classNames) { + backupClass(className, pool, SUFFIX); + } + cp.close(); + pool.removeClassPath(cp); + } + + protected void backupClass(String name, ClassPool pool, String SUFFIX) throws CannotCompileException, IOException { + 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 + } + } + +}