Skip to content

Commit cf06f67

Browse files
author
Oliver Heger
committed
CLI-159: It is now possible to set a required flag on a group explicitly. If this group is then added as child to another group, it is only validated if options of this group are found on the command line or its required flag is true.
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/cli/trunk@680178 13f79535-47bb-0310-9956-ffa450edef68
1 parent f104dd0 commit cf06f67

4 files changed

Lines changed: 274 additions & 9 deletions

File tree

src/java/org/apache/commons/cli2/builder/GroupBuilder.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public class GroupBuilder {
3333
private List options;
3434
private int minimum;
3535
private int maximum;
36+
private boolean required;
3637

3738
/**
3839
* Creates a new GroupBuilder
@@ -47,7 +48,7 @@ public GroupBuilder() {
4748
*/
4849
public Group create() {
4950
final GroupImpl group =
50-
new GroupImpl(options, name, description, minimum, maximum);
51+
new GroupImpl(options, name, description, minimum, maximum, required);
5152

5253
reset();
5354

@@ -63,6 +64,7 @@ public GroupBuilder reset() {
6364
options = new ArrayList();
6465
minimum = 0;
6566
maximum = Integer.MAX_VALUE;
67+
required = true;
6668
return this;
6769
}
6870

@@ -115,4 +117,16 @@ public GroupBuilder withOption(final Option option) {
115117
this.options.add(option);
116118
return this;
117119
}
120+
121+
/**
122+
* Sets the required flag. This flag is evaluated for groups that are
123+
* added to other groups as child groups. If set to <b>true</b> the
124+
* minimum and maximum constraints of the child group are always evaluated.
125+
* @param newRequired the required flag
126+
* @return this builder
127+
*/
128+
public GroupBuilder withRequired(final boolean newRequired) {
129+
this.required = newRequired;
130+
return this;
131+
}
118132
}

src/java/org/apache/commons/cli2/option/GroupImpl.java

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,15 @@ public class GroupImpl
6060
* @param description a description of this Group
6161
* @param minimum the minimum number of Options for a valid CommandLine
6262
* @param maximum the maximum number of Options for a valid CommandLine
63+
* @param required a flag whether this group is required
6364
*/
6465
public GroupImpl(final List options,
6566
final String name,
6667
final String description,
6768
final int minimum,
68-
final int maximum) {
69-
super(0, false);
69+
final int maximum,
70+
final boolean required) {
71+
super(0, required);
7072

7173
this.name = name;
7274
this.description = description;
@@ -247,7 +249,7 @@ public void validate(final WriteableCommandLine commandLine)
247249
final Option option = (Option) i.next();
248250

249251
// needs validation?
250-
boolean validate = option.isRequired() || option instanceof Group;
252+
boolean validate = option.isRequired();
251253

252254
// if the child option is present then validate it
253255
if (commandLine.hasOption(option)) {
@@ -470,8 +472,16 @@ public int getMaximum() {
470472
return maximum;
471473
}
472474

473-
public boolean isRequired() {
474-
return getMinimum() > 0;
475+
/**
476+
* Tests whether this option is required. For groups we evaluate the
477+
* <code>required</code> flag common to all options, but also take the
478+
* minimum constraints into account.
479+
*
480+
* @return a flag whether this option is required
481+
*/
482+
public boolean isRequired()
483+
{
484+
return (getParent() == null || super.isRequired()) && getMinimum() > 0;
475485
}
476486

477487
public void defaults(final WriteableCommandLine commandLine) {
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
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.bug;
18+
19+
import junit.framework.TestCase;
20+
21+
import org.apache.commons.cli2.CommandLine;
22+
import org.apache.commons.cli2.Group;
23+
import org.apache.commons.cli2.Option;
24+
import org.apache.commons.cli2.OptionException;
25+
import org.apache.commons.cli2.builder.DefaultOptionBuilder;
26+
import org.apache.commons.cli2.builder.GroupBuilder;
27+
import org.apache.commons.cli2.commandline.Parser;
28+
29+
/**
30+
* Inconsistent handling of minimum and maximum constraints for groups and their
31+
* child groups.
32+
*
33+
* @author Oliver Heger
34+
* @version $Id$
35+
*/
36+
public class BugCLI159Test extends TestCase
37+
{
38+
/** The parent group. */
39+
private Group parent;
40+
41+
/** The child group. */
42+
private Group child;
43+
44+
/** The parser. */
45+
private Parser parser;
46+
47+
/**
48+
* Creates some test options, including a group with a child group.
49+
*
50+
* @param childGroupRequired a flag whether the child group is required
51+
*/
52+
private void setUpOptions(boolean childGroupRequired)
53+
{
54+
final DefaultOptionBuilder obuilder = new DefaultOptionBuilder();
55+
final GroupBuilder gbuilder = new GroupBuilder();
56+
Option parentOpt = obuilder.withLongName("parent").withShortName("p")
57+
.create();
58+
Option childOpt1 = obuilder.withLongName("child").withShortName("c")
59+
.create();
60+
Option childOpt2 = obuilder.withLongName("sub").withShortName("s")
61+
.create();
62+
Option childOpt3 = obuilder.withLongName("test").withShortName("t")
63+
.create();
64+
child = gbuilder.withName("childOptions").withOption(childOpt1)
65+
.withOption(childOpt2).withOption(childOpt3).withMinimum(2)
66+
.withRequired(childGroupRequired).create();
67+
parent = gbuilder.withName("options").withOption(parentOpt).withOption(
68+
child).withMinimum(0).create();
69+
parser = new Parser();
70+
parser.setGroup(parent);
71+
}
72+
73+
/**
74+
* Tests whether the child group can be omitted.
75+
*/
76+
public void testNoChildGroup() throws OptionException
77+
{
78+
setUpOptions(false);
79+
CommandLine cl = parser.parse(new String[] {
80+
"--parent"
81+
});
82+
assertNotNull("No command line parsed", cl);
83+
assertFalse("Child group found", cl.hasOption(child));
84+
}
85+
86+
/**
87+
* Tests whether a required child groupd can be omitted.
88+
*/
89+
public void testNoChildGroupRequired()
90+
{
91+
setUpOptions(true);
92+
try
93+
{
94+
parser.parse(new String[] {
95+
"--parent"
96+
});
97+
fail("Missing child group not detected!");
98+
}
99+
catch (OptionException oex)
100+
{
101+
// ok
102+
}
103+
}
104+
105+
/**
106+
* Tests parsing an empty command line. Because the parent group is optional
107+
* this should be possible.
108+
*/
109+
public void testNoOptions() throws OptionException
110+
{
111+
setUpOptions(false);
112+
CommandLine cl = parser.parse(new String[0]);
113+
assertFalse("Found parent option", cl.hasOption("--parent"));
114+
assertFalse("Found child option", cl.hasOption("--child"));
115+
}
116+
117+
/**
118+
* Tests parsing a command line with options of the child group.
119+
*/
120+
public void testWithChildOptions() throws OptionException
121+
{
122+
setUpOptions(false);
123+
CommandLine cl = parser.parse(new String[] {
124+
"-ct"
125+
});
126+
assertTrue("child option not found", cl.hasOption("--child"));
127+
assertTrue("test option not found", cl.hasOption("--test"));
128+
}
129+
130+
/**
131+
* Tests a command line containing options of the child group, but the
132+
* minimum constraint is violated.
133+
*/
134+
public void testWithChildOptionsMissing()
135+
{
136+
setUpOptions(false);
137+
try
138+
{
139+
parser.parse(new String[] {
140+
"--parent", "--sub"
141+
});
142+
fail("Missing options of child group not detected!");
143+
}
144+
catch (OptionException oex)
145+
{
146+
// ok
147+
}
148+
}
149+
150+
/**
151+
* Tests whether the root group is always validated.
152+
*/
153+
public void testRequiredRootGroup()
154+
{
155+
setUpOptions(false);
156+
parser.setGroup(child);
157+
try
158+
{
159+
parser.parse(new String[] {
160+
"--test"
161+
});
162+
fail("Missing options not detected!");
163+
}
164+
catch (OptionException oex)
165+
{
166+
// ok
167+
}
168+
}
169+
}

src/test/org/apache/commons/cli2/option/GroupTest.java

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public static Group buildApacheCommandGroup() {
5555
options.add(COMMAND_START);
5656
options.add(COMMAND_STOP);
5757

58-
return new GroupImpl(options, "httpd-cmds", "The command to pass to the server", 1, 1);
58+
return new GroupImpl(options, "httpd-cmds", "The command to pass to the server", 1, 1, true);
5959
}
6060

6161
public static Group buildApachectlGroup() {
@@ -64,15 +64,26 @@ public static Group buildApachectlGroup() {
6464
options.add(ParentTest.buildKParent());
6565

6666
return new GroupImpl(options, "apachectl", "Controls the apache http deamon", 0,
67-
Integer.MAX_VALUE);
67+
Integer.MAX_VALUE, true);
6868
}
6969

7070
public static Group buildAntGroup() {
7171
final List options = new ArrayList();
7272
options.add(DefaultOptionTest.buildHelpOption());
7373
options.add(ArgumentTest.buildTargetsArgument());
7474

75-
return new GroupImpl(options, "ant", "The options for ant", 0, Integer.MAX_VALUE);
75+
return new GroupImpl(options, "ant", "The options for ant", 0, Integer.MAX_VALUE, true);
76+
}
77+
78+
private static Group buildRequiredTestGroup(final boolean required,
79+
final int minimum)
80+
{
81+
final Group group = new GroupImpl(new ArrayList(), "test", null,
82+
minimum, Integer.MAX_VALUE, required);
83+
final List options = new ArrayList(1);
84+
options.add(group);
85+
new GroupImpl(options, "parent", null, 0, Integer.MAX_VALUE, false);
86+
return group;
7687
}
7788

7889
/*
@@ -437,4 +448,65 @@ public void testHelpLines_NoName() {
437448

438449
assertFalse(i.hasNext());
439450
}
451+
452+
/**
453+
* Tests isRequired() for a child group if neither the required flag nor a
454+
* minimum constraint is set.
455+
*/
456+
public void testIsRequired_ChildNoFlagNoMinimum()
457+
{
458+
final Group group = buildRequiredTestGroup(false, 0);
459+
assertFalse("Group is required", group.isRequired());
460+
}
461+
462+
/**
463+
* Tests isRequired() for a child group that has a minimum constraint, but
464+
* the required flag is not set.
465+
*/
466+
public void testIsRequired_ChildNoFlagMinimum()
467+
{
468+
final Group group = buildRequiredTestGroup(false, 10);
469+
assertFalse("Group is required", group.isRequired());
470+
}
471+
472+
/**
473+
* Tests isRequired() for a child group that has the required flag set, but
474+
* no minimum constraint. In this constellation the group is de facto not
475+
* required.
476+
*/
477+
public void testIsRequired_ChildFlagNoMinimum()
478+
{
479+
final Group group = buildRequiredTestGroup(true, 0);
480+
assertFalse("Group is required", group.isRequired());
481+
}
482+
483+
/**
484+
* Tests isRequired() for a child group that has both the required flag and
485+
* a minimum constraint set. This is indeed a required group.
486+
*/
487+
public void testIsRequired_ChildFlagMinimum()
488+
{
489+
final Group group = buildRequiredTestGroup(true, 10);
490+
assertTrue("Group is not required", group.isRequired());
491+
}
492+
493+
/**
494+
* Tests isRequired() for the root group when no minimum constraint is set.
495+
*/
496+
public void testIsRequired_ParentNoMinimum()
497+
{
498+
final Group parent = (Group) buildRequiredTestGroup(false, 0)
499+
.getParent();
500+
assertFalse("Group is required", parent.isRequired());
501+
}
502+
503+
/**
504+
* Tests isRequired() for the root group with a minimum constraint.
505+
*/
506+
public void testIsRequired_ParentMiminum()
507+
{
508+
final Group parent = new GroupImpl(new ArrayList(), "test", null, 10,
509+
Integer.MAX_VALUE, false);
510+
assertTrue("Group not required", parent.isRequired());
511+
}
440512
}

0 commit comments

Comments
 (0)