Add @RequiresOptions annotation for dynamic options

Add @RequiresOptions annotation for dynamic options to note one or more
option dependencies so that the dynamic options will not be present
unless all the @Option's on which they depend are present.

Change-Id: Iadfb061a4b288bfebee34e1806d97c13e421fdb6
This commit is contained in:
Zac Livingston
2017-11-21 12:13:29 -07:00
committed by Martin Fick
parent 57b553ad94
commit 99a1ad102c
4 changed files with 131 additions and 4 deletions

View File

@@ -53,6 +53,8 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
@@ -355,6 +357,10 @@ public class CmdLineParser {
parser.parseWithPrefix(prefix, bean);
}
public void drainOptionQueue() {
parser.addOptionsWithMetRequirements();
}
private String makeOption(String name) {
if (!name.startsWith("-")) {
if (name.length() == 1) {
@@ -493,14 +499,54 @@ public class CmdLineParser {
@SuppressWarnings("rawtypes")
private List<OptionHandler> optionsList;
private Map<String, QueuedOption> queuedOptionsByName = new LinkedHashMap<>();
private HelpOption help;
private class QueuedOption {
public final Option option;
public final Setter setter;
public final String[] requiredOptions;
private QueuedOption(Option option, Setter setter, RequiresOptions requiresOptions) {
this.option = option;
this.setter = setter;
this.requiredOptions = requiresOptions != null ? requiresOptions.value() : new String[0];
}
}
MyParser(Object bean) {
super(bean, ParserProperties.defaults().withAtSyntax(false));
parseAdditionalOptions(bean, new HashSet<>());
addOptionsWithMetRequirements();
ensureOptionsInitialized();
}
public int addOptionsWithMetRequirements() {
int count = 0;
for (Iterator<Map.Entry<String, QueuedOption>> it = queuedOptionsByName.entrySet().iterator();
it.hasNext(); ) {
QueuedOption queuedOption = it.next().getValue();
if (hasAllRequiredOptions(queuedOption)) {
addOption(queuedOption.setter, queuedOption.option);
it.remove();
count++;
}
}
if (count > 0) {
count += addOptionsWithMetRequirements();
}
return count;
}
private boolean hasAllRequiredOptions(QueuedOption queuedOption) {
for (String name : queuedOption.requiredOptions) {
if (findOptionByName(name) == null) {
return false;
}
}
return true;
}
// NOTE: Argument annotations on bean are ignored.
public void parseWithPrefix(String prefix, Object bean) {
parseWithPrefix(prefix, bean, new HashSet<>());
@@ -515,13 +561,19 @@ public class CmdLineParser {
for (Method m : c.getDeclaredMethods()) {
Option o = m.getAnnotation(Option.class);
if (o != null) {
addOption(new MethodSetter(this, bean, m), new PrefixedOption(prefix, o));
queueOption(
new PrefixedOption(prefix, o),
new MethodSetter(this, bean, m),
m.getAnnotation(RequiresOptions.class));
}
}
for (Field f : c.getDeclaredFields()) {
Option o = f.getAnnotation(Option.class);
if (o != null) {
addOption(Setters.create(f, bean), new PrefixedOption(prefix, o));
queueOption(
new PrefixedOption(prefix, o),
Setters.create(f, bean),
f.getAnnotation(RequiresOptions.class));
}
if (f.isAnnotationPresent(Options.class)) {
try {
@@ -588,6 +640,14 @@ public class CmdLineParser {
return null;
}
private void queueOption(Option option, Setter setter, RequiresOptions requiresOptions) {
if (queuedOptionsByName.put(option.name(), new QueuedOption(option, setter, requiresOptions))
!= null) {
throw new IllegalAnnotationError(
"Option name " + option.name() + " is used more than once");
}
}
@SuppressWarnings("rawtypes")
private OptionHandler add(OptionHandler handler) {
ensureOptionsInitialized();

View File

@@ -0,0 +1,45 @@
// Copyright (C) 2017 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.util.cli;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
/**
* Marks a field/setter annotated with {@literal @}Option as having a dependency on multiple other
* command line option.
*
* <p>If any of the required command line options are not present, the {@literal @}Option will be
* ignored.
*
* <p>For example:
*
* <pre>
* {@literal @}RequiresOptions({"--help", "--usage"})
* {@literal @}Option(name = "--help-as-json",
* usage = "display help text in json format")
* public boolean displayHelpAsJson;
* </pre>
*/
@Retention(RUNTIME)
@Target({FIELD, METHOD, PARAMETER})
public @interface RequiresOptions {
String[] value();
}