From bbb3c08ba90239edc1de7d94c29553753bde41d2 Mon Sep 17 00:00:00 2001 From: Isaac Parenteau Date: Wed, 11 Jul 2018 14:44:12 -0500 Subject: [PATCH] Initial commit --- .gitignore | 17 ++ bitbucket-pipelines.yml | 14 ++ pom.xml | 36 ++++ .../net/locusworks/argparser/ArgParser.java | 167 ++++++++++++++++++ .../argparser/annotations/Parameter.java | 24 +++ .../argparser/annotations/SubParameter.java | 13 ++ .../argparser/converters/FileConverter.java | 14 ++ .../converters/IntegerConverter.java | 12 ++ .../argparser/converters/NoConverter.java | 11 ++ .../interfaces/IParameterConverter.java | 7 + .../java/test/argparser/ArgParserTest.java | 43 +++++ 11 files changed, 358 insertions(+) create mode 100644 .gitignore create mode 100644 bitbucket-pipelines.yml create mode 100644 pom.xml create mode 100644 src/main/java/net/locusworks/argparser/ArgParser.java create mode 100644 src/main/java/net/locusworks/argparser/annotations/Parameter.java create mode 100644 src/main/java/net/locusworks/argparser/annotations/SubParameter.java create mode 100644 src/main/java/net/locusworks/argparser/converters/FileConverter.java create mode 100644 src/main/java/net/locusworks/argparser/converters/IntegerConverter.java create mode 100644 src/main/java/net/locusworks/argparser/converters/NoConverter.java create mode 100644 src/main/java/net/locusworks/argparser/interfaces/IParameterConverter.java create mode 100644 src/test/java/test/argparser/ArgParserTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..07d69e7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +.classpath +.idea +.project +.settings +npm-debug.log +phantomjs +tags +*.DS_Store +*/bin/ +**/.classpath +**/.settings +**/*.swp +**/target +**/.tern-project +**/*.iml +**/git.properties +target/** diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml new file mode 100644 index 0000000..8a373e0 --- /dev/null +++ b/bitbucket-pipelines.yml @@ -0,0 +1,14 @@ +# This is a sample build configuration for Java (Maven). +# Check our guides at https://confluence.atlassian.com/x/zd-5Mw for more examples. +# Only use spaces to indent your .yml configuration. +# ----- +# You can specify a custom docker image from Docker Hub as your build environment. +image: maven:3.3.9 + +pipelines: + default: + - step: + caches: + - maven + script: # Modify the commands below to build your repository. + - mvn -B verify # -B batch mode makes Maven less verbose \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..e5719d5 --- /dev/null +++ b/pom.xml @@ -0,0 +1,36 @@ + + 4.0.0 + net.locuswors + argparser + 0.0.1-SNAPSHOT + ArgParser + Library to parse command lined arguments into a class + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.2 + + 1.8 + 1.8 + + + + + + + + + junit + junit + 4.12 + test + + + + + \ No newline at end of file diff --git a/src/main/java/net/locusworks/argparser/ArgParser.java b/src/main/java/net/locusworks/argparser/ArgParser.java new file mode 100644 index 0000000..ef1a8b4 --- /dev/null +++ b/src/main/java/net/locusworks/argparser/ArgParser.java @@ -0,0 +1,167 @@ +package net.locusworks.argparser; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import net.locusworks.argparser.annotations.Parameter; +import net.locusworks.argparser.interfaces.IParameterConverter; + +public class ArgParser { + + private Class argClass; + private List fields; + private String programName; + + public ArgParser() { } + + public static ArgParser newBuilder() { + return new ArgParser(); + } + + public ArgParser withArgClass(Class argClass) { + this.argClass = argClass; + this.fields = Stream.of(argClass.getDeclaredFields()) + .filter(f -> f.isAnnotationPresent(Parameter.class)) + .map(f -> { + f.setAccessible(true); + return f; + }) + .collect(Collectors.toList()); + + if (fields == null || fields.isEmpty()) throw new IllegalArgumentException("No fields with @Parameter defined"); + + fields.sort(new Comparator() { + + @Override + public int compare(Field o1, Field o2) { + Integer o1Order = o1.getDeclaredAnnotation(Parameter.class).order(); + Integer o2Order = o2.getDeclaredAnnotation(Parameter.class).order(); + return o1Order.compareTo(o2Order); + } + }); + + boolean positional = true; + for (Field f : fields) { + Parameter param = f.getDeclaredAnnotation(Parameter.class); + positional &= param.positional(); + if (!positional && param.positional()) throw new IllegalArgumentException("All positional values have to be first in list"); + } + + return this; + } + + public ArgParser setProgramName(String name) { + this.programName = name; + return this; + } + + public String help() { + return ""; + } + + public T parse(String[] args) { + try { + return parseItem(args); + } catch (Exception ex) { + System.err.println("An exception occured: " + ex.getMessage()); + System.err.println(help()); + } + return null; + } + + @SuppressWarnings("unchecked") + private T parseItem(String[] args) throws InstantiationException, IllegalAccessException { + if (argClass == null) throw new IllegalArgumentException("Argument class was not specified"); + Object obj = argClass.newInstance(); + this.fields = Stream.of(obj.getClass().getDeclaredFields()) + .filter(f -> f.isAnnotationPresent(Parameter.class)) + .map(f -> { + f.setAccessible(true); + return f; + }) + .collect(Collectors.toList()); + + List argList = new ArrayList<>(Arrays.asList(args)); + + List positional = new ArrayList<>(); + for(Iterator iter = fields.iterator(); iter.hasNext();) { + Field f = iter.next(); + Parameter param = f.getDeclaredAnnotation(Parameter.class); + if (param.positional()) { + positional.add(f); + iter.remove(); + } + } + + for(Iterator iter = argList.iterator(); iter.hasNext() && !positional.isEmpty();) { + String value = iter.next(); + Field f = positional.remove(0); + Parameter param = f.getDeclaredAnnotation(Parameter.class); + IParameterConverter converter = param.converter().newInstance(); + f.set(obj, converter.convert(value)); + iter.remove(); + } + + Map> paramMap = new LinkedHashMap<>(); + List params = null; + for(Iterator iter = argList.iterator(); iter.hasNext();) { + String value = iter.next(); + Field f = findField(value); + if (f != null) { + if (!paramMap.containsKey(f)) { + paramMap.put(f, new ArrayList<>()); + } + params = paramMap.get(f); + } else { + params.add(value); + } + iter.remove(); + } + + for(Entry> entry : paramMap.entrySet()) { + Field f = entry.getKey(); + List values = entry.getValue(); + Parameter param = f.getDeclaredAnnotation(Parameter.class); + if (param.variableArity() && !f.getType().isAssignableFrom(List.class)) + throw new IllegalArgumentException(String.format("Field %s is not of type list for the params", f.getName())); + IParameterConverter converter = param.converter().newInstance(); + List objs = values.stream().map(s -> converter.convert(s)).collect(Collectors.toList()); + if (param.variableArity()) { + f.set(obj, getGenericList(f.getType(), objs)); + } else { + f.set(obj, objs.get(0)); + } + } + + return (T)obj; + } + + private Field findField(String value) { + for (Field f : fields) { + Parameter param = f.getDeclaredAnnotation(Parameter.class); + for (String s : Arrays.asList(param.names())) { + if (s.equals(value)) return f; + } + } + return null; + } + + @SuppressWarnings("unchecked") + private List getGenericList(Class type, List items) { + List l = new ArrayList<>(); + for (Object o : items) { + l.add((Type)o); + } + return l; + } + +} diff --git a/src/main/java/net/locusworks/argparser/annotations/Parameter.java b/src/main/java/net/locusworks/argparser/annotations/Parameter.java new file mode 100644 index 0000000..7301f98 --- /dev/null +++ b/src/main/java/net/locusworks/argparser/annotations/Parameter.java @@ -0,0 +1,24 @@ +package net.locusworks.argparser.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import net.locusworks.argparser.converters.NoConverter; +import net.locusworks.argparser.interfaces.IParameterConverter; + +@Target(value= {ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Parameter { + + String[] names(); + String description() default ""; + boolean positional() default false; + int order() default Integer.MAX_VALUE; + int arityCount() default 0; + boolean variableArity() default false; + boolean required() default false; + Class> converter() default NoConverter.class; + SubParameter[] subParameters() default {}; +} diff --git a/src/main/java/net/locusworks/argparser/annotations/SubParameter.java b/src/main/java/net/locusworks/argparser/annotations/SubParameter.java new file mode 100644 index 0000000..a4916f7 --- /dev/null +++ b/src/main/java/net/locusworks/argparser/annotations/SubParameter.java @@ -0,0 +1,13 @@ +package net.locusworks.argparser.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(value={ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface SubParameter { + String parameter(); + Class subClass(); +} diff --git a/src/main/java/net/locusworks/argparser/converters/FileConverter.java b/src/main/java/net/locusworks/argparser/converters/FileConverter.java new file mode 100644 index 0000000..849028b --- /dev/null +++ b/src/main/java/net/locusworks/argparser/converters/FileConverter.java @@ -0,0 +1,14 @@ +package net.locusworks.argparser.converters; + +import java.io.File; + +import net.locusworks.argparser.interfaces.IParameterConverter; + +public class FileConverter implements IParameterConverter { + + public File convert(String value) { + File file = new File(value); + return file; + } + +} diff --git a/src/main/java/net/locusworks/argparser/converters/IntegerConverter.java b/src/main/java/net/locusworks/argparser/converters/IntegerConverter.java new file mode 100644 index 0000000..bb30707 --- /dev/null +++ b/src/main/java/net/locusworks/argparser/converters/IntegerConverter.java @@ -0,0 +1,12 @@ +package net.locusworks.argparser.converters; + +import net.locusworks.argparser.interfaces.IParameterConverter; + +public class IntegerConverter implements IParameterConverter { + + @Override + public Integer convert(String value) { + return Integer.parseInt(value); + } + +} diff --git a/src/main/java/net/locusworks/argparser/converters/NoConverter.java b/src/main/java/net/locusworks/argparser/converters/NoConverter.java new file mode 100644 index 0000000..c581dfa --- /dev/null +++ b/src/main/java/net/locusworks/argparser/converters/NoConverter.java @@ -0,0 +1,11 @@ +package net.locusworks.argparser.converters; + +import net.locusworks.argparser.interfaces.IParameterConverter; + +public class NoConverter implements IParameterConverter { + + public String convert(String value) { + return value; + } + +} diff --git a/src/main/java/net/locusworks/argparser/interfaces/IParameterConverter.java b/src/main/java/net/locusworks/argparser/interfaces/IParameterConverter.java new file mode 100644 index 0000000..6ac1f5f --- /dev/null +++ b/src/main/java/net/locusworks/argparser/interfaces/IParameterConverter.java @@ -0,0 +1,7 @@ +package net.locusworks.argparser.interfaces; + +public interface IParameterConverter { + + T convert(String value); + +} diff --git a/src/test/java/test/argparser/ArgParserTest.java b/src/test/java/test/argparser/ArgParserTest.java new file mode 100644 index 0000000..cbbe826 --- /dev/null +++ b/src/test/java/test/argparser/ArgParserTest.java @@ -0,0 +1,43 @@ +package test.argparser; + +import static org.junit.Assert.*; + +import java.util.List; + +import org.junit.Test; + +import net.locusworks.argparser.ArgParser; +import net.locusworks.argparser.annotations.Parameter; +import net.locusworks.argparser.converters.IntegerConverter; + +public class ArgParserTest { + + public static class Args { + @Parameter(names="position2", positional=true, order=2) + public String position2; + + @Parameter(names="position1", positional=true, order=1) + public String position1; + + @Parameter(names="-list", variableArity=true) + public List values; + + @Parameter(names="-count", converter=IntegerConverter.class) + public int count; + + } + + + @Test + public void testArgParser() { + String[] args = new String[] {"Hello", "World", "-list", "hell", "world", "-count", "1"}; + + Args arg = ArgParser.newBuilder().withArgClass(Args.class).parse(args); + + assertTrue(arg.position2.equals(args[0])); + assertTrue(arg.position1.equals(args[1])); + assertTrue(arg.values.size() == 2); + assertTrue(arg.count == 1); + } + +}