Skip to content

Commit bc47857

Browse files
authored
Add @DeprecatedFeature annotation. (#187)
Add DeprecatedFeature annotation, support deprecated arguments, fix Positional usage.
1 parent 7fe37aa commit bc47857

File tree

42 files changed

+1051
-237
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1051
-237
lines changed

src/main/java/org/broadinstitute/barclay/argparser/ArgumentDefinition.java

+58
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public abstract class ArgumentDefinition {
1919
private final Object containingObject;
2020
private final Class<?> underlyingFieldClass;
2121
private final boolean isCollection;
22+
private final DeprecatedFeature deprecatedAnnotation;
2223

2324
// Original values provided by the user for this argument, to be used when displaying this argument as a
2425
// command line string representation. This is used instead of post-expansion values, which may be a large list.
@@ -38,6 +39,7 @@ public ArgumentDefinition(final Object containingObject, final Field underlyingF
3839
this.underlyingField.setAccessible(true);
3940
this.underlyingFieldClass = getClassForUnderlyingField();
4041
this.isCollection = isCollectionField(underlyingField);
42+
this.deprecatedAnnotation = underlyingField.getAnnotation(DeprecatedFeature.class);
4143

4244
if (!canBeMadeFromString()) {
4345
throw new CommandLineException.CommandLineParserInternalException(
@@ -106,6 +108,25 @@ public abstract void setArgumentValues(
106108
*/
107109
public abstract void validateValues(CommandLineArgumentParser commandLineArgumentParser);
108110

111+
/**
112+
* the doc string for this argument, if any.
113+
* @return doc string. can be empty.
114+
*/
115+
public abstract String getDocString();
116+
117+
/**
118+
* return true if this argument definition has the {@code @DeprecatedFeature} annotation.
119+
* @return true if this argument is deprecated, otherwise false.
120+
*/
121+
public boolean isDeprecated() { return deprecatedAnnotation != null; }
122+
123+
/**
124+
* Get the deprecation detail string.
125+
* @return a String containing the detail (may be null if the argument is not annotated with the {@code
126+
* @DeprecatedFeature} annotation or if no deprecation detail was provided).
127+
*/
128+
public String getDeprecationDetail() { return isDeprecated() ? deprecatedAnnotation.detail() : null; }
129+
109130
/**
110131
* A {@code String} representation of this argument and it's value(s) which would be valid if copied and pasted
111132
* back as a command line argument
@@ -284,6 +305,43 @@ protected String getOptionsAsDisplayString() {
284305
}
285306
}
286307

308+
/**
309+
* Decorated the provided description with a deprecation notice if this arg is deprecated.
310+
* @param description
311+
* @return the provided description annotated with a deprecation notice if this arg is deprecated
312+
*/
313+
protected String getDeprecatedArgumentNotice(final String description) {
314+
return isDeprecated() ?
315+
"This argument is DEPRECATED (" + getDeprecationDetail() + "). " + description :
316+
description;
317+
}
318+
319+
/**
320+
* The formatted description for this arg, including a deprecation notice if the arg is marked
321+
* as deprecated.
322+
* @param rawDescription the raw description for this arg
323+
* @param argumentColumnWidth the width reserved for the argument descriptions
324+
* @param descriptionColumnWidth the display column width to use for formatting
325+
* @return the description for this arg, formatted for display
326+
*/
327+
protected String getFormattedDescription(
328+
final String rawDescription,
329+
final int argumentColumnWidth,
330+
final int descriptionColumnWidth) {
331+
final StringBuilder sb = new StringBuilder();
332+
final String description = getDeprecatedArgumentNotice(rawDescription);
333+
final String wrappedDescription = Utils.wrapParagraph(description, descriptionColumnWidth);
334+
final String[] descriptionLines = wrappedDescription.split("\n");
335+
for (int i = 0; i < descriptionLines.length; ++i) {
336+
if (i > 0) {
337+
Utils.printSpaces(sb, argumentColumnWidth);
338+
}
339+
sb.append(descriptionLines[i]);
340+
sb.append("\n");
341+
}
342+
return sb.toString();
343+
}
344+
287345
// True if clazz is an enum, or if it has a ctor that takes a single String argument.
288346
private boolean canBeMadeFromString() {
289347
final Class<?> clazz = getClassForUnderlyingField();

src/main/java/org/broadinstitute/barclay/argparser/BetaFeature.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
import java.lang.annotation.*;
44

55
/**
6-
* Marker interface for features that are under development and not ready for production use.
6+
* Marker interface for features that are under development and not ready for production use. Mutually exclusive
7+
* with {@link ExperimentalFeature} and {@link DeprecatedFeature}.
78
*/
89
@Documented
910
@Inherited

src/main/java/org/broadinstitute/barclay/argparser/CommandLineArgumentParser.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ private void findPluginsForDescriptor(final CommandLinePluginDescriptor<?> plugi
392392
}
393393
}
394394

395-
private final void printArgumentUsageBlock(final StringBuilder sb, final String preamble, final List<NamedArgumentDefinition> args) {
395+
private void printArgumentUsageBlock(final StringBuilder sb, final String preamble, final List<NamedArgumentDefinition> args) {
396396
if (args != null && !args.isEmpty()) {
397397
sb.append(preamble);
398398
args.stream().sorted(NamedArgumentDefinition.sortByLongName)
@@ -419,6 +419,13 @@ public String usage(final boolean printCommon, final boolean printHidden) {
419419
sb.append(Utils.wrapParagraph(preamble,DESCRIPTION_COLUMN_WIDTH + ARGUMENT_COLUMN_WIDTH));
420420
sb.append("\n" + getVersion() + "\n");
421421

422+
// first, positional arg(s)
423+
final PositionalArgumentDefinition positionalArgDef = getPositionalArgumentDefinition();
424+
if (positionalArgDef != null) {
425+
sb.append("\n\nPositional Arguments:\n\n");
426+
sb.append(positionalArgDef.getArgumentUsage(ARGUMENT_COLUMN_WIDTH, DESCRIPTION_COLUMN_WIDTH));
427+
}
428+
422429
// filter on common and partition on plugin-controlled
423430
final Map<Boolean, List<NamedArgumentDefinition>> allArgsMap = namedArgumentDefinitions.stream()
424431
.filter(argumentDefinition -> printCommon || !argumentDefinition.isCommon())
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.broadinstitute.barclay.argparser;
2+
3+
import java.lang.annotation.Documented;
4+
import java.lang.annotation.ElementType;
5+
import java.lang.annotation.Inherited;
6+
import java.lang.annotation.Retention;
7+
import java.lang.annotation.RetentionPolicy;
8+
import java.lang.annotation.Target;
9+
10+
/**
11+
* Used to mark a feature ({@link Argument} or {@link CommandLineProgramProperties}) as deprecated. Mutually
12+
* exclusive with {@link BetaFeature} and {@link ExperimentalFeature}.
13+
*/
14+
@Target({ElementType.TYPE,ElementType.FIELD})
15+
@Retention(RetentionPolicy.RUNTIME)
16+
@Documented
17+
@Inherited
18+
public @interface DeprecatedFeature {
19+
20+
/**
21+
* @return the deprecation detail string associated with this command-line argument.
22+
*/
23+
String detail() default "This feature is deprecated and will be removed in a future release.";
24+
}

src/main/java/org/broadinstitute/barclay/argparser/ExperimentalFeature.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
/**
66
* Marker interface for features that are experimental and not for production use. These tools may never become stable
7-
* and may be changed dramatically or completely removed.
7+
* and may be changed dramatically or completely removed. Mutually exclusive with {@link BetaFeature} and
8+
* {@link DeprecatedFeature}.
89
*/
910
@Documented
1011
@Inherited

src/main/java/org/broadinstitute/barclay/argparser/NamedArgumentDefinition.java

+7-28
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,7 @@ public NamedArgumentDefinition(
9595
*/
9696
public String getLongName() { return !getFullName().isEmpty() ? getFullName() : getUnderlyingField().getName(); }
9797

98-
/**
99-
* the doc string for this argument, if any.
100-
* @return doc string. can be empty.
101-
*/
98+
@Override
10299
public String getDocString() { return argumentAnnotation.doc(); }
103100

104101
/**
@@ -131,12 +128,6 @@ public NamedArgumentDefinition(
131128
*/
132129
public boolean isAdvanced() { return getUnderlyingField().getAnnotation(Advanced.class) != null; }
133130

134-
/**
135-
* return true if this argument has the {@code @Deprecated} annotation.
136-
* @return true if this argument is advanced, otherwise false.
137-
*/
138-
public boolean isDeprecated() { return getUnderlyingField().isAnnotationPresent(Deprecated.class); }
139-
140131
/**
141132
* return true if this argument is a flag (boolean valued) argument
142133
* @return true if this argument is boolean valued
@@ -451,18 +442,12 @@ public String getArgumentUsage(
451442
sb.append("\n");
452443
numSpaces = argumentColumnWidth;
453444
}
454-
printSpaces(sb, numSpaces);
455-
456-
final String description = getArgumentDescription(allActualArguments, pluginDescriptors);
457-
final String wrappedDescription = Utils.wrapParagraph(description, descriptionColumnWidth);
458-
final String[] descriptionLines = wrappedDescription.split("\n");
459-
for (int i = 0; i < descriptionLines.length; ++i) {
460-
if (i > 0) {
461-
printSpaces(sb, argumentColumnWidth);
462-
}
463-
sb.append(descriptionLines[i]);
464-
sb.append("\n");
465-
}
445+
Utils.printSpaces(sb, numSpaces);
446+
447+
sb.append(getFormattedDescription(
448+
getArgumentDescription(allActualArguments, pluginDescriptors),
449+
argumentColumnWidth,
450+
descriptionColumnWidth));
466451
sb.append("\n");
467452

468453
return sb.toString();
@@ -693,12 +678,6 @@ private boolean isValueOutOfRange(final Double value) {
693678
|| getMaxValue() != Double.POSITIVE_INFINITY && value > getMaxValue();
694679
}
695680

696-
private static void printSpaces(final StringBuilder sb, final int numSpaces) {
697-
for (int i = 0; i < numSpaces; ++i) {
698-
sb.append(" ");
699-
}
700-
}
701-
702681
@Override
703682
public boolean equals(Object o) {
704683
if (this == o) return true;

src/main/java/org/broadinstitute/barclay/argparser/PositionalArgumentDefinition.java

+42
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,48 @@ public void validateValues(final CommandLineArgumentParser commandLineArgumentPa
110110
}
111111
}
112112

113+
@Override
114+
public String getDocString() { return positionalArgumentsAnnotation.doc(); }
115+
116+
/**
117+
* Return a string with the usage statement for this positional argument.
118+
* @param argumentColumnWidth width reserved for argument name column display
119+
* @param descriptionColumnWidth width reserved for argument description column display
120+
* @return the usage string for this argument
121+
*/
122+
public String getArgumentUsage(final int argumentColumnWidth, final int descriptionColumnWidth) {
123+
124+
final StringBuilder sb = new StringBuilder();
125+
sb.append("--").append("POSITIONAL (must be first)");
126+
sb.append(String.format(" <%s>", getUnderlyingFieldClass().getSimpleName()));
127+
128+
int labelLength = sb.toString().length();
129+
int numSpaces = argumentColumnWidth - labelLength;
130+
if (labelLength > argumentColumnWidth) {
131+
sb.append("\n");
132+
numSpaces = argumentColumnWidth;
133+
}
134+
Utils.printSpaces(sb, numSpaces);
135+
136+
sb.append(getFormattedDescription(getArgumentDescription(), argumentColumnWidth, descriptionColumnWidth));
137+
sb.append("\n");
138+
139+
return sb.toString();
140+
}
141+
142+
// Return a usage string representing this argument.
143+
private String getArgumentDescription() {
144+
final StringBuilder sb = new StringBuilder();
145+
if (!getDocString().isEmpty()) {
146+
sb.append(getDocString());
147+
sb.append(" ");
148+
}
149+
if (isCollection()) {
150+
sb.append("This argument must be specified at least once. ");
151+
}
152+
return sb.toString();
153+
}
154+
113155
@Override
114156
public boolean equals(Object o) {
115157
if (this == o) return true;

src/main/java/org/broadinstitute/barclay/help/DefaultDocWorkUnitHandler.java

+18-25
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import java.util.*;
1515
import java.util.stream.Collectors;
1616

17+
1718
/**
1819
* Default implementation of DocWorkUnitHandler. The DocWorkUnitHandler determines the template that will be
1920
* used for a given work unit, and populates the Freemarker property map used for a single feature/work-unit.
@@ -235,6 +236,8 @@ protected void addHighLevelBindings(final DocWorkUnit workUnit)
235236
// FreeMarker map for the index.
236237
workUnit.setProperty("beta", workUnit.isBetaFeature());
237238
workUnit.setProperty("experimental", workUnit.isExperimentalFeature());
239+
workUnit.setProperty(TemplateProperties.FEATURE_DEPRECATED, workUnit.isDeprecatedFeature());
240+
workUnit.setProperty(TemplateMapConstants.FEATURE_DEPRECATION_DETAIL, workUnit.getDeprecationDetail());
238241

239242
workUnit.setProperty("description", getDescription(workUnit));
240243

@@ -311,26 +314,10 @@ protected void addCommandLineArgumentBindings(final DocWorkUnit currentWorkUnit,
311314
argMap.entrySet().stream().forEach( entry -> entry.setValue(sortArguments(entry.getValue())));
312315

313316
// Write out the GSON version
314-
// make a GSON-friendly map of arguments -- uses some hacky casting
317+
// make a GSON-friendly map of arguments
315318
final List<GSONArgument> allGSONArgs = new ArrayList<>();
316-
for (final Map<String, Object> item : argMap.get("all")) {
317-
GSONArgument itemGSONArg = new GSONArgument();
318-
319-
itemGSONArg.populate(item.get("summary").toString(),
320-
item.get("name").toString(),
321-
item.get("synonyms").toString(),
322-
item.get("type").toString(),
323-
item.get("required").toString(),
324-
item.get("fulltext").toString(),
325-
item.get("defaultValue").toString(),
326-
item.get("minValue").toString(),
327-
item.get("maxValue").toString(),
328-
item.get("minRecValue").toString(),
329-
item.get("maxRecValue").toString(),
330-
item.get("kind").toString(),
331-
(List<Map<String, Object>>)item.get("options")
332-
);
333-
allGSONArgs.add(itemGSONArg);
319+
for (final Map<String, Object> detailMap : argMap.get("all")) {
320+
allGSONArgs.add(new GSONArgument(detailMap));
334321
}
335322
currentWorkUnit.setProperty("gson-arguments", allGSONArgs);
336323
}
@@ -392,6 +379,9 @@ protected void processNamedArgument(
392379
// Finalize argument bindings
393380
args.get(argKind).add(argMap);
394381
args.get("all").add(argMap);
382+
if (argDef.isDeprecated()) {
383+
args.get(TemplateProperties.ARGUMENT_DEPRECATED).add(argMap);
384+
}
395385
}
396386
}
397387

@@ -483,6 +473,11 @@ protected void processPositionalArguments(
483473
argBindings.put("minElements", positionalArgDef.getPositionalArgumentsAnnotation().minElements());
484474
argBindings.put("maxElements", positionalArgDef.getPositionalArgumentsAnnotation().maxElements());
485475
argBindings.put("collection", positionalArgDef.isCollection());
476+
argBindings.put(TemplateProperties.ARGUMENT_DEPRECATED, positionalArgDef.isDeprecated());
477+
if (positionalArgDef.isDeprecated()) {
478+
argBindings.put(TemplateProperties.ARGUMENT_DEPRECATION_DETAIL, positionalArgDef.getDeprecationDetail());
479+
args.get(TemplateProperties.ARGLIST_TYPE_DEPRECATED).add(argBindings);
480+
}
486481
args.get("positional").add(argBindings);
487482
args.get("all").add(argBindings);
488483
}
@@ -496,16 +491,12 @@ protected void processPositionalArguments(
496491
*/
497492
private String docKindOfArg(final NamedArgumentDefinition argumentDefinition) {
498493

499-
// deprecated
500494
// required (common or otherwise)
501495
// common optional
502496
// advanced
503497
// hidden
504498

505499
// Required first (after positional, which are separate), regardless of what else it might be
506-
if (argumentDefinition.isDeprecated()) {
507-
return "deprecated";
508-
}
509500
if (argumentDefinition.isControlledByPlugin()) {
510501
return "dependent";
511502
}
@@ -539,7 +530,7 @@ private Map<String, List<Map<String, Object>>> createArgumentMap() {
539530
args.put("advanced", new ArrayList<>());
540531
args.put("dependent", new ArrayList<>());
541532
args.put("hidden", new ArrayList<>());
542-
args.put("deprecated", new ArrayList<>());
533+
args.put(TemplateProperties.ARGLIST_TYPE_DEPRECATED, new ArrayList<>());
543534
return args;
544535
}
545536

@@ -766,8 +757,10 @@ protected String processNamedArgument(
766757
if (!argDef.isOptional()) {
767758
attributes.add("required");
768759
}
760+
argBindings.put(TemplateProperties.ARGUMENT_DEPRECATED, argDef.isDeprecated());
769761
if (argDef.isDeprecated()) {
770-
attributes.add("deprecated");
762+
argBindings.put(TemplateProperties.ARGUMENT_DEPRECATION_DETAIL, argDef.getDeprecationDetail());
763+
attributes.add(TemplateMapConstants.ARG_DEPRECATED_ATTRIBUTE);
771764
}
772765
argBindings.put("attributes", attributes.size() > 0 ? String.join(", ", attributes) : "NA");
773766
argBindings.put("collection", argDef.isCollection());

0 commit comments

Comments
 (0)