Coverage Report - org.apache.commons.cli2.option.GroupImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
GroupImpl
97%
186/190
97%
127/130
4.182
ReverseStringComparator
100%
7/7
N/A
4.182
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one or more
 3  
  * contributor license agreements.  See the NOTICE file distributed with
 4  
  * this work for additional information regarding copyright ownership.
 5  
  * The ASF licenses this file to You under the Apache License, Version 2.0
 6  
  * (the "License"); you may not use this file except in compliance with
 7  
  * the License.  You may obtain a copy of the License at
 8  
  *
 9  
  *     http://www.apache.org/licenses/LICENSE-2.0
 10  
  *
 11  
  * Unless required by applicable law or agreed to in writing, software
 12  
  * distributed under the License is distributed on an "AS IS" BASIS,
 13  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  * See the License for the specific language governing permissions and
 15  
  * limitations under the License.
 16  
  */
 17  
 package org.apache.commons.cli2.option;
 18  
 
 19  
 import java.util.ArrayList;
 20  
 import java.util.Collection;
 21  
 import java.util.Collections;
 22  
 import java.util.Comparator;
 23  
 import java.util.HashSet;
 24  
 import java.util.Iterator;
 25  
 import java.util.List;
 26  
 import java.util.ListIterator;
 27  
 import java.util.Map;
 28  
 import java.util.Set;
 29  
 import java.util.SortedMap;
 30  
 import java.util.TreeMap;
 31  
 
 32  
 import org.apache.commons.cli2.Argument;
 33  
 import org.apache.commons.cli2.DisplaySetting;
 34  
 import org.apache.commons.cli2.Group;
 35  
 import org.apache.commons.cli2.HelpLine;
 36  
 import org.apache.commons.cli2.Option;
 37  
 import org.apache.commons.cli2.OptionException;
 38  
 import org.apache.commons.cli2.WriteableCommandLine;
 39  
 import org.apache.commons.cli2.resource.ResourceConstants;
 40  
 
 41  
 /**
 42  
  * An implementation of Group
 43  
  */
 44  
 public class GroupImpl
 45  
     extends OptionImpl implements Group {
 46  
     private final String name;
 47  
     private final String description;
 48  
     private final List options;
 49  
     private final int minimum;
 50  
     private final int maximum;
 51  
     private final List anonymous;
 52  
     private final SortedMap optionMap;
 53  
     private final Set prefixes;
 54  
 
 55  
     /**
 56  
      * Creates a new GroupImpl using the specified parameters.
 57  
      *
 58  
      * @param options the Options and Arguments that make up the Group
 59  
      * @param name the name of this Group, or null
 60  
      * @param description a description of this Group
 61  
      * @param minimum the minimum number of Options for a valid CommandLine
 62  
      * @param maximum the maximum number of Options for a valid CommandLine
 63  
      * @param required a flag whether this group is required
 64  
      */
 65  
     public GroupImpl(final List options,
 66  
                      final String name,
 67  
                      final String description,
 68  
                      final int minimum,
 69  
                      final int maximum,
 70  
                      final boolean required) {
 71  331
         super(0, required);
 72  
 
 73  331
         this.name = name;
 74  331
         this.description = description;
 75  331
         this.minimum = minimum;
 76  331
         this.maximum = maximum;
 77  
 
 78  
         // store a copy of the options to be used by the
 79  
         // help methods
 80  331
         this.options = Collections.unmodifiableList(options);
 81  
 
 82  
         // anonymous Argument temporary storage
 83  331
         final List newAnonymous = new ArrayList();
 84  
 
 85  
         // map (key=trigger & value=Option) temporary storage
 86  331
         final SortedMap newOptionMap = new TreeMap(ReverseStringComparator.getInstance());
 87  
 
 88  
         // prefixes temporary storage
 89  331
         final Set newPrefixes = new HashSet();
 90  
 
 91  
         // process the options
 92  331
         for (final Iterator i = options.iterator(); i.hasNext();) {
 93  1235
             final Option option = (Option) i.next();
 94  1235
             option.setParent(this);
 95  
 
 96  1235
             if (option instanceof Argument) {
 97  52
                 i.remove();
 98  52
                 newAnonymous.add(option);
 99  
             } else {
 100  1183
                 final Set triggers = option.getTriggers();
 101  
 
 102  1183
                 for (Iterator j = triggers.iterator(); j.hasNext();) {
 103  1806
                     newOptionMap.put(j.next(), option);
 104  
                 }
 105  
 
 106  
                 // store the prefixes
 107  1183
                 newPrefixes.addAll(option.getPrefixes());
 108  
             }
 109  1235
         }
 110  
 
 111  331
         this.anonymous = Collections.unmodifiableList(newAnonymous);
 112  331
         this.optionMap = Collections.unmodifiableSortedMap(newOptionMap);
 113  331
         this.prefixes = Collections.unmodifiableSet(newPrefixes);
 114  331
     }
 115  
 
 116  
     public boolean canProcess(final WriteableCommandLine commandLine,
 117  
                               final String arg) {
 118  141
         if (arg == null) {
 119  1
             return false;
 120  
         }
 121  
 
 122  
         // if arg does not require bursting
 123  140
         if (optionMap.containsKey(arg)) {
 124  55
             return true;
 125  
         }
 126  
 
 127  
         // filter
 128  85
         final Map tailMap = optionMap.tailMap(arg);
 129  
 
 130  
         // check if bursting is required
 131  85
         for (final Iterator iter = tailMap.values().iterator(); iter.hasNext();) {
 132  150
             final Option option = (Option) iter.next();
 133  
 
 134  150
             if (option.canProcess(commandLine, arg)) {
 135  55
                 return true;
 136  
             }
 137  95
         }
 138  
 
 139  30
         if (looksLikeOption(commandLine, arg)) {
 140  1
             return false;
 141  
         }
 142  
 
 143  
         // anonymous argument(s) means we can process it
 144  29
         if (anonymous.size() > 0) {
 145  6
             return true;
 146  
         }
 147  
 
 148  23
         return false;
 149  
     }
 150  
 
 151  
     public Set getPrefixes() {
 152  902
         return prefixes;
 153  
     }
 154  
 
 155  
     public Set getTriggers() {
 156  598
         return optionMap.keySet();
 157  
     }
 158  
 
 159  
     public void process(final WriteableCommandLine commandLine,
 160  
                         final ListIterator arguments)
 161  
         throws OptionException {
 162  86
         String previous = null;
 163  
 
 164  
         // [START process each command line token
 165  257
         while (arguments.hasNext()) {
 166  
             // grab the next argument
 167  179
             final String arg = (String) arguments.next();
 168  
 
 169  
             // if we have just tried to process this instance
 170  179
             if (arg == previous) {
 171  
                 // rollback and abort
 172  2
                 arguments.previous();
 173  
 
 174  2
                 break;
 175  
             }
 176  
 
 177  
             // remember last processed instance
 178  177
             previous = arg;
 179  
 
 180  177
             final Option opt = (Option) optionMap.get(arg);
 181  
 
 182  
             // option found
 183  177
             if (opt != null) {
 184  120
                 arguments.previous();
 185  120
                 opt.process(commandLine, arguments);
 186  
             }
 187  
             // [START option NOT found
 188  
             else {
 189  
                 // it might be an anonymous argument continue search
 190  
                 // [START argument may be anonymous
 191  57
                 if (looksLikeOption(commandLine, arg)) {
 192  
                     // narrow the search
 193  30
                     final Collection values = optionMap.tailMap(arg).values();
 194  
 
 195  30
                     boolean foundMemberOption = false;
 196  
 
 197  30
                     for (Iterator i = values.iterator(); i.hasNext() && !foundMemberOption;) {
 198  30
                         final Option option = (Option) i.next();
 199  
 
 200  30
                         if (option.canProcess(commandLine, arg)) {
 201  30
                             foundMemberOption = true;
 202  30
                             arguments.previous();
 203  30
                             option.process(commandLine, arguments);
 204  
                         }
 205  30
                     }
 206  
 
 207  
                     // back track and abort this group if necessary
 208  30
                     if (!foundMemberOption) {
 209  0
                         arguments.previous();
 210  
 
 211  0
                         return;
 212  
                     }
 213  30
                 } // [END argument may be anonymous
 214  
 
 215  
                 // [START argument is NOT anonymous
 216  
                 else {
 217  
                     // move iterator back, current value not used
 218  27
                     arguments.previous();
 219  
 
 220  
                     // if there are no anonymous arguments then this group can't
 221  
                     // process the argument
 222  27
                     if (anonymous.isEmpty()) {
 223  6
                         break;
 224  
                     }
 225  
 
 226  
                     // TODO: why do we iterate over all anonymous arguments?
 227  
                     // canProcess will always return true?
 228  21
                     for (final Iterator i = anonymous.iterator(); i.hasNext();) {
 229  21
                         final Argument argument = (Argument) i.next();
 230  
 
 231  21
                         if (argument.canProcess(commandLine, arguments)) {
 232  21
                             argument.process(commandLine, arguments);
 233  
                         }
 234  21
                     }
 235  
                 } // [END argument is NOT anonymous
 236  
             } // [END option NOT found
 237  171
         } // [END process each command line token
 238  86
     }
 239  
 
 240  
     public void validate(final WriteableCommandLine commandLine)
 241  
         throws OptionException {
 242  
         // number of options found
 243  86
         int present = 0;
 244  
 
 245  
         // reference to first unexpected option
 246  86
         Option unexpected = null;
 247  
 
 248  86
         for (final Iterator i = options.iterator(); i.hasNext();) {
 249  268
             final Option option = (Option) i.next();
 250  
 
 251  
             // needs validation?
 252  268
             boolean validate = option.isRequired();
 253  
 
 254  
             // if the child option is present then validate it
 255  268
             if (commandLine.hasOption(option)) {
 256  115
                 if (++present > maximum) {
 257  3
                     unexpected = option;
 258  
 
 259  3
                     break;
 260  
                 }
 261  112
                 validate = true;
 262  
             }
 263  
 
 264  265
             if (validate) {
 265  115
                 option.validate(commandLine);
 266  
             }
 267  261
         }
 268  
 
 269  
         // too many options
 270  82
         if (unexpected != null) {
 271  3
             throw new OptionException(this, ResourceConstants.UNEXPECTED_TOKEN,
 272  
                                       unexpected.getPreferredName());
 273  
         }
 274  
 
 275  
         // too few option
 276  79
         if (present < minimum) {
 277  6
             throw new OptionException(this, ResourceConstants.MISSING_OPTION);
 278  
         }
 279  
 
 280  
         // validate each anonymous argument
 281  73
         for (final Iterator i = anonymous.iterator(); i.hasNext();) {
 282  18
             final Option option = (Option) i.next();
 283  18
             option.validate(commandLine);
 284  17
         }
 285  72
     }
 286  
 
 287  
     public String getPreferredName() {
 288  1228
         return name;
 289  
     }
 290  
 
 291  
     public String getDescription() {
 292  588
         return description;
 293  
     }
 294  
 
 295  
     public void appendUsage(final StringBuffer buffer,
 296  
                             final Set helpSettings,
 297  
                             final Comparator comp) {
 298  60
         appendUsage(buffer, helpSettings, comp, "|");
 299  60
     }
 300  
 
 301  
     public void appendUsage(final StringBuffer buffer,
 302  
                             final Set helpSettings,
 303  
                             final Comparator comp,
 304  
                             final String separator) {
 305  73
         final Set helpSettingsCopy = new HashSet(helpSettings);
 306  
 
 307  73
         final boolean optional = !isRequired()
 308  
                 && (helpSettingsCopy.contains(DisplaySetting.DISPLAY_OPTIONAL) ||
 309  
                         helpSettingsCopy.contains(DisplaySetting.DISPLAY_OPTIONAL_CHILD_GROUP));
 310  
 
 311  73
         final boolean expanded =
 312  
             (name == null) || helpSettingsCopy.contains(DisplaySetting.DISPLAY_GROUP_EXPANDED);
 313  
 
 314  73
         final boolean named =
 315  
             !expanded ||
 316  
             ((name != null) && helpSettingsCopy.contains(DisplaySetting.DISPLAY_GROUP_NAME));
 317  
 
 318  73
         final boolean arguments = helpSettingsCopy.contains(DisplaySetting.DISPLAY_GROUP_ARGUMENT);
 319  
 
 320  73
         final boolean outer = helpSettingsCopy.contains(DisplaySetting.DISPLAY_GROUP_OUTER);
 321  
 
 322  73
         helpSettingsCopy.remove(DisplaySetting.DISPLAY_GROUP_OUTER);
 323  
 
 324  73
         final boolean both = named && expanded;
 325  
 
 326  73
         if (optional) {
 327  21
             buffer.append('[');
 328  
         }
 329  
 
 330  73
         if (named) {
 331  42
             buffer.append(name);
 332  
         }
 333  
 
 334  73
         if (both) {
 335  11
             buffer.append(" (");
 336  
         }
 337  
 
 338  73
         if (expanded) {
 339  
             final Set childSettings;
 340  
 
 341  42
             if (!helpSettingsCopy.contains(DisplaySetting.DISPLAY_GROUP_EXPANDED)) {
 342  11
                 childSettings = DisplaySetting.NONE;
 343  
             } else {
 344  31
                 childSettings = new HashSet(helpSettingsCopy);
 345  31
                 childSettings.remove(DisplaySetting.DISPLAY_OPTIONAL);
 346  
             }
 347  
 
 348  
             // grab a list of the group's options.
 349  
             final List list;
 350  
 
 351  42
             if (comp == null) {
 352  
                 // default to using the initial order
 353  41
                 list = options;
 354  
             } else {
 355  
                 // sort options if comparator is supplied
 356  1
                 list = new ArrayList(options);
 357  1
                 Collections.sort(list, comp);
 358  
             }
 359  
 
 360  
             // for each option.
 361  42
             for (final Iterator i = list.iterator(); i.hasNext();) {
 362  117
                 final Option option = (Option) i.next();
 363  
 
 364  
                 // append usage information
 365  117
                 option.appendUsage(buffer, childSettings, comp);
 366  
 
 367  
                 // add separators as needed
 368  117
                 if (i.hasNext()) {
 369  79
                     buffer.append(separator);
 370  
                 }
 371  117
             }
 372  
         }
 373  
 
 374  73
         if (both) {
 375  11
             buffer.append(')');
 376  
         }
 377  
 
 378  73
         if (optional && outer) {
 379  16
             buffer.append(']');
 380  
         }
 381  
 
 382  73
         if (arguments) {
 383  34
             for (final Iterator i = anonymous.iterator(); i.hasNext();) {
 384  7
                 buffer.append(' ');
 385  
 
 386  7
                 final Option option = (Option) i.next();
 387  7
                 option.appendUsage(buffer, helpSettingsCopy, comp);
 388  7
             }
 389  
         }
 390  
 
 391  73
         if (optional && !outer) {
 392  5
             buffer.append(']');
 393  
         }
 394  73
     }
 395  
 
 396  
     public List helpLines(final int depth,
 397  
                           final Set helpSettings,
 398  
                           final Comparator comp) {
 399  26
         final List helpLines = new ArrayList();
 400  
 
 401  26
         if (helpSettings.contains(DisplaySetting.DISPLAY_GROUP_NAME)) {
 402  25
             final HelpLine helpLine = new HelpLineImpl(this, depth);
 403  25
             helpLines.add(helpLine);
 404  
         }
 405  
 
 406  26
         if (helpSettings.contains(DisplaySetting.DISPLAY_GROUP_EXPANDED)) {
 407  
             // grab a list of the group's options.
 408  
             final List list;
 409  
 
 410  25
             if (comp == null) {
 411  
                 // default to using the initial order
 412  24
                 list = options;
 413  
             } else {
 414  
                 // sort options if comparator is supplied
 415  1
                 list = new ArrayList(options);
 416  1
                 Collections.sort(list, comp);
 417  
             }
 418  
 
 419  
             // for each option
 420  25
             for (final Iterator i = list.iterator(); i.hasNext();) {
 421  111
                 final Option option = (Option) i.next();
 422  111
                 helpLines.addAll(option.helpLines(depth + 1, helpSettings, comp));
 423  111
             }
 424  
         }
 425  
 
 426  26
         if (helpSettings.contains(DisplaySetting.DISPLAY_GROUP_ARGUMENT)) {
 427  25
             for (final Iterator i = anonymous.iterator(); i.hasNext();) {
 428  6
                 final Option option = (Option) i.next();
 429  6
                 helpLines.addAll(option.helpLines(depth + 1, helpSettings, comp));
 430  6
             }
 431  
         }
 432  
 
 433  26
         return helpLines;
 434  
     }
 435  
 
 436  
     /**
 437  
      * Gets the member Options of thie Group.
 438  
      * Note this does not include any Arguments
 439  
      * @return only the non Argument Options of the Group
 440  
      */
 441  
     public List getOptions() {
 442  177
         return options;
 443  
     }
 444  
 
 445  
     /**
 446  
      * Gets the anonymous Arguments of this Group.
 447  
      * @return the Argument options of this Group
 448  
      */
 449  
     public List getAnonymous() {
 450  0
         return anonymous;
 451  
     }
 452  
 
 453  
     public Option findOption(final String trigger) {
 454  176
         final Iterator i = getOptions().iterator();
 455  
 
 456  469
         while (i.hasNext()) {
 457  432
             final Option option = (Option) i.next();
 458  432
             final Option found = option.findOption(trigger);
 459  
 
 460  432
             if (found != null) {
 461  139
                 return found;
 462  
             }
 463  293
         }
 464  
 
 465  37
         return null;
 466  
     }
 467  
 
 468  
     public int getMinimum() {
 469  83
         return minimum;
 470  
     }
 471  
 
 472  
     public int getMaximum() {
 473  0
         return maximum;
 474  
     }
 475  
 
 476  
     /**
 477  
      * Tests whether this option is required. For groups we evaluate the
 478  
      * <code>required</code> flag common to all options, but also take the
 479  
      * minimum constraints into account.
 480  
      *
 481  
      * @return a flag whether this option is required
 482  
      */
 483  
     public boolean isRequired()
 484  
     {
 485  93
         return (getParent() == null || super.isRequired()) && getMinimum() > 0;
 486  
     }
 487  
 
 488  
     public void defaults(final WriteableCommandLine commandLine) {
 489  98
         super.defaults(commandLine);
 490  
 
 491  98
         for (final Iterator i = options.iterator(); i.hasNext();) {
 492  294
             final Option option = (Option) i.next();
 493  294
             option.defaults(commandLine);
 494  294
         }
 495  
 
 496  98
         for (final Iterator i = anonymous.iterator(); i.hasNext();) {
 497  20
             final Option option = (Option) i.next();
 498  20
             option.defaults(commandLine);
 499  20
         }
 500  98
     }
 501  
 
 502  
     /**
 503  
      * Helper method for testing whether an element of the command line looks
 504  
      * like an option. This method queries the command line, but sets the
 505  
      * current option first.
 506  
      *
 507  
      * @param commandLine the command line
 508  
      * @param trigger the trigger to be checked
 509  
      * @return a flag whether this element looks like an option
 510  
      */
 511  
     private boolean looksLikeOption(final WriteableCommandLine commandLine,
 512  
             final String trigger) {
 513  87
         Option oldOption = commandLine.getCurrentOption();
 514  
         try {
 515  87
             commandLine.setCurrentOption(this);
 516  87
             return commandLine.looksLikeOption(trigger);
 517  
         } finally {
 518  87
             commandLine.setCurrentOption(oldOption);
 519  
         }
 520  
     }
 521  
 }
 522  
 
 523  
 
 524  
 class ReverseStringComparator implements Comparator {
 525  1
     private static final Comparator instance = new ReverseStringComparator();
 526  
 
 527  1
     private ReverseStringComparator() {
 528  
         // just making sure nobody else creates one
 529  1
     }
 530  
 
 531  
     /**
 532  
      * Gets a singleton instance of a ReverseStringComparator
 533  
      * @return the singleton instance
 534  
      */
 535  
     public static final Comparator getInstance() {
 536  331
         return instance;
 537  
     }
 538  
 
 539  
     public int compare(final Object o1,
 540  
                        final Object o2) {
 541  5276
         final String s1 = (String) o1;
 542  5276
         final String s2 = (String) o2;
 543  
 
 544  5276
         return -s1.compareTo(s2);
 545  
     }
 546  
 }