Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for using model transformers with mixins. #2333

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,15 @@ public String getMixinName() {
return empty(annotationName) ? getName() : annotationName;
}

@Override
public Class<? extends CommandLine.IModelTransformer> getModelTransformer() {
if (isMixin()) {
return getAnnotation(CommandLine.Mixin.class).modelTransformer();
} else {
return null;
}
}

static String propertyName(String methodName) {
if (methodName.length() > 3 && (methodName.startsWith("get") || methodName.startsWith("set"))) { return decapitalize(methodName.substring(3)); }
return decapitalize(methodName);
Expand Down
22 changes: 22 additions & 0 deletions src/main/java/picocli/CommandLine.java
Original file line number Diff line number Diff line change
Expand Up @@ -4467,6 +4467,10 @@ public enum ScopeType {
* If not specified the name of the annotated field is used.
* @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 "";

/** Returns the model transformer for this mixin.
**/
Class<? extends IModelTransformer> modelTransformer() default NoOpModelTransformer.class;
}
/**
* Fields annotated with {@code @Spec} will be initialized with the {@code CommandSpec} for the command the field is part of. Example usage:
Expand Down Expand Up @@ -7464,6 +7468,7 @@ public void updateCommandAttributes(Command cmd, IFactory factory) {

void initAliases(String[] aliases) { if (aliases != null) { this.aliases.addAll(Arrays.asList(aliases));}}
void initName(String value) { if (initializable(name, value, DEFAULT_COMMAND_NAME)) {name = value;} }
void resetName() {name = null;}
void initHelpCommand(boolean value) { if (initializable(isHelpCommand, value, DEFAULT_IS_HELP_COMMAND)) {isHelpCommand = value;} }
void initVersion(String[] value) { if (initializable(version, value, UsageMessageSpec.DEFAULT_MULTI_LINE)) {version = value.clone();} }
void initVersionProvider(IVersionProvider value) { if (versionProvider == null) { versionProvider = value; } }
Expand Down Expand Up @@ -11347,6 +11352,7 @@ public interface IAnnotatedElement {
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
String getName();
String getMixinName();
Class<? extends IModelTransformer> getModelTransformer();
boolean isArgSpec();
boolean isOption();
boolean isParameter();
Expand Down Expand Up @@ -11539,6 +11545,13 @@ public String getMixinName() {
String annotationName = getAnnotation(Mixin.class).name();
return empty(annotationName) ? getName() : annotationName;
}
public Class<? extends IModelTransformer> getModelTransformer() {
if (isMixin()) {
return getAnnotation(Mixin.class).modelTransformer();
} else {
return NoOpModelTransformer.class;
}
}
static String propertyName(String methodName) {
if (methodName.length() > 3 && (methodName.startsWith("get") || methodName.startsWith("set"))) { return decapitalize(methodName.substring(3)); }
return decapitalize(methodName);
Expand Down Expand Up @@ -12058,6 +12071,15 @@ private static CommandSpec buildMixinForMember(IAnnotatedElement member, IFactor
member.setter().set(userObject);
}
CommandSpec result = CommandSpec.forAnnotatedObject(userObject, factory);
IModelTransformer modelTransformer = DefaultFactory.create(factory, member.getModelTransformer());
String oldName = result.name;
result.initName(member.getMixinName());
result = modelTransformer.transform(result);
if (oldName != null && !oldName.equals(CommandSpec.DEFAULT_COMMAND_NAME)) {
result.initName(oldName);
} else {
result.resetName();
}
return result.withToString(member.getToString());
} catch (InitializationException ex) {
throw ex;
Expand Down
49 changes: 49 additions & 0 deletions src/test/java/picocli/MixinTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import java.io.File;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static org.junit.Assert.*;
Expand Down Expand Up @@ -1100,4 +1102,51 @@ public void testIssue1836CommandAliasOnMixin() {
Help help = new Help(new App_Issue1836());
assertEquals("list, ls", help.commandList().trim());
}

public static class MixinReuseModelTransformer implements IModelTransformer {
@Override
public CommandSpec transform(CommandSpec commandSpec) {
String prefix = commandSpec.name();
ArrayList<OptionSpec> options = new ArrayList<OptionSpec>(commandSpec.options());
for (OptionSpec option : options) {
commandSpec.remove(option);
OptionSpec.Builder optionBuilder = option.toBuilder();
String[] names = optionBuilder.names();
String[] newNames = new String[names.length];
for (int i = 0; i < names.length; i++) {
String name = names[i];
String newName = "--" + prefix + name.substring(2, 3).toUpperCase() + name.substring(3);
newNames[i] = newName;
}
optionBuilder.names(newNames);
String defaultValue = optionBuilder.defaultValue();
if (defaultValue.startsWith("${env:")) {
optionBuilder.defaultValue("${env:" + prefix.toUpperCase() + "_" + defaultValue.substring(6, defaultValue.length() - 1).toUpperCase() + "}");
}
commandSpec.add(optionBuilder.build());
}
return commandSpec;
}
}

@Test
public void testModelTransformation() {
class ReusableMixin {
@Option(names = "--url", defaultValue = "${env:URL}")
String url;
}
class Application {
@Mixin(modelTransformer = MixinReuseModelTransformer.class)
ReusableMixin first;
@Mixin(modelTransformer = MixinReuseModelTransformer.class)
ReusableMixin second;
}
Application application = new Application();
CommandLine commandLine = new CommandLine(application, new InnerClassFactory(this));
List<OptionSpec> options = commandLine.getCommandSpec().options();
assertArrayEquals(new String[]{"--firstUrl"}, options.get(0).names());
assertEquals("${env:FIRST_URL}", options.get(0).defaultValueString());
assertArrayEquals(new String[]{"--secondUrl"}, options.get(1).names());
assertEquals("${env:SECOND_URL}", options.get(1).defaultValueString());
}
}
1 change: 1 addition & 0 deletions src/test/java/picocli/ModelArgSpecTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ private static class AnnotatedImpl implements CommandLine.Model.IAnnotatedElemen
public <T extends Annotation> T getAnnotation(Class<T> annotationClass) { return null;}
public String getName() {return name;}
public String getMixinName() {return null;}
public Class<? extends CommandLine.IModelTransformer> getModelTransformer() { return null; }
public boolean isArgSpec() {return false;}
public boolean isOption() {return false;}
public boolean isParameter() {return false;}
Expand Down
Loading