{
* @return a String to register the mixin object with, or an empty String if the name of the annotated field should be used */
String name() default "";
}
-
+ /**
+ * Fields annotated with {@code @Inject} will be initialized with the {@code CommandLine} or {@code CommandSpec} for the command the field is part of. Example usage:
+ *
+ * class InjectExample implements Runnable {
+ * @Inject CommandLine commandLine; // usually you inject either the CommandLine
+ * @Inject CommandSpec commandSpec; // or the CommandSpec
+ * //...
+ * public void run() {
+ * // do something with the injected objects
+ * }
+ * }
+ *
+ * @since 3.2
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.FIELD)
+ public @interface Inject { }
/**
* Annotate your class with {@code @Command} when you want more control over the format of the generated help
* message.
@@ -4361,6 +4377,11 @@ private static boolean initFromAnnotatedFields(Object scope, Class> cls, Comma
if (isOption(field)) { receiver.addOption(ArgsReflection.extractOptionSpec(scope, field, factory)); }
if (isParameter(field)) { receiver.addPositional(ArgsReflection.extractPositionalParamSpec(scope, field, factory)); }
}
+ if (isInject(field)) {
+ validateInject(field);
+ field.setAccessible(true);
+ new FieldBinding(scope, field).set(receiver);
+ }
}
return result;
}
@@ -4398,6 +4419,18 @@ private static void validateCommandSpec(CommandSpec result, boolean hasCommandAn
throw new InitializationException(command.getClass().getName() + " is not a command: it has no @Command, @Option, @Parameters or @Unmatched annotations");
}
}
+ private static void validateInject(Field field) {
+ if (isInject(field) && (isOption(field) || isParameter(field))) {
+ throw new DuplicateOptionAnnotationsException("A field cannot have both @Inject and @Option or @Parameters annotations, but '" + field + "' has both.");
+ }
+ if (isInject(field) && isUnmatched(field)) {
+ throw new DuplicateOptionAnnotationsException("A field cannot have both @Inject and @Unmatched annotations, but '" + field + "' has both.");
+ }
+ if (isInject(field) && isMixin(field)) {
+ throw new DuplicateOptionAnnotationsException("A field cannot have both @Inject and @Mixin annotations, but '" + field + "' has both.");
+ }
+ if (field.getType() != CommandSpec.class) { throw new InitializationException("@picocli.CommandLine.Inject annotation is only supported on fields of type " + CommandSpec.class.getName()); }
+ }
private static CommandSpec buildMixinForField(Field field, Object scope, IFactory factory) {
try {
field.setAccessible(true);
@@ -4442,6 +4475,7 @@ private static UnmatchedArgsBinding buildUnmatchedForField(final Field field, fi
static boolean isParameter(Field f) { return f.isAnnotationPresent(Parameters.class); }
static boolean isMixin(Field f) { return f.isAnnotationPresent(Mixin.class); }
static boolean isUnmatched(Field f) { return f.isAnnotationPresent(Unmatched.class); }
+ static boolean isInject(Field f) { return f.isAnnotationPresent(Inject.class); }
}
/** Helper class to reflectively create OptionSpec and PositionalParamSpec objects from annotated elements.
diff --git a/src/test/java/picocli/CommandLineModelTest.java b/src/test/java/picocli/CommandLineModelTest.java
index 6c7aa4d80..7d4c8bdba 100644
--- a/src/test/java/picocli/CommandLineModelTest.java
+++ b/src/test/java/picocli/CommandLineModelTest.java
@@ -1642,4 +1642,51 @@ public void testSubcommandNameNotOverwrittenWhenAddedToParent() {
" -x x option%n");
assertEquals(expected, systemOutRule.getLog());
}
+
+ @Test
+ public void testInject_AnnotatedFieldInjected() {
+ class Injected {
+ @Inject CommandSpec commandSpec;
+ @Parameters String[] params;
+ }
+ Injected injected = new Injected();
+ assertNull(injected.commandSpec);
+
+ CommandLine cmd = new CommandLine(injected);
+ assertSame(cmd.getCommandSpec(), injected.commandSpec);
+ }
+
+ @Test
+ public void testInject_AnnotatedFieldInjectedForSubcommand() {
+ class Injected {
+ @Inject CommandSpec commandSpec;
+ @Parameters String[] params;
+ }
+ Injected injected = new Injected();
+ Injected sub = new Injected();
+
+ assertNull(injected.commandSpec);
+ assertNull(sub.commandSpec);
+
+ CommandLine cmd = new CommandLine(injected);
+ assertSame(cmd.getCommandSpec(), injected.commandSpec);
+
+ CommandLine subcommand = new CommandLine(sub);
+ assertSame(subcommand.getCommandSpec(), sub.commandSpec);
+ }
+
+ @Test
+ public void testInject_FieldMustBeCommandSpec() {
+ class Injected {
+ @Inject CommandLine commandLine;
+ @Parameters String[] params;
+ }
+ Injected injected = new Injected();
+ try {
+ new CommandLine(injected);
+ fail("Expect exception");
+ } catch (InitializationException ex) {
+ assertEquals("@picocli.CommandLine.Inject annotation is only supported on fields of type picocli.CommandLine$Model$CommandSpec", ex.getMessage());
+ }
+ }
}