Skip to content

Commit

Permalink
update annotations, resolves #10
Browse files Browse the repository at this point in the history
  • Loading branch information
h908714124 committed Sep 25, 2019
1 parent dbea409 commit c6a4617
Show file tree
Hide file tree
Showing 21 changed files with 120 additions and 214 deletions.
33 changes: 5 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,8 @@ abstract class MyArguments {
````

Note that `path` is a required parameter, because its type is not `Optional<Path>`.
The correspondence of optional parameters to optional types,
and required parameters to all other types, can also be made explicit by setting the
`optional` attribute to `true`.
The general rule is easy to remember by the slogan that *jbock never returns null*.
The general rule regarding optional types is easy to remember by the slogan that
*jbock never returns null*.

After adding such a model class to your project,
you have to **build once** to trigger the code generation.
Expand Down Expand Up @@ -219,16 +217,6 @@ one of these four types:
abstract Optional<String> file();
````

If you want to be more explicit,
you can also set the `optional` attribute
in addition to that:

````java
@Parameter(shortName = 'f', optional = true)
abstract Optional<String> file();
````


### Repeatable parameters

Repeatable parameters are <a href="#binding-parameters">*binding parameters*</a>
Expand All @@ -253,14 +241,6 @@ assertEquals(List.of("Content-Type: application/json",
To declare a repeatable parameter, simply
make the corresponding model method return a `List`.

You can also be more explicit by setting the
`repeatable` attribute:

````java
@Parameter(shortName = 'X', repeatable = true)
abstract List<String> headers();
````

### Parameter shapes

Given a <a href="#binding-parameters">*binding parameter*</a> like this
Expand Down Expand Up @@ -349,16 +329,14 @@ class PositiveNumberMapper implements Function<String, Integer> {
The same mapper can also be used for
<a href="#required-and-optional-parameters">*optional*</a>
and <a href="#repeatable-parameters">*repeatable*</a> parameters.
If a parameter has a mapper, the attribute `optional` or `repeatable`
must be set explicitly.

````java
@Parameter(shortName = 'o', mappedBy = PositiveNumberMapper.class, optional = true)
@Parameter(shortName = 'o', mappedBy = PositiveNumberMapper.class)
abstract Optional<Integer> optionalNumber();
````

````java
@Parameter(shortName = 'x', mappedBy = PositiveNumberMapper.class, repeatable = true)
@Parameter(shortName = 'x', mappedBy = PositiveNumberMapper.class)
abstract List<Integer> numbers();
````

Expand All @@ -371,8 +349,7 @@ By using a custom collector, it is possible to create a
builds a `Map`:

````java
@Parameter(repeatable = true,
shortName = 'X',
@Parameter(shortName = 'X',
mappedBy = MapTokenizer.class,
collectedBy = MapCollector.class)
abstract Map<String, String> headers();
Expand Down
2 changes: 1 addition & 1 deletion core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ repositories {

dependencies {
implementation 'com.squareup:javapoet:1.11.1'
compile 'com.github.h908714124:jbock-annotations:2.4'
compile 'com.github.h908714124:jbock-annotations:2.6'
testImplementation 'com.google.testing.compile:compile-testing:0.18'
testImplementation 'org.mockito:mockito-core:3.0.0'
testImplementation 'org.junit.jupiter:junit-jupiter:5.5.2'
Expand Down
4 changes: 4 additions & 0 deletions core/src/main/java/net/jbock/compiler/Context.java
Original file line number Diff line number Diff line change
Expand Up @@ -270,4 +270,8 @@ boolean containsType(TypeName typeName) {
}
return false;
}

List<Param> positionalParameters() {
return positionalParameters;
}
}
24 changes: 11 additions & 13 deletions core/src/main/java/net/jbock/compiler/Param.java
Original file line number Diff line number Diff line change
Expand Up @@ -183,18 +183,10 @@ private static Param createNonpositional(
checkShortName(sourceMethod, shortName);
checkName(sourceMethod, longName);
ParamName name = enumConstant(params, sourceMethod);
boolean flag = isInferredFlag(mapperClass, collectorClass, parameter.flag(), sourceMethod.getReturnType(), tool);
boolean flag = isInferredFlag(mapperClass, collectorClass, sourceMethod.getReturnType(), tool);
InferredAttributes attributes = InferredAttributes.infer(sourceMethod.getReturnType(), tool);
Coercion coercion;
if (flag) {
if (mapperClass != null) {
throw ValidationException.create(sourceMethod,
"A flag parameter can't have a mapper.");
}
if (collectorClass != null) {
throw ValidationException.create(sourceMethod,
"A flag parameter can't have a collector.");
}
if (!parameter.descriptionArgumentName().isEmpty()) {
throw ValidationException.create(sourceMethod,
"A flag cannot have a description argument name.");
Expand Down Expand Up @@ -266,14 +258,13 @@ private static Param createPositional(
private static boolean isInferredFlag(
Object mapperClass,
Object collectorClass,
boolean flag,
TypeMirror mirror,
TypeTool tool) {
if (mapperClass != null || collectorClass != null) {
// no inferring
return flag;
return false;
}
return flag || isInferredFlag(tool, mirror);
return isInferredFlag(tool, mirror);
}

private static boolean isInferredFlag(TypeTool tool, TypeMirror mirror) {
Expand Down Expand Up @@ -411,7 +402,10 @@ Optional<String> bundleKey() {
}

PositionalRank positionalOrder() {
if (repeatable) {
if (!positionalIndex.isPresent()) {
throw new AssertionError();
}
if (repeatable()) {
return PositionalRank.LIST;
}
return optional ? PositionalRank.OPTIONAL : PositionalRank.REQUIRED;
Expand Down Expand Up @@ -464,5 +458,9 @@ private static OptionType optionType(boolean repeatable, boolean flag) {
}
return OptionType.REGULAR;
}

boolean optional() {
return optional;
}
}

24 changes: 24 additions & 0 deletions core/src/main/java/net/jbock/compiler/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
import java.io.PrintStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.stream.Collectors;

import static com.squareup.javapoet.MethodSpec.methodBuilder;
import static com.squareup.javapoet.ParameterSpec.builder;
Expand Down Expand Up @@ -89,6 +91,8 @@ private Parser(
}

static Parser create(Context context) {
checkOnlyOnePositionalList(context.positionalParameters());
checkRankConsistentWithPosition(context.positionalParameters());
MethodSpec readNextMethod = readNextMethod();
MethodSpec readArgumentMethod = readArgumentMethod(readNextMethod);
Option option = Option.create(context);
Expand All @@ -99,6 +103,26 @@ static Parser create(Context context) {
return new Parser(context, builder, option, helper, impl, parseResult, readNextMethod, readArgumentMethod);
}

private static void checkOnlyOnePositionalList(List<Param> allPositional) {
List<Param> positionalRepeatable = allPositional.stream().filter(Param::repeatable).collect(Collectors.toList());
if (positionalRepeatable.size() >= 2) {
throw ValidationException.create(positionalRepeatable.get(0).sourceMethod,
"There can only be one one repeatable positional parameter.");
}
}

private static void checkRankConsistentWithPosition(List<Param> allPositional) {
int currentOrdinal = -1;
for (Param param : allPositional) {
if (param.positionalOrder().ordinal() < currentOrdinal) {
throw ValidationException.create(param.sourceMethod,
"Invalid position: Optional parameters must come after required parameters." +
"Repeatable parameters must come last.");
}
currentOrdinal = param.positionalOrder().ordinal();
}
}

TypeSpec define() {
TypeSpec.Builder spec = TypeSpec.classBuilder(context.generatedClass);
spec.addModifiers(FINAL);
Expand Down
48 changes: 7 additions & 41 deletions core/src/main/java/net/jbock/compiler/Processor.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,18 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.OptionalInt;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.partitioningBy;
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import static javax.lang.model.element.Modifier.ABSTRACT;
Expand Down Expand Up @@ -181,14 +179,6 @@ private void write(
private static final Comparator<ExecutableElement> POSITION_COMPARATOR = Comparator
.comparingInt(e -> e.getAnnotation(PositionalParameter.class).position());

private PositionalRank getPositionalOrder(ExecutableElement sourceMethod) {
PositionalParameter parameter = sourceMethod.getAnnotation(PositionalParameter.class);
if (parameter.repeatable()) {
return PositionalRank.LIST;
}
return parameter.optional() ? PositionalRank.OPTIONAL : PositionalRank.REQUIRED;
}

private List<Param> getParams(TypeElement sourceType) {
List<ExecutableElement> abstractMethods = methodsIn(sourceType.getEnclosedElements()).stream()
.filter(method -> method.getModifiers().contains(ABSTRACT))
Expand All @@ -198,20 +188,9 @@ private List<Param> getParams(TypeElement sourceType) {
partitioningBy(method -> method.getAnnotation(PositionalParameter.class) != null));
List<ExecutableElement> allNonpositional = partition.getOrDefault(false, emptyList());
List<ExecutableElement> allPositional = partition.getOrDefault(true, emptyList());
Map<PositionalRank, List<ExecutableElement>> positionalGroups = allPositional.stream()
.collect(groupingBy(
this::getPositionalOrder,
() -> new EnumMap<>(PositionalRank.class),
toCollection(ArrayList::new)));
for (List<ExecutableElement> value : positionalGroups.values()) {
value.sort(POSITION_COMPARATOR);
}

checkPositionalRepeatable(sourceType, positionalGroups);
if (allPositional.size() >= 2) {
checkPositionUnique(allPositional);
}
List<ExecutableElement> sortedPositional = getSortedPositional(positionalGroups);
checkPositionUnique(allPositional);
checkFlagsThatRequirePositional(sourceType, allPositional);
List<ExecutableElement> sortedPositional = allPositional.stream().sorted(POSITION_COMPARATOR).collect(Collectors.toList());
List<Param> result = new ArrayList<>(abstractMethods.size());
for (int i = 0; i < sortedPositional.size(); i++) {
ExecutableElement method = sortedPositional.get(i);
Expand All @@ -228,14 +207,6 @@ private List<Param> getParams(TypeElement sourceType) {
return result;
}

private List<ExecutableElement> getSortedPositional(Map<PositionalRank, List<ExecutableElement>> positionalGroups) {
List<ExecutableElement> sortedPositional = new ArrayList<>();
for (PositionalRank positionalRank : PositionalRank.values()) {
sortedPositional.addAll(positionalGroups.getOrDefault(positionalRank, emptyList()));
}
return sortedPositional;
}

private void checkPositionUnique(List<ExecutableElement> allPositional) {
Set<Integer> positions = new HashSet<>();
for (ExecutableElement method : allPositional) {
Expand All @@ -246,21 +217,16 @@ private void checkPositionUnique(List<ExecutableElement> allPositional) {
}
}

private void checkPositionalRepeatable(TypeElement sourceType, Map<PositionalRank, List<ExecutableElement>> positionalGroups) {
List<ExecutableElement> positionalRepeatable = positionalGroups.getOrDefault(PositionalRank.LIST, emptyList());
if (positionalRepeatable.size() >= 2) {
throw ValidationException.create(positionalRepeatable.get(1),
"There can only be one one repeatable positional parameter.");
}
private void checkFlagsThatRequirePositional(TypeElement sourceType, List<ExecutableElement> allPositional) {
CommandLineArguments annotation = sourceType.getAnnotation(CommandLineArguments.class);
if (annotation.allowEscapeSequence()) {
if (positionalGroups.values().stream().allMatch(List::isEmpty)) {
if (allPositional.isEmpty()) {
throw ValidationException.create(sourceType,
"Define a positional parameter, or disable the escape sequence.");
}
}
if (annotation.allowPrefixedTokens()) {
if (positionalGroups.values().stream().allMatch(List::isEmpty)) {
if (allPositional.isEmpty()) {
throw ValidationException.create(sourceType,
"Define a positional parameter, or disallow prefixed tokens.");
}
Expand Down
32 changes: 12 additions & 20 deletions core/src/main/java/net/jbock/compiler/Tokenizer.java
Original file line number Diff line number Diff line change
Expand Up @@ -257,26 +257,18 @@ private MethodSpec synopsisMethod() {
}

for (Param param : positional) {
PositionalRank positionalRank = param.positionalOrder();
if (positionalRank == null) {
continue;
}
switch (positionalRank) {
case OPTIONAL:
spec.addStatement("$N.add($S)", joiner, "[<" +
param.descriptionArgumentName() + ">]");
break;
case REQUIRED:
spec.addStatement("$N.add($S)", joiner, "<" +
param.descriptionArgumentName() + ">");
break;
case LIST:
spec.addStatement("$N.add($S)", joiner, context.allowEscape() ?
"[[--] <" + param.descriptionArgumentNameWithDots() + ">]" :
"[<" + param.descriptionArgumentNameWithDots() + ">]");
break;
default:
throw new AssertionError();
if (param.optional()) {
spec.addStatement("$N.add($S)", joiner, "[<" +
param.descriptionArgumentName() + ">]");
} else if (param.required()) {
spec.addStatement("$N.add($S)", joiner, "<" +
param.descriptionArgumentName() + ">");
} else if (param.repeatable()) {
spec.addStatement("$N.add($S)", joiner, context.allowEscape() ?
"[[--] <" + param.descriptionArgumentNameWithDots() + ">]" :
"[<" + param.descriptionArgumentNameWithDots() + ">]");
} else {
throw new AssertionError();
}
}

Expand Down

0 comments on commit c6a4617

Please sign in to comment.