From 20154d86786ee614bc184027846cc48f3c67a4de Mon Sep 17 00:00:00 2001 From: Alex Lapins Date: Tue, 28 Jun 2022 20:06:29 -0400 Subject: [PATCH] Allow specification of a ClassParser instance; initial implementation of a ClassParser which allows inheriting classes to override annotations on their parents. --- .../args4j/ClassOverridableParser.java | 53 +++++++++++++++++++ .../src/org/kohsuke/args4j/CmdLineParser.java | 44 ++++++++++++++- .../kohsuke/args4j/InheritanceOverride.java | 7 +++ .../args4j/InheritanceOverrideTest.java | 37 +++++++++++++ 4 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 args4j/src/org/kohsuke/args4j/ClassOverridableParser.java create mode 100644 args4j/test/org/kohsuke/args4j/InheritanceOverride.java create mode 100644 args4j/test/org/kohsuke/args4j/InheritanceOverrideTest.java diff --git a/args4j/src/org/kohsuke/args4j/ClassOverridableParser.java b/args4j/src/org/kohsuke/args4j/ClassOverridableParser.java new file mode 100644 index 00000000..5e06a5c7 --- /dev/null +++ b/args4j/src/org/kohsuke/args4j/ClassOverridableParser.java @@ -0,0 +1,53 @@ +package org.kohsuke.args4j; + +import org.kohsuke.args4j.spi.MethodSetter; +import org.kohsuke.args4j.spi.Setters; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.HashSet; + + +/** + * Parser for analyzing Args4J annotations in the class hierarchy, allowing annotation declarations on overridden + * members to override the annotations on the parents. The value of the overriding annotation is uses as-is; it is + * not merged with parent bindings. + * + * This can be used to feed option bindings that span across multiple instances. + * + * @author Alex Lapins + */ +public class ClassOverridableParser extends ClassParser { + @Override + public void parse(Object bean, CmdLineParser parser) { + HashSet sf = new HashSet<>(); + HashSet sm = new HashSet<>(); + for( Class c=bean.getClass(); c!=null; c=c.getSuperclass()) { + for( Method m : c.getDeclaredMethods() ) { + if (sf.contains(m.getName())) continue; + Option o = m.getAnnotation(Option.class); + if(o!=null) { + parser.addOption(new MethodSetter(parser,bean,m), o); + } + Argument a = m.getAnnotation(Argument.class); + if(a!=null) { + parser.addArgument(new MethodSetter(parser,bean,m), a); + } + sf.add(m.getName()); + } + + for( Field f : c.getDeclaredFields() ) { + if (sf.contains(f.getName())) continue; + Option o = f.getAnnotation(Option.class); + if(o!=null) { + parser.addOption(Setters.create(f,bean),o); + } + Argument a = f.getAnnotation(Argument.class); + if(a!=null) { + parser.addArgument(Setters.create(f,bean), a); + } + sf.add(f.getName()); + } + } + } +} diff --git a/args4j/src/org/kohsuke/args4j/CmdLineParser.java b/args4j/src/org/kohsuke/args4j/CmdLineParser.java index f3a48947..40efde67 100644 --- a/args4j/src/org/kohsuke/args4j/CmdLineParser.java +++ b/args4j/src/org/kohsuke/args4j/CmdLineParser.java @@ -68,7 +68,7 @@ public class CmdLineParser { */ public CmdLineParser(Object bean) { // for display purposes, we like the arguments in argument order, but the options in alphabetical order - this(bean, ParserProperties.defaults()); + this(bean, new ClassParser(), ParserProperties.defaults()); } /** @@ -87,13 +87,53 @@ public CmdLineParser(Object bean) { * if the option bean class is using args4j annotations incorrectly. */ public CmdLineParser(Object bean, ParserProperties parserProperties) { + this(bean, new ClassParser(), ParserProperties.defaults()); + } + + /** + * Creates a new command line owner that + * parses arguments/options and set them into + * the given object. + * + * @param bean + * instance of a class annotated by {@link Option} and {@link Argument}. + * this object will receive values. If this is {@code null}, the processing will + * be skipped, which is useful if you'd like to feed metadata from other sources. + * + * @param classParser class which parses annotations from given the bean + * + * @throws IllegalAnnotationError + * if the option bean class is using args4j annotations incorrectly. + */ + public CmdLineParser(Object bean, ClassParser classParser) { + this(bean, classParser, ParserProperties.defaults()); + } + + /** + * Creates a new command line owner that + * parses arguments/options and set them into + * the given object. + * + * @param bean + * instance of a class annotated by {@link Option} and {@link Argument}. + * this object will receive values. If this is {@code null}, the processing will + * be skipped, which is useful if you'd like to feed metadata from other sources. + * + * @param classParser class which parses annotations from given the bean + * + * @param parserProperties various settings for this class + * + * @throws IllegalAnnotationError + * if the option bean class is using args4j annotations incorrectly. + */ + public CmdLineParser(Object bean, ClassParser classParser, ParserProperties parserProperties) { this.parserProperties = parserProperties; // A 'return' in the constructor just skips the rest of the implementation // and returns the new object directly. if (bean==null) return; // Parse the metadata and create the setters - new ClassParser().parse(bean,this); + classParser.parse(bean,this); if (parserProperties.getOptionSorter()!=null) { Collections.sort(options, parserProperties.getOptionSorter()); diff --git a/args4j/test/org/kohsuke/args4j/InheritanceOverride.java b/args4j/test/org/kohsuke/args4j/InheritanceOverride.java new file mode 100644 index 00000000..2b254f86 --- /dev/null +++ b/args4j/test/org/kohsuke/args4j/InheritanceOverride.java @@ -0,0 +1,7 @@ +package org.kohsuke.args4j; + +public class InheritanceOverride extends Inheritance { + + @Option(name="-m", required = true) + public String me; +} diff --git a/args4j/test/org/kohsuke/args4j/InheritanceOverrideTest.java b/args4j/test/org/kohsuke/args4j/InheritanceOverrideTest.java new file mode 100644 index 00000000..2afe950d --- /dev/null +++ b/args4j/test/org/kohsuke/args4j/InheritanceOverrideTest.java @@ -0,0 +1,37 @@ +package org.kohsuke.args4j; + +public class InheritanceOverrideTest extends Args4JTestBase { + @Override + public InheritanceOverride getTestObject() { + return new InheritanceOverride(); + } + + @Override + protected CmdLineParser createParser() { + return new CmdLineParser(testObject, new ClassOverridableParser()); + } + + public void testMyselfRequired() { + args = new String[]{"-f", "My father"}; + InheritanceOverride bo = testObject; + try { + parser.parseArgument(args); + System.out.println("ParsedCorrectly"); + assertEquals("Value for class itself not arrived", "Thats me", bo.me); + } catch (CmdLineException e) { + System.out.println("ParseFailed"); + assertEquals("Option \"-m\" is required", e.getMessage()); + } + } + + public void testMyself() { + args = new String[]{"-m", "Thats me"}; + InheritanceOverride bo = testObject; + try { + parser.parseArgument(args); + assertEquals("Value for class itself not arrived", "Thats me", bo.me); + } catch (CmdLineException e) { + fail("This exception should not occur"); + } + } +}