Index: lutinutil/src/java/org/codelutin/option/def/DefinitionParserFromProperties.java diff -u /dev/null lutinutil/src/java/org/codelutin/option/def/DefinitionParserFromProperties.java:1.1 --- /dev/null Sun Dec 30 22:50:51 2007 +++ lutinutil/src/java/org/codelutin/option/def/DefinitionParserFromProperties.java Sun Dec 30 22:50:46 2007 @@ -0,0 +1,152 @@ +/* +* ##% 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 static org.codelutin.i18n.I18n._; +import static org.codelutin.option.def.DefinitionParserContexts.ConfigsContext; +import static org.codelutin.option.def.DefinitionParserContexts.OptionsContext; +import static org.codelutin.option.def.DefinitionParserContexts.ParserContext; +import org.codelutin.option.def.DefinitionParserUtil.ConfigDefEntry; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +/** + * Implantation d'un parseur de définition, ayant pour source un fichier de propriété. + * + * @author tony + */ +public class DefinitionParserFromProperties extends DefinitionParser { + + final static String ODEFINITION_KEY_SUFFIX = ".option.definition"; + final static String CDEFINITION_KEY_FACTOR = ".config.definition."; + final static String CMODIFIERS_KEY_FACTOR = ".config.modifiers."; + final static int CDEFINITION_KEY_FACTOR_LENGTH = CDEFINITION_KEY_FACTOR.length(); + final static int ODEFINITION_KEY_PREFIX_LENGTH = ODEFINITION_KEY_SUFFIX.length(); + + /** + * Initialise un contexte de parseur à partir d'un fichier de propriétés. + * + * @param src la source où lire les définitions (un fichier de propriétés) + * @return un contexte de parseur avec les définitions lues + * @throws java.io.IOException if any problem while reading source + */ + protected DefinitionParserContexts.ParserContext init(Object src) throws IOException { + File source = (File) src; + if (!source.exists()) { + throw new IllegalArgumentException(_("lutinutil.parserdef.unfound.source", source)); + } + + // load properties file + Properties properties = new Properties(); + InputStream stream = null; + try { + stream = new BufferedInputStream(new FileInputStream(source)); + properties.load(stream); + } finally { + stream.close(); + } + + // récupération des clefs d'options + List okeys = new ArrayList(); + List categories = new ArrayList(); + Map> cdefinitions = new HashMap>(); + Map> cmodifiers = new HashMap>(); + for (Object o : properties.keySet()) { + String key = o.toString(); + if (key.endsWith(ODEFINITION_KEY_SUFFIX)) { + String s = key.substring(0, key.length() - ODEFINITION_KEY_PREFIX_LENGTH); + okeys.add(s); + continue; + } + if (key.contains(CDEFINITION_KEY_FACTOR)) { + int pos = key.indexOf(CDEFINITION_KEY_FACTOR); + String cat = key.substring(0, pos); + String ckey = key.substring(pos + CDEFINITION_KEY_FACTOR_LENGTH); + List defs, mods; + if (!categories.contains(cat)) { + // found a new config category + categories.add(cat); + cdefinitions.put(cat, defs = new ArrayList()); + cmodifiers.put(cat, mods = new ArrayList()); + } else { + defs = cdefinitions.get(cat); + mods = cmodifiers.get(cat); + } + String def = properties.getProperty(key); + ConfigDefEntry cdef = new ConfigDefEntry(ckey, def); + defs.add(cdef); + String mod = properties.getProperty(cat + CMODIFIERS_KEY_FACTOR + ckey); + if (mod != null && !"".equals(mod)) { + // only save modifiers if we found one + ConfigDefEntry cmod = new ConfigDefEntry(ckey, mod); + mods.add(cmod); + } + } + } + + // on trie les clefs d'options une seule fois + // ensuite on travaille sur cet ordre établi. + Collections.sort(okeys); + + // on trie les catégories une seule fois + // ensuite on travaille sur cet ordre établi. + Collections.sort(categories); + + List odefinitions = new ArrayList(); + // récupérations des définitions d'options + for (String optionKey : okeys) { + odefinitions.add(properties.getProperty(optionKey + ODEFINITION_KEY_SUFFIX)); + } + + ParserContext context = new ParserContext(); + + // construction du context de parsing des options + OptionsContext options = new OptionsContext( + context, + okeys.toArray(new String[okeys.size()]), + odefinitions.toArray(new String[okeys.size()]) + ); + + // construction du context de parsing des configs + ConfigsContext configs = new ConfigsContext( + context, + categories.toArray(new String[categories.size()]), + cdefinitions, + cmodifiers); + + context.setOptions(options); + context.setConfigs(configs); + + okeys.clear(); + categories.clear(); + odefinitions.clear(); + properties.clear(); + return context; + } +} Index: lutinutil/src/java/org/codelutin/option/def/package.html diff -u /dev/null lutinutil/src/java/org/codelutin/option/def/package.html:1.1 --- /dev/null Sun Dec 30 22:50:51 2007 +++ lutinutil/src/java/org/codelutin/option/def/package.html Sun Dec 30 22:50:46 2007 @@ -0,0 +1,31 @@ + + +

Lutin util option definition

+Ce paquetage contient l'ensemble des classes nécessaires à la construction des +définitions d'option. +

+On retrouve toutes les classes de définition : +

    +
  • OptionDefinition : la définition d'une option
  • +
  • GroupDefinition : la définition d'un groupe d'argument + d'une option +
  • +
  • ArgumentDefinition : la définition d'un argument d'option +
  • +
  • ArgumentType : le type d'un argument d'option
  • +
  • ArgumentValueType : le type de la valeur d'un argument + d'une option +
  • +
+On y retrouve le parseur de définition AbstractDefinitionParser, +ses implantations (uniquement DefinitionParserFromProperties pour +le moment) et les contextes qui lui sont associés +DefinitionParserContexts + +

+On introduit la classe DefinitionBuilder pour construire des +définitions d'options par programmation (utilisé par le générateur de +OptionParser). +

+ + \ No newline at end of file Index: lutinutil/src/java/org/codelutin/option/def/DefinitionParserException.java diff -u /dev/null lutinutil/src/java/org/codelutin/option/def/DefinitionParserException.java:1.1 --- /dev/null Sun Dec 30 22:50:51 2007 +++ lutinutil/src/java/org/codelutin/option/def/DefinitionParserException.java Sun Dec 30 22:50:46 2007 @@ -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 DefinitionParserUtil.AbsractDefinitionContext context; + private static final long serialVersionUID = 7683172805089232103L; + + public DefinitionParserException(String msg, DefinitionParserUtil.AbsractDefinitionContext context) { + super(msg); + this.context = context; + } + + public DefinitionParserException(String msg, Throwable eee, DefinitionParserUtil.AbsractDefinitionContext context) { + super(msg, eee); + this.context = context; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder().append(super.toString()).append("\nfrom contexts :"); + DefinitionParserUtil.AbsractDefinitionContext cont = context; + List l = new ArrayList(); + String prefix = ""; + while (!cont.isRoot()) { + l.add(cont); + cont = (DefinitionParserUtil.AbsractDefinitionContext) cont.getParent(); + } + l.add(cont); + for (DefinitionParserUtil.AbsractDefinitionContext context : l) { + builder.append('\n').append(prefix).append(context); + prefix += " "; + } + return builder.toString(); + } +} // DefinitionParserException \ No newline at end of file Index: lutinutil/src/java/org/codelutin/option/def/GroupDefinition.java diff -u /dev/null lutinutil/src/java/org/codelutin/option/def/GroupDefinition.java:1.1 --- /dev/null Sun Dec 30 22:50:51 2007 +++ lutinutil/src/java/org/codelutin/option/def/GroupDefinition.java Sun Dec 30 22:50:46 2007 @@ -0,0 +1,75 @@ +/** + * ##% 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 org.codelutin.util.CardinalityHelper; + +/** + * Cette classe représente la définition d'un groupe d'arguments d'une option. + *

+ * Elle comprend : + *

    + *
  • sa position {@link #pos} (si argument dans un groupe + * d'arguments obligatoires) , sinon -1
  • + *
+ * De manière générale cette classe ne doit pas être instanciée directement, + * cela est fait automatiquement lors du parsing de la définition de l'option + * par le parseur ou dans les factory de définitions. + *

+ * Pour l'instant les cardinalités sur groupe se limite à {0,1} : facultatif, + * et {1,1} obligatoire exacement une fois. + *

+ * Il suffira de modifier les algorithmes des parseurs pour intégrer la gestion + * des cardinalités sur les groupes d'arguments d'option. A faire. + * + * @author chemit + */ +public class GroupDefinition { + + public static final int OPTIONAL_POSITION = -1; + + /** la position du groupe d'argument dans l'option si obligatoire, -1 sinon */ + protected int pos; + + /** la liste de tous les argument du groupe */ + protected ArgumentDefinition[] arguments; + + public GroupDefinition(int pos, ArgumentDefinition[] arguments) { + this.pos = pos; + this.arguments = arguments; + } + + public int getPos() { + return pos; + } + + public boolean isMandatory() { + return pos != OPTIONAL_POSITION; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + StringBuffer s = new StringBuffer(); + for (ArgumentDefinition argument : arguments) { + s.append('|').append(argument); + } + String s1 = s.toString(); + CardinalityHelper.printCardinalite(sb, s1.length() > 0 ? s1.substring(1) : s1, isMandatory() ? 1 : 0, 1, isMandatory(), "<", ">", "[", "]"); + return sb.toString(); + } + + public ArgumentDefinition[] getArguments() { + return arguments; + } +} \ No newline at end of file Index: lutinutil/src/java/org/codelutin/option/def/OptionDefinitionBuilder.java diff -u /dev/null lutinutil/src/java/org/codelutin/option/def/OptionDefinitionBuilder.java:1.1 --- /dev/null Sun Dec 30 22:50:51 2007 +++ lutinutil/src/java/org/codelutin/option/def/OptionDefinitionBuilder.java Sun Dec 30 22:50:46 2007 @@ -0,0 +1,122 @@ +/* +* ##% 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.option.def.ArgumentType.constant; +import static org.codelutin.option.def.ArgumentValueType.STRING; + +/** + * Une classe pour construire les définitions d'options d'un parseur par + * programmation. + * + * @author chemit + */ +public class OptionDefinitionBuilder { + + protected static final Log log = LogFactory.getLog(OptionDefinitionBuilder.class); + + private OptionDefinition[] options; + private GroupDefinition[] groups; + private ArgumentDefinition[] arguments; + + private int posOption, posGroup, posArgument; + + private boolean openOption, openGroup; + + public OptionDefinitionBuilder beginBuilder(int nbOptions) { + log.debug("nbOptions: " + nbOptions); + options = new OptionDefinition[nbOptions]; + posOption = 0; + return this; + } + + public OptionDefinitionBuilder endBuilder() { + checkClosed(); + log.debug(""); + return this; + } + + public OptionDefinitionBuilder beginOption(int nbGroups) { + log.debug("nbGroups: " + nbGroups); + groups = new GroupDefinition[nbGroups]; + posGroup = 0; + openOption = true; + return this; + } + + public OptionDefinitionBuilder beginGroup(int nbArguments) { + log.debug("nbArguments: " + nbArguments); + arguments = new ArgumentDefinition[nbArguments]; + posArgument = 0; + openGroup = true; + return this; + } + + public OptionDefinitionBuilder addArgument(ArgumentType type, ArgumentValueType valueType, String key, int min, int max) { + ArgumentDefinition argumentDefinition = new ArgumentDefinition(type, valueType, key, min, max); + log.debug(posArgument + " : " + argumentDefinition); + arguments[posArgument++] = argumentDefinition; + return this; + } + + public OptionDefinitionBuilder addConstantArgument(String key, int min) { + ArgumentDefinition argumentDefinition = new ArgumentDefinition(constant, STRING, key, min, 1); + log.debug(posArgument + " : " + argumentDefinition); + arguments[posArgument++] = argumentDefinition; + return this; + } + + public OptionDefinitionBuilder endGroup(int pos) { + GroupDefinition groupDefinition = new GroupDefinition(pos, arguments); + log.debug(posGroup + " : " + groupDefinition); + groups[posGroup++] = groupDefinition; + openGroup = false; + return this; + } + + public OptionDefinitionBuilder endOption(String key, int min, int max, String... alias) { + OptionDefinition optionDefinition = new OptionDefinition(key, min, max, alias, groups); + log.debug(posOption + " : " + optionDefinition); + options[posOption++] = optionDefinition; + openOption = false; + return this; + } + + public OptionDefinition[] getDefinition() { + checkClosed(); + return options; + } + + public void clear() { + options = null; + groups = null; + arguments = null; + } + + private void checkClosed() { + if (openGroup) { + throw new IllegalStateException("a group is not closed!"); + } + if (openOption) { + throw new IllegalStateException("a option is not closed!"); + } + } +} Index: lutinutil/src/java/org/codelutin/option/def/DefinitionParserContexts.java diff -u /dev/null lutinutil/src/java/org/codelutin/option/def/DefinitionParserContexts.java:1.1 --- /dev/null Sun Dec 30 22:50:51 2007 +++ lutinutil/src/java/org/codelutin/option/def/DefinitionParserContexts.java Sun Dec 30 22:50:46 2007 @@ -0,0 +1,862 @@ +/* +* ##% 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 org.codelutin.option.def.DefinitionParserUtil.AbsractDefinitionContext; +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) { + 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=null; + 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); + 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 getClass().getSimpleName() + " 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: lutinutil/src/java/org/codelutin/option/def/ArgumentType.java diff -u /dev/null lutinutil/src/java/org/codelutin/option/def/ArgumentType.java:1.1 --- /dev/null Sun Dec 30 22:50:52 2007 +++ lutinutil/src/java/org/codelutin/option/def/ArgumentType.java Sun Dec 30 22:50:46 2007 @@ -0,0 +1,161 @@ +package org.codelutin.option.def; + +/** + * le type d'un argument d'option. + *

+ * La classe offre une méthode publique statique : + *

+ * {@link #findType(String)} pour trouver un type à partir de la + * définition d'un argument. + *

+ * Chaque constante offre deux méthodes publiques : + *

+ * {@link #explodeDefinition(String)} pour obtenir le couple + * [clef d'argument, type d'argument] à partir de la définition + * d'un argument. + *

+ * {@link #extractArgumentValue(String)} pour obtenir la valeur d'un + * argument de la ligne de commande. + * + * @author chemit + */ +public enum ArgumentType { + /** + * Dans la définition d'un argument , ce type est représenté sous la forme + *

+ *

constantKey
+ *

+ *

+ * La clef d'un tel argument est constantKey. + *

+ * Son type est toujours {@link ArgumentValueType#STRING}. + *

+ * Pour accepter cet argument de la ligne de commande, il faut entrer + * extactement constantKey. + * + * @see ArgumentValueType#matchType(String) + * @see ArgumentValueType#findTypeFromDefinition(String) + * @see ArgumentValueType#findTypeFromArgument(String) + */ + constant() { + public String[] explodeDefinition(String argumentDefinition) { + return new String[]{argumentDefinition, ArgumentValueType.STRING.toString()}; + } + public String extractArgumentValue(String argument) { + return argument; + } + }, + /** + * Dans la définition d'un argument , ce type est représenté sous la forme + *

+ *

valuedKey:type
+ *

+ * La clef d'un tel argument est valuedKey. + *

+ * Son type est celui mappe sur type. + *

+ * Pour accepter cet argument de la ligne de commande, il faut entrer + * extactement un argument vérifiant le type. + * + * @see ArgumentValueType#matchType(String) + * @see ArgumentValueType#findTypeFromDefinition(String) + * @see ArgumentValueType#findTypeFromArgument(String) + */ + valued() { + public String[] explodeDefinition(String argumentDefinition) { + int index = argumentDefinition.indexOf(':'); + return new String[]{argumentDefinition.substring(0, index), + argumentDefinition.substring(index + 1)}; + } + public String extractArgumentValue(String argument) { + return argument; + } + }, + /** + * Dans la définition d'un argument , ce type est représenté sous la forme + *

+ *

namedAndValuedKey=type
+ *

+ * La clef d'un tel argument est namedAndValuedKey. + *

+ * Son type est celui mappe sur type. + *

+ * Pour accepter cet argument de la ligne de commande, il faut entrer + * extactement un argument de la forme + *

namedAndValuedKey=valeur, où + * type doit vérifier le type mappé sur type + *
+ * + * @see ArgumentValueType#matchType(String) + * @see ArgumentValueType#findTypeFromDefinition(String) + * @see ArgumentValueType#findTypeFromArgument(String) + */ + namedAndValued() { + public String[] explodeDefinition(String argumentDefinition) { + int index = argumentDefinition.indexOf('='); + return new String[]{argumentDefinition.substring(0, index), + argumentDefinition.substring(index + 1)}; + } + public String extractArgumentValue(String argument) { + int index = argument.indexOf('='); + return index == -1 || index == argument.length() - 1 ? "" + : argument.substring(index + 1); + } + }; + + /** + * Cherche un type de d'argument à partir d'un argument de sa définition : + * + * @param argumentDefinition le type de valeur recherché + * @return le type trouvé, ou null si la définition est vide. + */ + public static ArgumentType findType(String argumentDefinition) { + // on effectue ici un controle + if (argumentDefinition == null || argumentDefinition.isEmpty() || + (argumentDefinition = argumentDefinition.trim()).isEmpty()) { + return null; + } + if (argumentDefinition.indexOf(':') > -1) { + // on a un valued type + return valued; + } + if (argumentDefinition.indexOf('=') > -1) { + // on a un named valued type + return namedAndValued; + } + return constant; + } + + /** + * Pour exploser une définition d'argument en un + * couple [clef argument,type argument]. + *

+ * Pour les arguments de type {@link ArgumentType#constant}, + * on retourne [argument,"STRING"] puisque tous les arguments de type + * constant sont de type string. + *

+ * Pour les arguments de type {@link ArgumentType#valued}, + * on retourne [clef,type] (la définition est de la forme clef:type). + *

+ * Pour les arguments de type {@link ArgumentType#namedAndValued}, + * on retourne [clef,type] (la définition est de la forme clef=type). + * + * @param argumentDefinition la définition de l'argument + * @return le couple [clef,type] trouvé à partir de la définition + */ + public abstract String[] explodeDefinition(String argumentDefinition); + + /** + * Extrait d'un argument de la ligne de commande, la valeur de l'argument. + *

+ * Pour les arguments de type {@link ArgumentType#constant} et + * {@link ArgumentType#valued}, il s'agit de tout l'argument, alors + * que pour le type {@link ArgumentType#namedAndValued}, il s'agit de + * ce qui suit le caractère '=' dans l'argument (représenté sous la + * forme clef=valeur). + * + * @param argument l'argument de la ligne de commande à traiter + * @return la valeur extraite de l'argument + */ + public abstract String extractArgumentValue(String argument); +} Index: lutinutil/src/java/org/codelutin/option/def/DefinitionParserUtil.java diff -u /dev/null lutinutil/src/java/org/codelutin/option/def/DefinitionParserUtil.java:1.1 --- /dev/null Sun Dec 30 22:50:52 2007 +++ lutinutil/src/java/org/codelutin/option/def/DefinitionParserUtil.java Sun Dec 30 22:50:46 2007 @@ -0,0 +1,175 @@ +/* +* ##% 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 static org.codelutin.i18n.I18n._; +import org.codelutin.option.ParserUtil; +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 = {')', '}', ']', '>'}; + + /** + * permet de définir le type de source pour lire les définitions à analyser. + *

+ * Pour le moment seul le type {@link #properties} est implanté. + * + * @author chemit + */ + public enum TypeSource { + /** pour lire à partir d'un fichier de propriétés (mode par défaut). */ + properties(DefinitionParserFromProperties.class), + /** pour lire à partir d'un fichier xml (pas implanter). */ + xml(null), + /** pour lire depuis un InputReader (pas implanter) */ + console(null); + + private Class impl; + + TypeSource(Class aClass) { + this.impl = aClass; + } + + public DefinitionParser newParserInstance() { + if (impl == null) { + throw new IllegalArgumentException(_("lutinutil.parserdef.notimplemented.sourceType")); + } + try { + return impl.newInstance(); + } catch (InstantiationException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + } + + /** + * Classe abstraite définissant un context de parser de définitions. + * + * @author tony + */ + public abstract static 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(_("lutinutil.parser.min.can.not.be.negative", min, definition)); + } + if (max == 0) { + addError(_("lutinutil.parser.max.can.not.be.zero", max, definition)); + } + if (max < -1) { // on vérifie que la cardinalité max >-2 + addError(_("lutinutil.parser.max.too.low", max, definition)); + } + if (max != -1 && max < min) { // on vérifie que la cardinalité max==-1 || max >= min + addError(_("lutinutil.parser.max.lowest.than.min", max, min, definition)); + } + return getNbErrors() == s; + } + + } + + static class ConfigDefEntry extends AbstractMap.SimpleEntry { + private static final long serialVersionUID = -5430904379928407190L; + + public ConfigDefEntry(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) { + } + } +} Index: lutinutil/src/java/org/codelutin/option/def/ArgumentValueType.java diff -u /dev/null lutinutil/src/java/org/codelutin/option/def/ArgumentValueType.java:1.1 --- /dev/null Sun Dec 30 22:50:52 2007 +++ lutinutil/src/java/org/codelutin/option/def/ArgumentValueType.java Sun Dec 30 22:50:46 2007 @@ -0,0 +1,169 @@ +package org.codelutin.option.def; + +import java.io.File; +import static java.lang.Boolean.FALSE; +import static java.lang.Boolean.TRUE; + +/** + * le type de la valeur d'un argument d'option. + *

+ * La classe offre deux méthodes statiques publiques pour trouver un tel type : + *

+ * {@link #findTypeFromDefinition(String)} pour trouver un type à partir d'un + * type détecté dans la définition d'un argument. + *

+ * {@link #findTypeFromArgument(String)} pour trouver un type à partir d'un + * argument de la ligne de commande. + *

+ * * Chaque constante offre deux méthodes publiques : + *

+ * {@link #matchType(String)} pour détecter si la valeur d'un argument de la + * ligne de commande correspond à un type. + *

+ * * {@link #getClazz()} } pour retourner la class d'implantation du type. + * + * @author chemit + */ +public enum ArgumentValueType { + /** + * Argument de type boolean. + *

+ * Valeurs autorisées : false|true + */ + BOOLEAN(Boolean.class) { + public boolean matchType(String arg) { + return super.matchType(arg) && + (arg.equalsIgnoreCase(FALSE.toString()) || + arg.equalsIgnoreCase(TRUE.toString())); + } + }, + /** + * Argument de type entier. + *

+ * Valeurs autorisées : tout {@link java.lang.Long} + */ + INTEGER(Integer.class) { + public boolean matchType(String arg) { + return super.matchType(arg) && arg.matches("[0-9]+"); + } + }, + /** + * Argument de type decimale. + *

+ * Valeurs autorisées : tout {@link java.lang.Double} + */ + FLOAT(Double.class) { + public boolean matchType(String arg) { + return super.matchType(arg) && arg.matches("[0-9]+\\.[0-9]+"); + } + }, + /** + * Argument de type class. + *

+ * Valeurs autorisées : tout nom complet de classe (existante ou non) + */ + CLASS(Class.class) { + public boolean matchType(String arg) { + if (!super.matchType(arg)) { + return false; + } + try { + java.lang.Class.forName(arg); + return true; + } catch (ClassNotFoundException e) { + // on essaye quand même de matcher une classe par sa syntaxe ? + return arg.matches("(\\w+\\.)*[A-Z]\\w+"); + } + } + }, + /** + * Argument de type fichier existant. + *

+ * Valeurs autorisées : tout path to fichier existant + */ + FILE(File.class) { + public boolean matchType(String arg) { + return super.matchType(arg) && new File(arg).exists(); + } + }, + /** + * Argument de type fichier. + *

+ * Valeurs autorisées : tout path de fichier (existant, ou non) + */ + NEWFILE(File.class) { + public boolean matchType(String arg) { + //TODO Find a way to distinguish this from STRING... + return super.matchType(arg); + } + }, + + /** + * Argument de type string. + *

+ * Valeurs autorisées : tout! (sauf chaine vide) + */ + STRING(String.class); + + /** + * Cherche un type de valeur d'argument à partir d'un type détecté dans la + * définition d'un argument de manière laxiste : + *

+ * la nom du type n'est pas case sensitive, il peut être entouré d'espace. + *

+ * Par contre il ne peut pas être vide ou null. + * + * @param type le type de valeur recherché + * @return le type trouvé ou null si n'existe pas + */ + public static ArgumentValueType findTypeFromDefinition(String type) { + // on effectue ici un controle + if (type == null || type.isEmpty() || (type = type.trim()).isEmpty()) { + return null; + } + //STRING tmp = "_" + type.toLowerCase(); + for (ArgumentValueType valueType : values()) { + if (valueType.name().equalsIgnoreCase(type)) { + return valueType; + } + } + return null; + } + + /** + * Cherche un type de valeur d'argument à partir d'un argument de + * la ligne de commande : + * + * @param arg le type de valeur recherché + * @return le type trouvé ou null si n'existe pas + */ + public static ArgumentValueType findTypeFromArgument(String arg) { + for (ArgumentValueType type : values()) { + if (type.matchType(arg)) { + return type; + } + } + return null; + } + + + /** @return la class d'implantation du type */ + public Class getClazz() { + return clazz; + } + + /** + * @param arg l'argument dont on veut tester le type + * @return true si l'arg donné est du bon type + */ + public boolean matchType(String arg) { + return arg != null && !arg.isEmpty(); + } + + /** la classe d'implantation du type */ + private final Class clazz; + + ArgumentValueType(Class clazz) { + this.clazz = clazz; + } +} Index: lutinutil/src/java/org/codelutin/option/def/OptionDefinition.java diff -u /dev/null lutinutil/src/java/org/codelutin/option/def/OptionDefinition.java:1.1 --- /dev/null Sun Dec 30 22:50:52 2007 +++ lutinutil/src/java/org/codelutin/option/def/OptionDefinition.java Sun Dec 30 22:50:46 2007 @@ -0,0 +1,142 @@ +/** + * ##% 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 org.codelutin.util.CardinalityHelper; + +/** + * Classe qui représente la définition d'une option. + *

+ * Une option est définition par : + *

    + *
  • un nom {@link #key} qui doit être unique (il s'agit d'un identifiant interne)
  • + *
  • un ensemble d'alias {@link #alias} (--option-o,...) qui commencent tous par un '-'
  • + *
  • une cardinalité {@link #min} {@link #max}
  • + *
  • un ensemble de groupes d'arguments {@link #groups}
  • + *
+ * + * @author chemit + * @see ArgumentType + * @see ArgumentValueType + * @see ArgumentDefinition + */ +public class OptionDefinition {// OptionDefinition + + /** Le nom de cette option */ + protected String key; + + /** les alias acceptés pour cette option : -o --option,... */ + protected String[] alias; + + /** nombre minimum d'occurrences requis */ + protected int min; + + /** nombre maximum d'occurrences requis, ou -1 si pas de limite */ + protected int max; + + /** les groupes d'arguments de l'option */ + protected GroupDefinition[] groups; + + + /** + * Pour construire une définition d'option en connaissant à l'avance + * la définition de tous ses arguments. + * + * @param key le nom de l'option + * @param min le nombre d'occurrences minimum à respecter + * @param max le nombre maximum d'occurrences à respecter + * @param alias les alias de l'option + * @param arguments les definitions d'arguments possibles pour l'option + */ + public OptionDefinition(String key, int min, int max, String[] alias, GroupDefinition[] arguments) { + this.key = key; + this.min = min; + this.max = max; + this.alias = alias; + this.groups = arguments; + } + + public int getMin() { + return min; + } + + public int getMax() { + return max; + } + + public String getKey() { + return key; + } + + public String[] getAlias() { + return alias; + } + + public GroupDefinition[] getGroups() { + return groups == null ? new GroupDefinition[0] : groups; + } + + public boolean isMandatory() { + return min > 0; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + StringBuffer sb = new StringBuffer(); + for (String alia : alias) { + sb.append('|').append(alia); + } + String s1 = sb.toString().substring(1); + if (CardinalityHelper.isDefaultOptional(min, max)) { + CardinalityHelper.printCardinalite(builder, s1, min, max, isMandatory(), "", "", "", ""); + } else { + CardinalityHelper.printCardinalite(builder, s1, min, max, isMandatory(), "", " ", "[", "]"); + } + if (groups.length > 0) { + for (GroupDefinition group : groups) { + builder.append(' ').append(group); + } + } + return builder.toString(); + } + + @Override + public boolean equals(Object o) { + return this == o || !(o == null || getClass() != o.getClass()) && + key.equals(((OptionDefinition) o).key); + } + + @Override + public int hashCode() { + return key.hashCode(); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + if (groups != null) { + for (int i = 0; i < groups.length; i++) { + groups[i] = null; + } + groups = null; + } + if (alias != null) { + for (int i = 0; i < alias.length; i++) { + alias[i] = null; + } + alias = null; + } + } +}// OptionDefinition Index: lutinutil/src/java/org/codelutin/option/def/ConfigPropertyModifier.java diff -u /dev/null lutinutil/src/java/org/codelutin/option/def/ConfigPropertyModifier.java:1.1 --- /dev/null Sun Dec 30 22:50:52 2007 +++ lutinutil/src/java/org/codelutin/option/def/ConfigPropertyModifier.java Sun Dec 30 22:50:46 2007 @@ -0,0 +1,78 @@ +/* +* ##% 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; + +/** + * An Enum to describe all modifiers that can be definied on a ConfigProperty + * + * @author chemit + */ +public enum ConfigPropertyModifier { + FINAL, // for a non mutable property + STATIC, // for a static property + TRANSIENT, // for a property to never save + MANDATORY; // for a property required to validate the config + + private final int intValue; + + ConfigPropertyModifier() { + intValue = 1 << ordinal(); + } + + public int getIntValue() { + return intValue; + } + + public boolean isUsed(int modifiers) { + return (modifiers & intValue) != 0; + } + + public static String toString(int modifiers) { + if (modifiers == 0) { + return ""; + } + StringBuilder sb = new StringBuilder(); + for (ConfigPropertyModifier o : values()) { + if (o.isUsed(modifiers)) { + sb.append(',').append(o.name().toLowerCase()); + } + } + return sb.toString().substring(1); + } + + /*public static void main(String[] args) { + int test1 = FINAL.intValue | STATIC.intValue; + Assert.assertTrue(FINAL.isUsed(test1)); + Assert.assertTrue(STATIC.isUsed(test1)); + Assert.assertFalse(TRANSIENT.isUsed(test1)); + Assert.assertFalse(MANDATORY.isUsed(test1)); + test1 = FINAL.intValue | STATIC.intValue | MANDATORY.intValue; + Assert.assertTrue(FINAL.isUsed(test1)); + Assert.assertTrue(STATIC.isUsed(test1)); + Assert.assertFalse(TRANSIENT.isUsed(test1)); + Assert.assertTrue(MANDATORY.isUsed(test1)); + + test1 = TRANSIENT.intValue; + Assert.assertFalse(FINAL.isUsed(test1)); + Assert.assertFalse(STATIC.isUsed(test1)); + Assert.assertTrue(TRANSIENT.isUsed(test1)); + Assert.assertFalse(MANDATORY.isUsed(test1)); + + } */ +} Index: lutinutil/src/java/org/codelutin/option/def/ArgumentDefinition.java diff -u /dev/null lutinutil/src/java/org/codelutin/option/def/ArgumentDefinition.java:1.1 --- /dev/null Sun Dec 30 22:50:52 2007 +++ lutinutil/src/java/org/codelutin/option/def/ArgumentDefinition.java Sun Dec 30 22:50:46 2007 @@ -0,0 +1,124 @@ +/** + * ##% 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 org.codelutin.util.CardinalityHelper; + +/** + * Cette classe représente la définition d'un argument d'une option. + *

+ * Elle comprend : + *

    + *
  • son type {@link #type}, à savoir constant, valued ou namedAndValued.
  • + *
  • le type de sa valeur {@link #valueType}.
  • + *
  • la clef de l'argument {@link #key} (pour un argument constant, il s'agit + * de la constante elle même, pour un ValuedArgument de sa description et + * pour un NamedValuedArgument de sa clef).
  • + *
  • sa cardinalité {@link #min} {@link #max}
  • + *
+ * De manière générale cette classe ne doit pas être instanciée directement, + * cela est fait automatiquement lors du parsing de la définition de l'option + * par le parseur ou dans les factory de définitions. + * + * @author chemit + * @see ArgumentType + * @see ArgumentValueType + * @see OptionDefinition + */ +public class ArgumentDefinition { + + /** type de l'argument */ + protected ArgumentType type; + + /** type de la valeur de l'argument */ + protected ArgumentValueType valueType; + + /** clef unique qui définie l'argument */ + protected String key; + + /** nombre minimum d'occurrences requis */ + protected int min; + + /** nombre maximum d'occurrences requis, ou -1 si pas de limite */ + protected int max; + + public ArgumentDefinition(ArgumentType type, + ArgumentValueType valueType, + String key, + int min, int max) { + this.type = type; + this.valueType = valueType; + this.key = key; + this.min = min; + this.max = max; + } + + public ArgumentType getType() { + return type; + } + + public ArgumentValueType getValueType() { + return valueType; + } + + public String getKey() { + return key; + } + + public int getMax() { + return max; + } + + public int getMin() { + return min; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ArgumentDefinition that = (ArgumentDefinition) o; + return key.equals(that.key) && valueType.equals(that.valueType); + } + + @Override + public int hashCode() { + int result; + result = key.hashCode(); + result = 31 * result + valueType.hashCode(); + return result; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + StringBuffer s = new StringBuffer(key); + switch (type) { + case constant: + break; + case namedAndValued: + s.append('=').append(valueType.name()); + break; + case valued: + s.append(':').append(valueType.name()); + break; + } + CardinalityHelper.printCardinalite(sb, s.toString(), min, max, min > 0, "", "", "", ""); + return sb.toString(); + } + +} Index: lutinutil/src/java/org/codelutin/option/def/DefinitionParser.java diff -u /dev/null lutinutil/src/java/org/codelutin/option/def/DefinitionParser.java:1.1 --- /dev/null Sun Dec 30 22:50:52 2007 +++ lutinutil/src/java/org/codelutin/option/def/DefinitionParser.java Sun Dec 30 22:50:46 2007 @@ -0,0 +1,150 @@ +/* +* ##% 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 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 static org.codelutin.option.def.DefinitionParserUtil.TypeSource; + +import java.io.IOException; +import java.io.Writer; +import java.util.Arrays; + +/** + * Parseur de définitions. TODO Revoir javadoc non correcte + *

+ * La classe contient une méthode statique publique : + * {@link #doParse(org.codelutin.option.def.DefinitionParserUtil.TypeSource ,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 { + + /** + * 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 + */ + public static DefinitionParser doParse(TypeSource type, Object src) throws IOException { + if (type == null) { + throw new IllegalArgumentException(_("lutinutil.parserdef.null.sourceType", type, Arrays.toString(TypeSource.values()))); + } + if (src == null) { + throw new IllegalArgumentException(_("lutinutil.parserdef.null.source")); + } + + // obtain instance of implementation of parser + DefinitionParser parser = type.newParserInstance(); + + // 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()); + } + } + +}