Index: maven-commandline-plugin/src/java/org/codelutin/option/def/DefinitionParserContexts.java diff -u /dev/null maven-commandline-plugin/src/java/org/codelutin/option/def/DefinitionParserContexts.java:1.1 --- /dev/null Sun Mar 23 00:51:26 2008 +++ maven-commandline-plugin/src/java/org/codelutin/option/def/DefinitionParserContexts.java Sun Mar 23 00:51:21 2008 @@ -0,0 +1,865 @@ +/* +* ##% Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 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 org.codelutin.option.def; + +import org.apache.commons.beanutils.Converter; +import static org.codelutin.i18n.I18n._; +import static org.codelutin.option.def.DefinitionParserUtil.CLOSING_CHARS; +import static org.codelutin.option.def.DefinitionParserUtil.CONFIG_PROPERTY_DEFINITION_PATTERN; +import static org.codelutin.option.def.DefinitionParserUtil.CONFIG_PROPERTY_DEFINITION_WITH_DEFAULT_PATTERN; +import org.codelutin.option.def.DefinitionParserUtil.ConfigDefEntry; +import static org.codelutin.option.def.DefinitionParserUtil.IDENTIFIER_PATTERN; +import static org.codelutin.option.def.DefinitionParserUtil.OPENING_CHARS; +import org.codelutin.util.CardinalityHelper; +import org.codelutin.util.ConverterUtil; +import org.codelutin.util.StringUtil; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.regex.Matcher; + +/** + * Cette classe contient les contexts de parsing utilisés pour construire des + * {@link OptionDefinition}. + * + * @author chemit + */ +public class DefinitionParserContexts { + + /** + * Le context princiapl du parseur. Aggrège les contextes OptionsContext et + * ConfigsContext. + * + * @author tony + */ + public static class ParserContext extends AbsractDefinitionContext { + + OptionsContext options; + ConfigsContext configs; + + protected ParserContext() { + // no parent, no sons + super(null, false); + } + + protected void parse() { + // parse options + options.parseOptions(); + // parse configs + configs.parseConfigs(); + } + + @Override + protected void clear() { + super.clear(); + options.clear(); + configs.clear(); + options = null; + configs = null; + } + + ConfigContext[] getConfigs() { + return configs.getContexts().toArray(new ConfigContext[configs.getContexts().size()]); + } + + OptionContext[] getOptions() { + return options.getContexts().toArray(new OptionContext[options.getContexts().size()]); + } + + void setOptions(OptionsContext options) { + this.options = options; + } + + void setConfigs(ConfigsContext configs) { + this.configs = configs; + } + } + + /** @author tony */ + public static class OptionsContext extends AbsractDefinitionContext { + + protected final String[] okeys; + protected final String[] odefinitions; + + /** la liste des alias d'option déjà rencontrées pendant le parsing */ + protected final List aliaKeyUsed; + + /** la liste des clefs d'option déjà rencontrées pendant le parsing */ + protected final List optionKeyUsed; + + /** la liste des clefs d'arguments d'options déjà rencontrées pendant le parsing */ + protected final List optionArgumentKeyUsed; + + OptionsContext(ParserContext context, String[] keys, String[] definitions) { + super(context, true); + this.okeys = keys; + this.odefinitions = definitions; + this.optionArgumentKeyUsed = new ArrayList(); + this.optionKeyUsed = new ArrayList(); + this.aliaKeyUsed = new ArrayList(); + } + + protected void parseOptions() { + // parse options + for (int i = 0, j = okeys.length; i < j; i++) { + String key = okeys[i]; + String definition = odefinitions[i]; + if (checkOptionSyntax(key, definition)) { + OptionContext optionContext = parseOption(key, definition); + addSon(optionContext); + } + } + } + + OptionContext parseOption(String key, String definition) { + if (log.isDebugEnabled()) { + log.info('[' + key + "] '" + definition + "'"); + } + + String[] strings = StringUtil.split(OPENING_CHARS, CLOSING_CHARS, definition, " "); + List tokens = new ArrayList(Arrays.asList(strings)); + + // by default, option is optional + int min = 0, max = 1; + String token = tokens.get(0).trim(); + // alias + String[] alias = token.split("\\|"); + tokens.remove(0); + + if (!tokens.isEmpty()) { + token = tokens.get(0).trim(); + if (token.startsWith("{") || token.startsWith("+") || token.startsWith("*")) { + Object[] tmp = CardinalityHelper.parseCardinalite(token, false); + min = (Integer) tmp[1]; + max = (Integer) tmp[2]; + tokens.remove(0); + } + } + + OptionContext context = new OptionContext(this, key, min, max, alias); + + if (!checkOption(alias, definition, min, max)) { + context.unvalidate(); + return context; + } + if (!tokens.isEmpty()) { + // l'option possede des arguments + for (String groupArgument : tokens) { + GroupContext group = context.parseGroup(groupArgument); + context.addSon(group); + } + } + return context; + } + + @Override + protected void postAddSonHook(OptionContext context) { + aliaKeyUsed.addAll(Arrays.asList(context.alias)); + optionKeyUsed.add(context.key); + optionArgumentKeyUsed.clear(); + } + + /** + * Examine une définition d'option avant parsing. + * + * @param key la clef de l'option à vérifier + * @param definition la définition de l'option à parser + * @return valid value after check + */ + boolean checkOptionSyntax(String key, String definition) { + boolean oldValid = valid; + // check definition is not empty + if (definition == null || definition.isEmpty() || definition.trim().isEmpty()) { + addError(_("lutinutil.parserdef.unvalid.syntax.empty.option.definition", definition)); + return false; + } + // check definition contains alias ? + if (definition.trim().indexOf('-') != 0) { + addError(_("lutinutil.parserdef.unvalid.syntax.unfound.alias.in.option", definition)); + } + // on vérifier que le nom n'est pas dupliqué + if (optionKeyUsed.contains(key)) { + addError(_("lutinutil.parserdef.duplicated.option.name", key, definition)); + } + // check same number {[< to >]} in definition + if (!StringUtil.checkEnclosure(definition, '{', '}')) { + addError(_("lutinutil.parserdef.unvalid.syntax.underbrace.option", definition)); + } + if (!StringUtil.checkEnclosure(definition, '<', '>')) { + addError(_("lutinutil.parserdef.unvalid.syntax.lesser.option", definition)); + } + if (!StringUtil.checkEnclosure(definition, '[', ']')) { + addError(_("lutinutil.parserdef.unvalid.syntax.caret.option", definition)); + } + return !oldValid || valid; + } + + boolean checkOption(String[] alias, String definition, int min, int max) { + if (alias.length > 4) { + addError(_("lutinutil.parserdef.too.much.alias.option", Arrays.toString(alias), definition)); + } + // on vérifier que les alias commencent tous par '-' et ne sont + // pas déjà utilisés + for (String alia : alias) { + if (!alia.startsWith("-")) { + addError(_("lutinutil.parserdef.unvalid.syntax.alias.option", alia, definition)); + } + if (aliaKeyUsed.contains(alia)) { + addError(_("lutinutil.parserdef.duplicated.option.alias", alia, definition)); + } + } + checkCardinalite(min, max, definition); + return valid; + } + + @Override + protected void clear() { + super.clear(); + optionKeyUsed.clear(); + optionArgumentKeyUsed.clear(); + aliaKeyUsed.clear(); + } + } + + + /** + * Cette classe contient une option à analyser. + *

+ * Elle est de visibilité package, car elle n'a pas à être utilisée ailleurs. + * + * @author chemit + */ + public static class OptionContext extends AbsractDefinitionContext { + + protected final String key; + protected final int min; + protected final int max; + protected final String[] alias; + + protected int currentPos = 0; + + OptionContext(OptionsContext parent, String key, + int min, int max, String[] alias) { + super(parent, true); + this.key = key; + this.min = min; + this.max = max; + this.alias = alias; + } + + GroupContext parseGroup(String groupDefinition) { + log.debug(groupDefinition); + + // on supprime tout espace en début ou fin de chaine de définition + groupDefinition = groupDefinition.trim(); + if (!checkGroup(groupDefinition)) { + // check failed + return null; + } + boolean mandatory = groupDefinition.startsWith("<"); + + // on supprime les délimitateurs de début et fin de chaine de définition + groupDefinition = groupDefinition.substring(1, groupDefinition.length() - 1); + + GroupContext groupContext; + groupContext = new GroupContext(this, mandatory ? currentPos : -1); + + // on split pour obtenir la définition de chaque argument + String[] argumentDefinitions = groupDefinition.split("\\|"); + for (String argumentDefinition : argumentDefinitions) { + ArgumentContext argumentContext = groupContext.parseArgument(argumentDefinition); + groupContext.addSon(argumentContext); + if (!groupContext.isValid()) { + break; + } + } + return groupContext; + } + + @Override + protected void postAddSonHook(GroupContext context) { + super.postAddSonHook(context); + if (context.pos > -1) { + // add a mandatory group increment currentPos + currentPos++; + } + } + + boolean checkGroup(String definition) { + definition = definition.trim(); + if (!(definition.startsWith("<") || definition.startsWith("[")) || + !(definition.endsWith(">") || definition.endsWith("]"))) { + addError(_("lutinutil.parserdef.unvalid.syntax.unknown.group.of.arguments", definition)); + } + return valid; + } + + public String getKey() { + return key; + } + + public List getGroups() { + return contexts; + } + + public int getMin() { + return min; + } + + public String[] getAlias() { + return alias; + } + + public int getMax() { + return max; + } + } + + /** + * Cette classe contient un groupe d'arguments d'une option à analyser. + *

+ * Elle est de visibilité package, car elle n'a pas à être utilisée ailleurs. + * + * @author chemit + */ + public static class GroupContext extends AbsractDefinitionContext { + + protected final int pos; + + /** la liste des type valués déjà rencontrés dans le groupe */ + protected final Collection valueTypesFound; + + GroupContext(OptionContext parent, int pos) { + super(parent, true); + this.pos = pos; + this.valueTypesFound = new ArrayList(); + } + + @Override + protected void postAddSonHook(ArgumentContext context) { + if (context.type == ArgumentType.valued) { + // on sauvegarde le type (pour les conflits possibles) + valueTypesFound.add(context.valueType); + } + // keep the key used + parent.getParent().optionArgumentKeyUsed.add(context.key); + } + + ArgumentContext parseArgument(String argumentDefinition) { + log.debug(argumentDefinition); + + ArgumentContext result = null; + + ArgumentType type = ArgumentType.findType(argumentDefinition); + + //contient la clef et le type + String[] keyAndType = type.explodeDefinition(argumentDefinition); + + //contient le type (STRING), min(INTEGER), max(INTEGER) + Object[] typeAndMinAndMax; + + boolean mandatory = pos > -1; + + if (type == ArgumentType.constant) { + // la clef contient la cardinalite + typeAndMinAndMax = CardinalityHelper.parseCardinalite(keyAndType[0], mandatory); + // on recupère la clef nettoye + keyAndType[0] = (String) typeAndMinAndMax[0]; + // on pousse le type connu + typeAndMinAndMax[0] = keyAndType[1]; + } else { + typeAndMinAndMax = CardinalityHelper.parseCardinalite(keyAndType[1], mandatory); + } + + // recherche de la classe d'implantation + ArgumentValueType valueType; + valueType = ArgumentValueType.findTypeFromDefinition((String) typeAndMinAndMax[0]); + + // check coherence of the new required argument definition + if (checkArgument(argumentDefinition, toString(), type, + valueType, keyAndType[0], + (Integer) typeAndMinAndMax[1], + (Integer) typeAndMinAndMax[2], mandatory)) { + result = new ArgumentContext(this, type, valueType, + keyAndType[0], (Integer) typeAndMinAndMax[1], + (Integer) typeAndMinAndMax[2]); + } + return result; + } + + boolean checkArgument(String definition, String groupementDefinition, + ArgumentType type, + ArgumentValueType valueType, + String key, int min, int max, boolean mandatory) { + if (type == null) { + addError(_("lutinutil.parserdef.unvalid.syntax.unknown.argument.type", definition, groupementDefinition)); + return false; + } + if (valueType == null) { + addError(_("lutinutil.parserdef.unvalid.syntax.unknown.value.type", definition, groupementDefinition)); + return false; + } + if (key == null) { + addError(_("lutinutil.parserdef.unfound.key", definition, groupementDefinition)); + return false; + } + if (!checkCardinalite(min, max, definition)) { + return false; + } + // check none duplicated option argument key + // application arguments keys are in a separate namespace + // and has already be checked in checkArgumentSyntax method + if (parent.getParent().optionArgumentKeyUsed.contains(key)) { + addError(_("lutinutil.parserdef.duplicated.argument.key", key, definition, parent.getParent().optionArgumentKeyUsed)); + } + // un argument de type constant ne peut pas avoir de cardinalite autre que {1,1} + // appliquable uniquement si c'est un argument d'option + if (type == ArgumentType.constant) { + if (mandatory && !(min == 1 && max == 1)) { + addError(_("lutinutil.parserdef.const.argument.mandatory.cardinalite", min, max, definition, groupementDefinition)); + } + if (!mandatory && !(min == 0 && max == 1)) { + addError(_("lutinutil.parserdef.const.argument.optional.cardinalite", min, max, definition, groupementDefinition)); + } + } else { + if (mandatory && min < 1) { + addError(_("lutinutil.parserdef.argument.mandatory.cardinalite", min, max, definition, groupementDefinition)); + } + if (!mandatory && min > 0) { + addError(_("lutinutil.parserdef.argument.optional.cardinalite", min, max, definition, groupementDefinition)); + } + } + if (type == ArgumentType.valued) { + // on vérifier qu'un type n'est pas dupliqué + if (valueTypesFound.contains(valueType)) { + addError(_("lutinutil.parserdef.duplicated.argument.valued.type", valueType, definition, groupementDefinition)); + } + switch (valueType) { + case BOOLEAN: + break; + case CLASS: + break; + case FILE: + // don't accept with NewFile + if (valueTypesFound.contains(ArgumentValueType.NEWFILE)) { + // never! + addError(_("lutinutil.parserdef.file.and.newfile.in.same.group", definition, groupementDefinition)); + } + break; + /*case FLOAT: + if (valueTypesFound.contains(ArgumentValueType.INTEGER)) { + // not for the moment + addError(_("lutinutil.parserdef.float.and.integer.in.same.group", definition, groupementDefinition)); + } + break; + case INTEGER: + if (valueTypesFound.contains(ArgumentValueType.FLOAT)) { + // not for the moment + addError(_("lutinutil.parserdef.float.and.integer.in.same.group", definition, groupementDefinition)); + } + break;*/ + case NEWFILE: + if (valueTypesFound.contains(ArgumentValueType.STRING)) { + // never! + addError(_("lutinutil.parserdef.string.and.newfile.in.same.group", definition, groupementDefinition)); + } + break; + case STRING: + if (valueTypesFound.contains(ArgumentValueType.NEWFILE)) { + // never! + addError(_("lutinutil.parserdef.string.and.newfile.in.same.group", definition, groupementDefinition)); + } + break; + } + } + //TODO better to count errors + return valid; + } + + @Override + protected void clear() { + super.clear(); + valueTypesFound.clear(); + } + + public int getPos() { + return pos; + } + + public List getArguments() { + return contexts; + } + + public boolean isValid() { + return valid; + } + } + + /** + * Cette classe contient le parser d'un argument d'un groupe d'une option.. + *

+ * Elle est de visibilité package, car elle n'a pas à être utilisée ailleurs. + * + * @author chemit + */ + public static class ArgumentContext extends AbsractDefinitionContext { + + protected final ArgumentType type; + + protected final ArgumentValueType valueType; + + protected final int min; + + protected final int max; + + protected final String key; + + ArgumentContext(GroupContext parent, + ArgumentType type, + ArgumentValueType valueType, + String key, Integer min, Integer max) { + super(parent, false); + this.type = type; + this.valueType = valueType; + this.key = key; + this.min = min; + this.max = max; + } + + public String getKey() { + return key; + } + + public ArgumentType getType() { + return type; + } + + public ArgumentValueType getValueType() { + return valueType; + } + + public int getMax() { + return max; + } + + public int getMin() { + return min; + } + } + + /** + * Pour représenter une liste de config + * + * @author tony + */ + public static class ConfigsContext extends AbsractDefinitionContext { + + + protected final String[] categories; + protected final Map> definitions; + protected final Map> modifiers; + + protected List safeCategories; + + protected ConfigsContext(ParserContext parent, String[] categories, + Map> definitions, + Map> modifiers) { + super(parent, true); + this.categories = categories; + this.definitions = definitions; + this.modifiers = modifiers; + } + + protected void parseConfigs() { + this.safeCategories = new ArrayList(); + Map _defs = new HashMap(); + HashMap _mods = new HashMap(); + for (String category : categories) { + List configDefs = definitions.get(category); + List configMods = modifiers.get(category); + _defs.clear(); + _mods.clear(); + if (checkConfigSyntax(category, configDefs, configMods, _defs, _mods)) { + ConfigContext context = new ConfigContext(this, category, _defs, _mods); + log.debug(context); + addSon(context); + } + } + } + + @Override + protected void preAddSonHook(ConfigContext context) { + super.preAddSonHook(context); + if (context.isValid()) { + context.parseProperties(); + } + } + + /** + * @param category la catégorie de la config + * @param configDefs les entrées de définitions de propriétés de la config + * @param configMods les entrées de modifiers de propriétés de la config + * @param _defs le dictionnaire de definition a construire + * @param _mods le dictionnaire de modifiers a construire + * @return true if tout est ok. + */ + protected boolean checkConfigSyntax(String category, List configDefs, List configMods, Map _defs, Map _mods) { + if (safeCategories.contains(category)) { + // fatal error + addError(_("lutinutil.error.parserdef.config.duplicated.category", category, safeCategories)); + return false; + } + // check valid syntax of the category + if (!IDENTIFIER_PATTERN.matcher(category).matches()) { + addError(_("lutinutil.error.parserdef.config.unvalid.syntax.category", category)); + return false; + } + + List keyUsed = new ArrayList(); + for (ConfigDefEntry entry : configDefs) { + String key = entry.getKey(); + // check if key is syntax valid + if (!IDENTIFIER_PATTERN.matcher(key).matches()) { + addError(_("lutinutil.error.parserdef.config.unvalid.syntax.property.key", key)); + return false; + } + // check if not already registred key + if (keyUsed.contains(key)) { + addError(_("lutinutil.error.parserdef.config.duplicated.property.key", key, category, keyUsed)); + return false; + } + String value = entry.getValue(); + // check syntax of definition type:defaultValue + Matcher matcher = CONFIG_PROPERTY_DEFINITION_PATTERN.matcher(value); + if (matcher.matches()) { + String type = matcher.group(0); + keyUsed.add(key); + _defs.put(key, new String[]{type}); + } else { + matcher = CONFIG_PROPERTY_DEFINITION_WITH_DEFAULT_PATTERN.matcher(value); + if (matcher.matches()) { + String type = matcher.group(1); + String defaultValue = matcher.group(matcher.groupCount()); + keyUsed.add(key); + _defs.put(key, new String[]{type, defaultValue}); + } else { + addError(_("lutinutil.error.parserdef.config.unvalid.syntax.property.definition", value, key, category)); + return false; + } + } + } + // check if there is no mods without a def + for (ConfigDefEntry entry : configMods) { + String key = entry.getKey(); + if (!keyUsed.contains(key)) { + // found a mod with no def + addError(_("lutinutil.error.parserdef.config.orphan.modifier", key, category, keyUsed)); + return false; + } + // check syntax of modifiers, [XXX,]*[XXX] + + String value = entry.getValue(); + String[] mods = value.split(","); + if (mods.length == 0) { + _mods.put(key, 0); + } + List modifiers = new ArrayList(); + for (String mod : mods) { + ConfigPropertyModifier val; + try { + val = ConfigPropertyModifier.valueOf(mod.toUpperCase()); + } catch (IllegalArgumentException e) { + addError(_("lutinutil.parserdef.unvalid.syntax.unknown.modifier",mod)); + return false; + } + if (modifiers.contains(val.getIntValue())) { + // duplicated modifier + addError(_("lutinutil.error.parserdef.config.duplicated.property.modifier", mod, key, category, Arrays.toString(mods))); + return false; + } + modifiers.add(val.getIntValue()); + } + int _mod = 0; + for (Integer modifier : modifiers) { + _mod |= modifier; + } + _mods.put(key, _mod); + } + return true; + } + + @Override + protected void clear() { + super.clear(); + definitions.clear(); + modifiers.clear(); + safeCategories.clear(); + safeCategories = null; + } + } + + /** + * Pour représenter une configuration + * + * @author tony + */ + public static class ConfigContext extends AbsractDefinitionContext { + + final protected String category; + final protected Map defs; + final protected TreeMap modifiers; + + protected ConfigContext(ConfigsContext parent, String category, Map defs, HashMap modifiers) { + super(parent, true); + this.category = category; + this.defs = new TreeMap(defs); + this.modifiers = new TreeMap(modifiers); + } + + @Override + public String toString() { + return getClass().getSimpleName() + " cat:" + category + ", keys:" + defs.keySet(); + } + + protected void parseProperties() { + for (String key : defs.keySet()) { + String[] vals = defs.get(key); + Integer mods = modifiers.get(key); + if (mods == null) { + mods = 0; + } + Class type = getType(key, vals[0]); + if (type != null) { + Converter converter = getConverter(key, type); + if (converter != null) { + boolean hasDefaultValue = vals.length == 2; + Object defaultValue = null; + if (hasDefaultValue) { + defaultValue = getDefaultValue(key, type, converter, vals[1]); + } + ConfigPropertyContext context = new ConfigPropertyContext(this, key, type, mods, defaultValue); + if (log.isDebugEnabled()) { + log.info(context); + } + addSon(context); + } + } + } + } + + + protected Class getType(String key, String fqn) { + try { + Class type; + type = Class.forName(fqn); + return type; + } catch (ClassNotFoundException e) { + // try a java.lang.fqn + addError(_("lutinutil.error.parserdef.config.type.unfound", fqn, key, category)); + return null; + } + } + + protected Converter getConverter(String key, Class type) { + Converter conv = ConverterUtil.getConverter(type); + if (conv == null) { + addError(_("lutinutil.error.parserdef.config.convert.unfound", type, key, category)); + return null; + } + return conv; + } + + protected Object getDefaultValue(String key, Class type, Converter converter, String defaultValue) { + // check there is a valid converter for this type ? + Object val = null; + if (defaultValue != null) { + val = converter.convert(type, defaultValue); + if (val == null) { + addError(_("lutinutil.error.parserdef.config.convert.defaultValue", defaultValue, type, key, category)); + return null; + } + } + return val; + } + + @Override + protected void clear() { + super.clear(); + defs.clear(); + modifiers.clear(); + } + + public boolean isValid() { + return valid; + } + + public String getCategory() { + return category; + } + + } + + /** + * Pour représenter une propriété de config + * + * @author tony + */ + public static class ConfigPropertyContext extends AbsractDefinitionContext { + protected final String key; + protected final Object defaultValue; + protected final Class type; + protected final int modifiers; + + protected ConfigPropertyContext(ConfigContext parent, String key, Class type, int modifiers, Object defaultValue) { + super(parent, false); + this.key = key; + this.type = type; + this.modifiers = modifiers; + this.defaultValue = defaultValue; + } + + @Override + public String toString() { + return "key:" + key + ", type:" + type.getName() + ", modifiers:" + ConfigPropertyModifier.toString(modifiers) + (defaultValue == null ? "" : (", defaultValue:" + defaultValue)); + } + + public String getKey() { + return key; + } + + public int getModifiers() { + return modifiers; + } + + public Class getType() { + return type; + } + + public Object getDefaultValue() { + return defaultValue; + } + } + + +} Index: maven-commandline-plugin/src/java/org/codelutin/option/def/DefinitionParser.java diff -u /dev/null maven-commandline-plugin/src/java/org/codelutin/option/def/DefinitionParser.java:1.1 --- /dev/null Sun Mar 23 00:51:27 2008 +++ maven-commandline-plugin/src/java/org/codelutin/option/def/DefinitionParser.java Sun Mar 23 00:51:21 2008 @@ -0,0 +1,154 @@ +/* +* ##% Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 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 org.codelutin.option.def; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import static org.codelutin.i18n.I18n._; +import static org.codelutin.option.def.DefinitionParserContexts.ConfigContext; +import static org.codelutin.option.def.DefinitionParserContexts.OptionContext; +import static org.codelutin.option.def.DefinitionParserContexts.ParserContext; + +import java.io.IOException; +import java.io.Writer; + +/** + * Parseur de définitions. TODO Revoir javadoc non correcte + *

+ * La classe contient une méthode statique publique : + * {@link #doParse(Class,Object)} + * pour lancer un parsing sur une source de définitions donnée. + *

+ * Dans ce fichier chaque option est représentée par 1 entrée : + *

+ * optionName.definition={0,2} --option|-o + * [groupArgumentFacultatif] + * + * @author chemit + */ + +public abstract class DefinitionParser { + + protected static final Log log = LogFactory.getLog(DefinitionParser.class); + + /** + * Initialise le parser avec son context remplit avec les données de la source. + * + * @param source la source de donnée + * @return le context de parseur initilisé avec les données de la source + * @throws IOException si problème pendant lecture de la source + */ + protected abstract ParserContext init(Object source) throws IOException; + + /** + * Effectue la parsing d'un fichier contenant les définitions des options. + *

+ * Retourne le parseur utilisé après le parsing. + *

+ * A ce stade, aunce definition ou annotation n'as été crée, on connait + * uniquement les contexts d'options valides. + *

+ * + * @param type le type de source à traiter. + * @param src la source contenant les définitions des options + * @return le parseur après parsing du fichier + * @throws IOException if any problem when reading source + * @throws IllegalAccessException if pb while init + * @throws InstantiationException if pb while init + */ + public static

P doParse(Class

type, Object src) throws IOException, IllegalAccessException, InstantiationException { + if (type == null) { + throw new IllegalArgumentException(_("lutinutil.parserdef.null.sourceType")); + } + if (src == null) { + throw new IllegalArgumentException(_("lutinutil.parserdef.null.source")); + } + + // obtain instance of implementation of parser + P parser = type.newInstance(); + + // init parser parser : reading from source + ParserContext context = parser.init(src); + + // parse and build options and configs contexts + context.parse(); + + // transfert valid option's contexts + parser.options = context.getOptions(); + + // transfert valid config's contexts + parser.configs = context.getConfigs(); + + // transfert errors from parser parser + parser.errors = context.getErrors().toArray(new DefinitionParserException[context.getNbErrors()]); + + // clean context + context.clear(); + + return parser; + } + + /** safe options contexts available after parsing */ + protected OptionContext[] options; + + /** safe configs contexts available after parsing */ + protected ConfigContext[] configs; + + /** errors found while parsing */ + protected DefinitionParserException[] errors; + + /** @return true if errors were detected while parsing */ + public boolean hasFailed() { + return errors != null && errors.length > 0; + } + + /** @return the valid option contexts */ + public OptionContext[] getOptions() { + return options; + } + + /** @return the valid config contexts */ + public ConfigContext[] getConfigs() { + return configs; + } + + /** @return les erreurs rencontrées pendant le parsing. */ + public DefinitionParserException[] getErrors() { + return errors; + } + + /** + * Imprime dans un writer les erreurs rencontrées pendant le parsing. + * + * @param w le writer à utiliser + * @throws IOException si problèmes d'écriture dans le writer + */ + public void printErrors(Writer w) throws IOException { + if (!hasFailed()) { + return; + } + w.append(_("lutinutil.parserdef.printError.head", errors.length)); + for (int i = 0, j = errors.length; i < j; i++) { + w.append(_("lutinutil.parserdef.printError.error", i + 1, j)); + w.append(errors[i].toString()); + } + } + +} Index: maven-commandline-plugin/src/java/org/codelutin/option/def/AbsractDefinitionContext.java diff -u /dev/null maven-commandline-plugin/src/java/org/codelutin/option/def/AbsractDefinitionContext.java:1.1 --- /dev/null Sun Mar 23 00:51:27 2008 +++ maven-commandline-plugin/src/java/org/codelutin/option/def/AbsractDefinitionContext.java Sun Mar 23 00:51:21 2008 @@ -0,0 +1,60 @@ +/** + * # #% Copyright (C) 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 org.codelutin.option.def; + +import org.codelutin.option.ParserUtil; + +/** + * Classe abstraite définissant un context de parser de définitions. + * + * @author tony + */ +public abstract class AbsractDefinitionContext

, S extends AbsractDefinitionContext> extends ParserUtil.AbstractParserContext { + + protected AbsractDefinitionContext(P parent, boolean withSons) { + super(parent, withSons); + } + + protected DefinitionParserException newError(String content, Exception e) { + DefinitionParserException e1; + if (e == null) { + e1 = new DefinitionParserException(content, this); + } else if (e instanceof DefinitionParserException) { + // propage exception + e1 = (DefinitionParserException) e; + } else { + e1 = new DefinitionParserException(content, e, this); + } + return e1; + } + + protected boolean checkCardinalite(int min, int max, String definition) { + int s = getNbErrors(); + if (min < 0) { + addError(org.codelutin.i18n.I18n._("lutinutil.parserdef.min.can.not.be.negative", min, definition)); + } + if (max == 0) { + addError(org.codelutin.i18n.I18n._("lutinutil.parserdef.max.can.not.be.zero", max, definition)); + } + if (max < -1) { // on vérifie que la cardinalité max >-2 + addError(org.codelutin.i18n.I18n._("lutinutil.parserdef.max.too.low", max, definition)); + } + if (max != -1 && max < min) { // on vérifie que la cardinalité max==-1 || max >= min + addError(org.codelutin.i18n.I18n._("lutinutil.parserdef.max.lowest.than.min", max, min, definition)); + } + return getNbErrors() == s; + } + +} Index: maven-commandline-plugin/src/java/org/codelutin/option/def/DefinitionParserException.java diff -u /dev/null maven-commandline-plugin/src/java/org/codelutin/option/def/DefinitionParserException.java:1.1 --- /dev/null Sun Mar 23 00:51:27 2008 +++ maven-commandline-plugin/src/java/org/codelutin/option/def/DefinitionParserException.java Sun Mar 23 00:51:21 2008 @@ -0,0 +1,66 @@ +/**##% + * 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.option.def; + +import java.util.ArrayList; +import java.util.List; + +/** + * Exception soulevable uniquement par le parser de définition d'option. + *

+ * Cette exception est de type runtime, car une telle exception est toujours + * fatale et le parseur de définition est exécuté indépendemment de toute + * application finale (voir maven-commandline-plugin). + * + * @author chemit + * @see DefinitionParser + */ +public class DefinitionParserException extends RuntimeException { // DefinitionParserException + + final AbsractDefinitionContext context; + private static final long serialVersionUID = 7683172805089232103L; + + public DefinitionParserException(String msg, AbsractDefinitionContext context) { + super(msg); + this.context = context; + } + + public DefinitionParserException(String msg, Throwable eee, AbsractDefinitionContext context) { + super(msg, eee); + this.context = context; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder().append(super.toString()).append("\nfrom contexts :"); + AbsractDefinitionContext cont = context; + List l = new ArrayList(); + String prefix = ""; + while (!cont.isRoot()) { + l.add(cont); + cont = (AbsractDefinitionContext) cont.getParent(); + } + l.add(cont); + for (AbsractDefinitionContext context : l) { + builder.append('\n').append(prefix).append(context); + prefix += " "; + } + return builder.toString(); + } +} // DefinitionParserException \ No newline at end of file Index: maven-commandline-plugin/src/java/org/codelutin/option/def/DefinitionParserUtil.java diff -u /dev/null maven-commandline-plugin/src/java/org/codelutin/option/def/DefinitionParserUtil.java:1.1 --- /dev/null Sun Mar 23 00:51:27 2008 +++ maven-commandline-plugin/src/java/org/codelutin/option/def/DefinitionParserUtil.java Sun Mar 23 00:51:21 2008 @@ -0,0 +1,111 @@ +/* +* ##% Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 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 org.codelutin.option.def; + +import org.codelutin.option.def.DefinitionParserContexts.ArgumentContext; +import org.codelutin.option.def.DefinitionParserContexts.GroupContext; +import org.codelutin.option.def.DefinitionParserContexts.OptionContext; + +import java.util.AbstractMap; +import java.util.regex.Pattern; + +/** + * Une classe pour définir des types et méthodes utiles pour le parseur de + * définition {@link DefinitionParser} + * + * @author tony + */ +public class DefinitionParserUtil { + /** FQN */ + public static final Pattern CONFIG_PROPERTY_DEFINITION_PATTERN = Pattern.compile("([a-z]+(\\.[a-zA-Z][a-zA-Z0-9]+)*)"); + /** FQN:defaultvalue */ + public static final Pattern CONFIG_PROPERTY_DEFINITION_WITH_DEFAULT_PATTERN = Pattern.compile("([a-z]+(\\.[a-zA-Z][a-zA-Z0-9]+)*)\\:(.*)"); + public static final Pattern IDENTIFIER_PATTERN = Pattern.compile("[a-zA-Z][a-zA-Z0-9]*"); + + public static final Character[] OPENING_CHARS = {'(', '{', '[', '<'}; + public static final Character[] CLOSING_CHARS = {')', '}', ']', '>'}; + + final static String ODEFINITION_KEY_SUFFIX = ".option.definition"; + final static String CDEFINITION_KEY_FACTOR = ".config.definition."; + final static String CMODIFIERS_KEY_FACTOR = ".config.modifiers."; + + static class ConfigDefEntry extends AbstractMap.SimpleEntry { + private static final long serialVersionUID = -5430904379928407190L; + String def; + String mods; + + //TODO use only one entry for a config property + public ConfigDefEntry(String key, String value) { + super(key, value); + } + } + + static class OptionDefEntry extends AbstractMap.SimpleEntry { + private static final long serialVersionUID = -5430904379928407190L; + String def; + String mods; + + public OptionDefEntry(String key, String value) { + super(key, value); + } + } + + /** + * This class defines an abstract Walker on + * {@link org.codelutin.option.def.DefinitionParser} contexts. + * + * @author chemit + */ + + public abstract static class DefinitionParserWalker { + + public abstract Object doWalk(OptionContext[] contexts); + + protected void walk(OptionContext[] options) { + for (int i = 0, i1 = options.length; i < i1; i++) { + OptionContext option = options[i]; + enterOption(option, i); + for (int j = 0, j1 = option.getContexts().size(); j < j1; j++) { + GroupContext group = option.getContexts().get(j); + enterGroup(group, j); + for (int k = 0, k1 = group.getContexts().size(); k < k1; k++) { + enterArgument(group.getContexts().get(k), k); + } + exitGroup(group, j); + } + exitOption(option, i); + } + } + + protected void enterOption(OptionContext option, int optionIndex) { + } + + protected void enterGroup(GroupContext group, int groupIndex) { + } + + protected void enterArgument(ArgumentContext argument, int argumentIndex) { + } + + protected void exitGroup(GroupContext group, int groupIndex) { + } + + protected void exitOption(OptionContext config, int optionIndex) { + } + } +}