Skip to content

Commit

Permalink
[#850] Fix bug where @Mixin-annotated fields were not included in `…
Browse files Browse the repository at this point in the history
…reflect-config.json` by `picocli-codegen` annotation processor.
  • Loading branch information
remkop committed Nov 1, 2019
1 parent da63bd4 commit 83dc734
Show file tree
Hide file tree
Showing 9 changed files with 309 additions and 2 deletions.
1 change: 1 addition & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Picocli follows [semantic versioning](http://semver.org/).
* [#829] (Bugfix) `@ArgGroup` with `@Option`-annotated methods fail with `NullPointerException`. Thanks to [A2 Geek](https://github.com/a2geek) for raising this.
* [#828] (Bugfix/enhancement) Subcommands should not be parsed as option values for options with optional parameters. Thanks to [Martin Paljak](https://github.com/martinpaljak) for raising this.
* [#811] (Bugfix) `CommandLine.setResourceBundle` did not propagate resource bundle to subcommands recursively. Thanks to [thope](https://github.com/frontfact) for the pull request with the bug fix.
* [#850] (Bugfix) `@Mixin`-annotated fields were not included in `reflect-config.json` by `picocli-codegen` annotation processor. Thanks to [Nikolaos Georgiou](https://github.com/ngeor) for raising this.
* [#826] (Enhancement) Suppress compiler warning "Supported source version 'RELEASE_6' from annotation processor... less than -source..." in picocli-codegen.
* [#815] (Enhancement) `@ArgGroup` should match multiple occurrences of a multi-value `@Option` in the same group instance, not create new group for each occurrence. Thanks to [kacchi](https://github.com/kacchi) for raising this.
* [#810] (Bugfix) `@ArgGroup` should not validate when marked as `validate = false`. Thanks to [Andreas Deininger](https://github.com/deining) for raising this.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,30 @@ public void testGenerateProxyInterfaceOptionOtherInterfaces() {
.contentsAsUtf8String().isEqualTo(slurp("/picocli/codegen/graalvm/example/example-additional-interface-proxy.json"));
}


@Test
public void testGenerateReflectConfigIssue850MissingMixin() {
NativeImageConfigGeneratorProcessor processor = new NativeImageConfigGeneratorProcessor();
Compilation compilation =
javac()
.withProcessors(processor)
.withOptions("-A" + OPTION_PROJECT + "=issue850")
.compile(JavaFileObjects.forSourceLines(
"picocli.issue850missingmixin.App",
slurp("/picocli/issue850missingmixin/App.java")),
JavaFileObjects.forSourceLines(
"picocli.issue850missingmixin.InitCommand",
slurp("/picocli/issue850missingmixin/InitCommand.java")),
JavaFileObjects.forSourceLines(
"picocli.issue850missingmixin.ProviderMixin",
slurp("/picocli/issue850missingmixin/ProviderMixin.java"))
);
assertThat(compilation).succeeded();
assertThat(compilation)
.generatedFile(StandardLocation.CLASS_OUTPUT, "META-INF/native-image/picocli-generated/issue850/reflect-config.json")
.contentsAsUtf8String().isEqualTo(slurp("/picocli/issue850missingmixin/issue850-reflect-config.json"));
}

@Test
public void testGenerateResourceOptionOtherBundlesAndPatterns() {
NativeImageConfigGeneratorProcessor processor = new NativeImageConfigGeneratorProcessor();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package picocli.issue850missingmixin;

import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Mixin;
import picocli.CommandLine.Option;

import java.util.concurrent.Callable;

/**
* The entry point to the application.
*/
@Command(
synopsisSubcommandLabel = "COMMAND",
subcommands = {
// ActivateBitbucketPipelinesCommand.class,
// ActivateTravisCommand.class,
// CreateCommand.class,
// DeactivateBitbucketPipelinesCommand.class,
// DeactivateTravisCommand.class,
// DeleteCommand.class,
// DownCommand.class,
InitCommand.class,
// ListCommand.class,
// MergePullRequestsCommand.class,
// UpCommand.class,
// UpdateLicenseCommand.class
}
)
public class App implements Callable<Integer> {

@Command
public int commandMethod(@Option(names = "-x") int x, @Mixin ProviderMixin pm) {
return 0;
}
/**
* Runs the application.
* @param args Command line arguments.
*/
public static void main(String[] args) {
System.exit(
new CommandLine(new App())
.setCaseInsensitiveEnumValuesAllowed(true)
.execute(args)
);
}

@Override
public Integer call() {
System.out.println("Missing sub-command");
return -1;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package picocli.issue850missingmixin;

import picocli.CommandLine.Command;
import picocli.CommandLine.Mixin;
import picocli.CommandLine.Option;

import java.util.concurrent.Callable;

/**
* Initializes an existing git repository.
*/
@Command(
name = "init",
description = {"Initializes an existing git repository"}
)
class InitCommand implements Callable<Integer> {
@Option(
names = {"--name"},
required = true,
description = {"The name of the repository"}
)
private String name;

@Mixin
private ProviderMixin providerMixin;

@Option(
names = {"--description"},
required = true,
description = {"The description of the repository"}
)
private String description;

@Option(
names = {"--language"},
required = true,
description = {"The language of the repository"}
)
private String language;

@Option(
names = {"--clone-dir"},
required = true,
description = {"The directory in which the repository should be cloned"}
)
private String cloneDir;

@Option(
names = {"--travis-badge"},
required = false,
description = {"Add the Travis badge in the README file"}
)
private boolean travisBadge;

@Override
public Integer call() {
System.out.println("hello init");
return 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package picocli.issue850missingmixin;

import picocli.CommandLine.Option;

/**
* Reusable options related to a git provider.
*/
public class ProviderMixin {
@Option(
names = {"--owner"},
required = true,
description = {"The owner of the repository"}
)
private String owner;

@Option(
names = {"--username"},
required = true,
description = {"The username to access the git provider API"}
)
private String username;

@Option(
names = {"--password"},
required = true,
description = {"The password to access the git provider API"}
)
private String password;

@Option(
names = {"--provider"},
required = true,
description = {"The provider of the git repository (${COMPLETION-CANDIDATES})"}
)
private GitProvider provider;

public String getOwner() {
return owner;
}

public void setOwner(String owner) {
this.owner = owner;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public GitProvider getProvider() {
return provider;
}

public void setProvider(GitProvider provider) {
this.provider = provider;
}

static enum GitProvider {
/**
* GitHub.
*/
GITHUB,

/**
* Bitbucket Cloud.
*/
BITBUCKET
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
[
{
"name" : "java.util.Collections$UnmodifiableRandomAccessList",
"allDeclaredConstructors" : true,
"allPublicConstructors" : true,
"allDeclaredMethods" : true,
"allPublicMethods" : true
},
{
"name" : "picocli.issue850missingmixin.App",
"allDeclaredConstructors" : true,
"allPublicConstructors" : true,
"allDeclaredMethods" : true,
"allPublicMethods" : true,
"methods" : [
{ "name" : "commandMethod", "parameterTypes" : ["int", "picocli.issue850missingmixin.ProviderMixin"] }
]
},
{
"name" : "picocli.issue850missingmixin.InitCommand",
"allDeclaredConstructors" : true,
"allPublicConstructors" : true,
"allDeclaredMethods" : true,
"allPublicMethods" : true,
"fields" : [
{ "name" : "cloneDir" },
{ "name" : "description" },
{ "name" : "language" },
{ "name" : "name" },
{ "name" : "providerMixin" },
{ "name" : "travisBadge" }
]
},
{
"name" : "picocli.issue850missingmixin.ProviderMixin",
"allDeclaredConstructors" : true,
"allPublicConstructors" : true,
"allDeclaredMethods" : true,
"allPublicMethods" : true,
"fields" : [
{ "name" : "owner" },
{ "name" : "password" },
{ "name" : "provider" },
{ "name" : "username" }
]
},
{
"name" : "picocli.issue850missingmixin.ProviderMixin$GitProvider",
"allDeclaredConstructors" : true,
"allPublicConstructors" : true,
"allDeclaredMethods" : true,
"allPublicMethods" : true
},
{
"name" : "picocli.issue850missingmixin.ProviderMixin.GitProvider",
"allDeclaredConstructors" : true,
"allPublicConstructors" : true,
"allDeclaredMethods" : true,
"allPublicMethods" : true
}
]
Original file line number Diff line number Diff line change
Expand Up @@ -873,7 +873,7 @@ private void connectModel(AbstractCommandSpecProcessor proc) {
CommandSpec mixee = mixinEntry.getKey();
for (MixinInfo mixinInfo : mixinEntry.getValue()) {
logger.fine(String.format("Adding mixin name=%s to %s", mixinInfo.mixinName(), mixee.name()));
mixee.addMixin(mixinInfo.mixinName(), mixinInfo.mixin()/*, mixinInfo.annotatedElement()*/);
mixee.addMixin(mixinInfo.mixinName(), mixinInfo.mixin(), mixinInfo.annotatedElement());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,8 +229,15 @@ void visitCommandSpec(CommandSpec spec) throws Exception {
for (ArgGroupSpec group : spec.argGroups()) {
visitGroupSpec(group);
}
for (CommandSpec mixin : spec.mixins().values()) {
for (Map.Entry<String, CommandSpec> entry : spec.mixins().entrySet()) {
CommandSpec mixin = entry.getValue();
visitCommandSpec(mixin);

String name = entry.getKey();
IAnnotatedElement annotatedElement = spec.mixinAnnotatedElements().get(name);
if (annotatedElement != null) {
visitGetter(annotatedElement.getter());
}
}
for (CommandLine sub : spec.subcommands().values()) {
visitCommandSpec(sub.getCommandSpec());
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/picocli/CommandLine.java
Original file line number Diff line number Diff line change
Expand Up @@ -5137,6 +5137,7 @@ public static class CommandSpec {
private final Map<String, OptionSpec> negatedOptionsByNameMap = new LinkedHashMap<String, OptionSpec>();
private final Map<Character, OptionSpec> posixOptionsByKeyMap = new LinkedHashMap<Character, OptionSpec>();
private final Map<String, CommandSpec> mixins = new LinkedHashMap<String, CommandSpec>();
private final Map<String, IAnnotatedElement> mixinAnnotatedElements = new LinkedHashMap<String, IAnnotatedElement>();
private final List<ArgSpec> requiredArgs = new ArrayList<ArgSpec>();
private final List<ArgSpec> args = new ArrayList<ArgSpec>();
private final List<OptionSpec> options = new ArrayList<OptionSpec>();
Expand Down Expand Up @@ -5621,6 +5622,19 @@ private void check(ArgGroupSpec group, Set<ArgGroupSpec> existing) {
for (ArgGroupSpec sub : group.subgroups()) { check(sub, existing); }
}

/** Adds the specified mixin {@code CommandSpec} object to the map of mixins for this command.
* @param name the name that can be used to later retrieve the mixin
* @param mixin the mixin whose options and positional parameters and other attributes to add to this command
* @param annotatedElement the `{@literal @}Mixin`-annotated program element
* @return this CommandSpec for method chaining
* @see #mixinAnnotatedElements()
* @since 5.0 */
public CommandSpec addMixin(String name, CommandSpec mixin, IAnnotatedElement annotatedElement) {
CommandSpec result = addMixin(name, mixin);
mixinAnnotatedElements.put(interpolator.interpolate(name), annotatedElement);
return result;
}

/** Adds the specified mixin {@code CommandSpec} object to the map of mixins for this command.
* @param name the name that can be used to later retrieve the mixin
* @param mixin the mixin whose options and positional parameters and other attributes to add to this command
Expand Down Expand Up @@ -5686,6 +5700,12 @@ void initParentCommand(Object parent) {
* @return an immutable map of mixins added to this command. */
public Map<String, CommandSpec> mixins() { return Collections.unmodifiableMap(mixins); }

/** Returns a map of the mixin names to mixin {@code IAnnotatedElement} objects for this command.
* @return an immutable map of `{@literal @}Mixin`-annotated elements added to this command.
* @see #addMixin(String, CommandSpec, IAnnotatedElement)
* @since 5.0 */
public Map<String, IAnnotatedElement> mixinAnnotatedElements() { return Collections.unmodifiableMap(mixinAnnotatedElements); }

/** Returns the list of options configured for this command.
* @return an immutable list of options that this command recognizes. */
public List<OptionSpec> options() { return Collections.unmodifiableList(options); }
Expand Down

0 comments on commit 83dc734

Please sign in to comment.