Skip to content

Commit

Permalink
more flexible command line value description
Browse files Browse the repository at this point in the history
Signed-off-by: Daniel Kraschewski <daniel.kraschewski@tngtech.com>
  • Loading branch information
Daniel Kraschewski committed Nov 20, 2020
1 parent 45be5aa commit 9f09fa6
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.tngtech.configbuilder.annotation.valueextractor;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* This annotation is used to mark a method as description text supplier for command line options.<br>
* The annotated method must be static and accept a single parameter, which is the longOpt name of a command line option.<br>
* There may be at most one such method per class (or none at all).<br>
* If a field is annotated with {@link com.tngtech.configbuilder.annotation.valueextractor.CommandLineValue} but has no description,<br>
* then this method is called to generate the description text.<br>
* <b>Usage:</b> <code>@CommandLineValueDescriptor</code>
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CommandLineValueDescriptor {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.tngtech.configbuilder.exception;

public class InvalidDescriptionMethodException extends RuntimeException {}
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
package com.tngtech.configbuilder.util;

import com.tngtech.configbuilder.annotation.valueextractor.CommandLineValue;
import com.tngtech.configbuilder.annotation.valueextractor.CommandLineValueDescriptor;
import com.tngtech.configbuilder.configuration.ErrorMessageSetup;
import com.tngtech.configbuilder.exception.ConfigBuilderException;
import com.tngtech.configbuilder.exception.InvalidDescriptionMethodException;
import org.apache.commons.cli.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Set;

import static com.google.common.collect.Iterables.getOnlyElement;

public class CommandLineHelper {

Expand All @@ -32,27 +38,44 @@ public CommandLine getCommandLine(Class configClass, String[] args) {
public Options getOptions(Class configClass) {
Options options = configBuilderFactory.createInstance(Options.class);
for (Field field : annotationHelper.getFieldsAnnotatedWith(configClass, CommandLineValue.class)) {
if (field.isSynthetic()) {
continue;
if (!field.isSynthetic()) {
options.addOption(getOption(field, configClass));
}
options.addOption(getOption(field));
}
return options;
}

@SuppressWarnings("AccessStaticViaInstance")
private Option getOption(Field field) {
private Option getOption(Field field, Class configClass) {
CommandLineValue commandLineValue = field.getAnnotation(CommandLineValue.class);
log.debug("adding command line option {} for field {}", commandLineValue.shortOpt(), field.getName());
return Option.builder(commandLineValue.shortOpt())
.longOpt(commandLineValue.longOpt())
.hasArg()
.required(commandLineValue.required())
.desc(commandLineValue.description())
.desc(extractDescriptionString(commandLineValue, configClass))
.hasArg(commandLineValue.hasArg())
.build();
}

private String extractDescriptionString(CommandLineValue commandLineValue, Class configClass) {
if (!commandLineValue.description().isEmpty()) {
return commandLineValue.description();
}

Set<Method> descriptorMethods = annotationHelper.getMethodsAnnotatedWith(configClass, CommandLineValueDescriptor.class);
if (descriptorMethods.isEmpty()) {
return "";
}

try {
Method descriptorMethod = getOnlyElement(descriptorMethods);
descriptorMethod.setAccessible(true);
return descriptorMethod.invoke(null, commandLineValue.longOpt()).toString();
} catch (Exception e) {
throw new ConfigBuilderException(errorMessageSetup.getErrorMessage(InvalidDescriptionMethodException.class), e);
}
}

private CommandLine parseCommandLine(String[] args, Options options) {
CommandLine commandLine;
try {
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/errors.properties
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ com.tngtech.configbuilder.exception.TypeTransformerException = couldn't find a t
com.tngtech.configbuilder.exception.PrimitiveParsingException = unable to parse "%s" to %s
com.tngtech.configbuilder.exception.ImportedConfigurationException = couldn't find a field with the name %s
com.tngtech.configbuilder.exception.FactoryInstantiationException = could not create an instance of %s
com.tngtech.configbuilder.exception.InvalidDescriptionMethodException = invalid or multiple use of the @CommandLineValueDescriptor annotation (the annotated method must be static and accept a single String parameter)
standardMessage = %s was thrown
1 change: 1 addition & 0 deletions src/main/resources/errors_de.properties
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ com.tngtech.configbuilder.exception.TypeTransformerException = Konnte keinen Tra
com.tngtech.configbuilder.exception.PrimitiveParsingException = Kann "%s" nicht zu %s verarbeiten!
com.tngtech.configbuilder.exception.ImportedConfigurationException = Konnte kein Feld mit dem Namen %s finden.
com.tngtech.configbuilder.exception.FactoryInstantiationException = Konnte keine Instanz von %s erzeugen.
com.tngtech.configbuilder.exception.InvalidDescriptionMethodException = ungültige oder mehrfache Verwendung der @CommandLineValueDescriptor-Annotation (die annotierte Methode muss statisch sein und einen einzelnen String-Parameter haben)
standardMessage = Es gab eine Exception vom Typ %s
1 change: 1 addition & 0 deletions src/main/resources/errors_en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ com.tngtech.configbuilder.exception.TypeTransformerException = couldn't find a t
com.tngtech.configbuilder.exception.PrimitiveParsingException = unable to parse "%s" to %s
com.tngtech.configbuilder.exception.ImportedConfigurationException = couldn't find a field with the name %s
com.tngtech.configbuilder.exception.FactoryInstantiationException = could not create an instance of %s
com.tngtech.configbuilder.exception.InvalidDescriptionMethodException = invalid or multiple use of the @CommandLineValueDescriptor annotation (the annotated method must be static and accept a single String parameter)
standardMessage = %s was thrown
Original file line number Diff line number Diff line change
@@ -1,35 +1,46 @@
package com.tngtech.configbuilder.util;

import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.tngtech.configbuilder.annotation.valueextractor.CommandLineValue;
import com.tngtech.configbuilder.annotation.valueextractor.CommandLineValueDescriptor;
import com.tngtech.configbuilder.configuration.ErrorMessageSetup;
import com.tngtech.configbuilder.exception.ConfigBuilderException;
import org.apache.commons.cli.*;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import java.lang.reflect.Field;
import java.util.Comparator;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import static com.google.common.collect.Sets.newHashSet;
import static java.util.Comparator.comparing;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class CommandLineHelperTest {

private static class TestConfig {

@CommandLineValue(shortOpt = "u", longOpt = "user", required = true)
public String aString;
@CommandLineValue(shortOpt = "v", longOpt = "vir", required = false)
public String anotherString;

@CommandLineValueDescriptor
private static String commandLineValueDescription(String longOpt) {
switch (longOpt) {
case "vir":
return "some description string";
default:
return "";
}
}
}

private CommandLineHelper commandLineHelper;
Expand All @@ -44,19 +55,15 @@ private static class TestConfig {
@Mock
private ConfigBuilderFactory configBuilderFactory;
@Mock
private AnnotationHelper annotationHelper;
@Mock
private ErrorMessageSetup errorMessageSetup;

@Before
public void setUp() throws Exception {
when(configBuilderFactory.getInstance(AnnotationHelper.class)).thenReturn(annotationHelper);
when(configBuilderFactory.getInstance(AnnotationHelper.class)).thenReturn(new AnnotationHelper());
when(configBuilderFactory.getInstance(ErrorMessageSetup.class)).thenReturn(errorMessageSetup);

commandLineHelper = new CommandLineHelper(configBuilderFactory);

Set<Field> fields = newHashSet(TestConfig.class.getDeclaredFields());
when(annotationHelper.getFieldsAnnotatedWith(TestConfig.class, CommandLineValue.class)).thenReturn(fields);
when(parser.parse(options, args)).thenReturn(commandLine);
}

Expand All @@ -68,11 +75,11 @@ public void testGetCommandLine() throws Exception {
assertThat(commandLineHelper.getCommandLine(TestConfig.class, args)).isSameAs(commandLine);
verify(options, times(2)).addOption(captor.capture());
verify(parser).parse(options, args);
List<Option> options = captor.getAllValues();

assertThat(options).hasSize(2);
List<Option> sortedOptions = new ArrayList<>(captor.getAllValues());
sortedOptions.sort(comparing(Option::getLongOpt));

final ImmutableList<Option> sortedOptions = FluentIterable.from(options).toSortedList((o1, o2) -> o1.getLongOpt().compareTo(o2.getLongOpt()));
assertThat(sortedOptions).hasSize(2);

assertThat(sortedOptions.get(0).getLongOpt()).isEqualTo("user");
assertThat(sortedOptions.get(0).getOpt()).isEqualTo("u");
Expand All @@ -98,5 +105,6 @@ public void testGetOptions() {
assertThat(commandLineHelper.getOptions(TestConfig.class)).isEqualTo(options1);
assertThat(options1.getOption("user").getLongOpt()).isEqualTo("user");
assertThat(options1.getOption("vir").getOpt()).isEqualTo("v");
assertThat(options1.getOption("vir").getDescription()).isEqualTo("some description string");
}
}

0 comments on commit 9f09fa6

Please sign in to comment.