Mercurial > hg > release > thermostat-0.6
changeset 956:539c0e95b660
Option conflict avoidance in BuiltInCommandInfo
Reviewed-by: omajid
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-February/005503.html
author | Jon VanAlten <jon.vanalten@redhat.com> |
---|---|
date | Wed, 06 Feb 2013 16:17:02 -0500 |
parents | 9a0cd2dcf73c |
children | 1a1c6524e05b |
files | launcher/src/main/java/com/redhat/thermostat/launcher/internal/BuiltInCommandInfo.java launcher/src/test/java/com/redhat/thermostat/launcher/internal/BuiltInCommandInfoTest.java |
diffstat | 2 files changed, 204 insertions(+), 27 deletions(-) [+] |
line wrap: on
line diff
--- a/launcher/src/main/java/com/redhat/thermostat/launcher/internal/BuiltInCommandInfo.java Thu Feb 07 12:05:12 2013 -0500 +++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/BuiltInCommandInfo.java Wed Feb 06 16:17:02 2013 -0500 @@ -39,6 +39,7 @@ import java.io.File; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Map.Entry; import java.util.Properties; @@ -109,7 +110,19 @@ private void learnOptions(String optionsValue, Properties props) { List<String> optionNames = Arrays.asList(optionsValue.split(",")); + + boolean addDbOptions = false; + boolean addLogOption = false; + for (String optionString : optionNames) { + if (optionString.equals(CommonOptions.OPTIONS_COMMON_DB_OPTIONS)) { + addDbOptions = true; + continue; + } + if (optionString.equals(CommonOptions.OPTIONS_COMMON_LOG_OPTION)) { + addLogOption = true; + continue; + } List<String> optionsList = Arrays.asList(optionString.trim().split("\\|")); if (optionsList.size() == 1) { learnOption(optionsList.get(0).trim(), props); @@ -117,23 +130,115 @@ learnOptionGroup(optionsList, props); } } + + if (addDbOptions) { + for (Option opt: CommonOptions.getDbOptions()) { + addOptionUsingOverriddenRequiredValue(opt); + } + } + if (addLogOption) { + Option opt = CommonOptions.getLogOption(); + addOptionUsingOverriddenRequiredValue(opt); + } + } + + /* + * For options that come from CommonOptions, there can be an override of + * the default value of boolean property <option>.required. This is not + * to be confused with the value itself being required, which it is not. + */ + private void addOptionUsingOverriddenRequiredValue(Option option) { + if (conflictExistsWithCommonOption(option)) { + throwConflictingOption(option); + } + Option overrider = options.getOption(option.getArgName()); + if (overrider != null) { + option.setRequired(overrider.isRequired()); + } + // This replaces the existing placeholder. + options.addOption(option); + } + + // If a common option is being added, call this method to check if + // there is something conflicted that was defined in command.properties + private boolean conflictExistsWithCommonOption(Option toCheck) { + String optString = toCheck.getOpt(); + String longOptString = toCheck.getLongOpt(); + Option optionReturnedByShortOpt = options.getOption(optString); + Option optionReturnedByLongOpt = options.getOption(longOptString); + if (optionReturnedByShortOpt != null && + optionReturnedByLongOpt != null && + optionReturnedByShortOpt != optionReturnedByLongOpt) { + // Two different options? Definitely conflict. + return true; + } + Option potentialConflict = optionReturnedByShortOpt; + if (potentialConflict == null) { + // We know from above there is no more than one distinct match. + potentialConflict = optionReturnedByLongOpt; + } + if (potentialConflict == null) { + // Still null? No match -> no conflict. + return false; + } + if (potentialConflict.getOpt() != null || + potentialConflict.getDescription() != null) { + // Only name and required should be set (name becomes longopt if no longopt explicitly set) + return true; + } + return false; + } + + private boolean optionConflictsWithOptions(Option option, Options options) { + for (Option o : (Collection<Option>) options.getOptions()) { + if (optionConflictsWithOption(option, o)) { + return true; + } + } + return false; + } + + + private boolean optionConflictsWithGroup(Option option, OptionGroup group) { + for (Option o : (Collection<Option>) group.getOptions()) { + if(optionConflictsWithOption(o, option)) { + return true; + } + } + return false; + } + + // Compares the two to detect conflicts. Not to be confused with conflictExistsWithCommonOption + private boolean optionConflictsWithOption(Option o1, Option o2) { + String name = o1.getArgName(); + String opt = o1.getOpt(); + String longOpt = o1.getLongOpt(); + if (name != null && name.equals(o2.getArgName())) { + return true; + } + if (opt != null && opt.equals(o2.getOpt())) { + return true; + } + if (longOpt != null && longOpt.equals(o2.getLongOpt())) { + return true; + } + return false; + } + + private void throwConflictingOption(Option option) { + // Throwing this to catch issues during development. Not intended as a + // robust solution to this happening in production. + throw new RuntimeException("The " + name + + " command contains a conflicting option: " + + option.getOpt()); } private void learnOption(String name, Properties props) { - if (name.equals(CommonOptions.OPTIONS_COMMON_DB_OPTIONS)) { - addDbOptions(); - } else if (name.equals(CommonOptions.OPTIONS_COMMON_LOG_OPTION)) { - options.addOption(CommonOptions.getLogOption()); - } else { - Option option = optionFromProperties(name, props); - options.addOption(option); + Option option = optionFromProperties(name, props); + if (optionConflictsWithOptions(option, options)) { + throwConflictingOption(option); } - } - - private void addDbOptions() { - for (Option opt: CommonOptions.getDbOptions()) { - options.addOption(opt); - } + options.addOption(option); } /* TODO currently this assumes that any set of mutually exclusive options will be @@ -146,6 +251,10 @@ og.setRequired(true); for (String optionName : optionsList) { Option option = optionFromProperties(optionName.trim(), props); + if (optionConflictsWithGroup(option, og) | + optionConflictsWithOptions(option, options)) { + throwConflictingOption(option); + } og.addOption(option); } options.addOptionGroup(og); @@ -163,17 +272,6 @@ String argKey = name + PROP_OPTHASARG; String requiredKey = name + PROP_OPTREQUIRED; String descKey = name + PROP_OPTDESC; - - // required property of common options are allowed to be overridden by - // command.properties files - if (CommonOptions.ALL_COMMON_OPTIONS.contains(name) && options.hasOption(name)) { - if (props.containsKey(requiredKey)) { - Option optionToChange = options.getOption(name); - required = Boolean.parseBoolean((String) props.getProperty(requiredKey)); - optionToChange.setRequired(required); - return optionToChange; - } - } if (props.containsKey(optKey)) { opt = (String) props.getProperty(optKey); @@ -182,7 +280,8 @@ longOpt = (String) props.getProperty(longKey); } if (opt == null && longOpt == null) { - logger.severe("Neither short nor long version of option " + name + " was set. Check properties file."); + logger.warning("Neither short nor long version of option " + name + " was set. Assuming long option same as name."); + longOpt = name; } if (props.containsKey(argKey)) { hasArg = Boolean.parseBoolean((String) props.getProperty(argKey));
--- a/launcher/src/test/java/com/redhat/thermostat/launcher/internal/BuiltInCommandInfoTest.java Thu Feb 07 12:05:12 2013 -0500 +++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/BuiltInCommandInfoTest.java Wed Feb 06 16:17:02 2013 -0500 @@ -219,7 +219,6 @@ Properties props = new Properties(); String name = "name"; props.put("options", "AUTO_DB_OPTIONS, dbUrl"); - props.put("dbUrl.long", "ignored"); props.put("dbUrl.required", "true"); BuiltInCommandInfo info = new BuiltInCommandInfo(name, props, tempLibs.toString()); @@ -262,5 +261,84 @@ assertTrue(members.contains(foo)); assertTrue(members.contains(bar)); } + + @Test(expected=RuntimeException.class) + public void verifyConflictingShortOptions() { + Properties props = new Properties(); + String name = "name"; + props.put("options", "bar,baz"); + props.put("bar.short", "b"); + props.put("baz.short", "b"); + @SuppressWarnings("unused") + BuiltInCommandInfo info = new BuiltInCommandInfo(name, props, tempLibs.toString()); + } + + @Test(expected=RuntimeException.class) + public void verifyConflictingLongOptions() { + Properties props = new Properties(); + String name = "name"; + props.put("options", "bar,baz"); + props.put("bar.long", "ba"); + props.put("baz.long", "ba"); + @SuppressWarnings("unused") + BuiltInCommandInfo info = new BuiltInCommandInfo(name, props, tempLibs.toString()); + } + + @Test(expected=RuntimeException.class) + public void verifyConflictingOptionsInGroup() { + Properties props = new Properties(); + String name = "name"; + props.put("options", "bar|baz"); + props.put("bar.short", "b"); + props.put("baz.short", "b"); + @SuppressWarnings("unused") + BuiltInCommandInfo info = new BuiltInCommandInfo(name, props, tempLibs.toString()); + } + + @Test(expected=RuntimeException.class) + public void verifyGroupOptionConflictingWithNongroupOption() { + Properties props = new Properties(); + String name = "name"; + props.put("options", "foo|bar,baz"); + props.put("foo.short", "f"); + props.put("bar.short", "b"); + props.put("baz.short", "b"); + @SuppressWarnings("unused") + BuiltInCommandInfo info = new BuiltInCommandInfo(name, props, tempLibs.toString()); + } + + @Test(expected=RuntimeException.class) + public void verifyConflictsWithCommonShortOption() { + Properties props = new Properties(); + String name = "name"; + props.put("options", "AUTO_DB_OPTIONS, dbUrl"); + props.put("dbUrl.short", "x"); + props.put("dbUrl.long", "dbUrl"); + props.put("dbUrl.required", "true"); + @SuppressWarnings("unused") + BuiltInCommandInfo info = new BuiltInCommandInfo(name, props, tempLibs.toString()); + } + + @Test(expected=RuntimeException.class) + public void verifyConflictsWithCommonLongOption() { + Properties props = new Properties(); + String name = "name"; + props.put("options", "AUTO_DB_OPTIONS, dbUrl"); + props.put("dbUrl.short", "d"); + props.put("dbUrl.long", "notDbUrl"); + props.put("dbUrl.required", "true"); + @SuppressWarnings("unused") + BuiltInCommandInfo info = new BuiltInCommandInfo(name, props, tempLibs.toString()); + } + + @Test(expected=RuntimeException.class) + public void verifyDescriptionConflictsWithCommonOption() { + Properties props = new Properties(); + String name = "name"; + props.put("options", "AUTO_DB_OPTIONS, dbUrl"); + props.put("dbUrl.description", "An attempt to cause confusion."); + props.put("dbUrl.required", "true"); + @SuppressWarnings("unused") + BuiltInCommandInfo info = new BuiltInCommandInfo(name, props, tempLibs.toString()); + } } -