Skip to content

Commit

Permalink
Merge pull request #116 from gdgib/G2-1500-NullNamed
Browse files Browse the repository at this point in the history
G2-1500 Support null named command arguments
  • Loading branch information
gdgib authored Jan 11, 2024
2 parents 2445a5e + 0a8b78a commit d8a5f96
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.g2forge.alexandria.annotations.note.Note;
Expand All @@ -15,6 +17,7 @@
import com.g2forge.alexandria.command.invocation.format.ICommandFormat;
import com.g2forge.alexandria.command.stdio.StandardIO;
import com.g2forge.alexandria.java.core.enums.EnumException;
import com.g2forge.alexandria.java.core.error.HError;
import com.g2forge.alexandria.java.core.error.NotYetImplementedError;
import com.g2forge.alexandria.java.core.helpers.HCollection;
import com.g2forge.alexandria.java.core.marker.ISingleton;
Expand Down Expand Up @@ -118,8 +121,13 @@ protected static class ArgumentContext {
builder.add(ArgumentContext.class, Boolean.class, bool);
builder.add(ArgumentContext.class, Boolean.TYPE, bool);
builder.fallback((c, v) -> {
if (v == null) HDumbCommandConverter.set(c, c.getArgument(), null);
else throw new IllegalArgumentException(String.format("Parameter %1$s cannot be converted to a command line argument because the type of \"2$s\" (%3$s) is unknown. Please consider implementing %4$s.", c.getArgument().getName(), v, v.getClass(), IArgumentRenderer.class.getSimpleName()));
if (v == null) {
final ISubject subject = c.getArgument().getMetadata();
if (subject.isPresent(Working.class)) return;
if (subject.isPresent(Environment.class)) return;
if (subject.isPresent(EnvPath.class)) return;
HDumbCommandConverter.set(c, c.getArgument(), null);
} else throw new IllegalArgumentException(String.format("Parameter %1$s cannot be converted to a command line argument because the type of \"2$s\" (%3$s) is unknown. Please consider implementing %4$s.", c.getArgument().getName(), v, v.getClass(), IArgumentRenderer.class.getSimpleName()));
});
}).build();

Expand Down Expand Up @@ -175,11 +183,13 @@ public <T> ProcessInvocation<T> apply(ProcessInvocation<T> processInvocation, Me
if (returnTypeRef.getErasedType().isAssignableFrom(Void.class) || returnTypeRef.getErasedType().isAssignableFrom(Void.TYPE)) commandInvocationBuilder.io(StandardIO.<IRedirect, IRedirect>builder().standardInput(InheritRedirect.create()).standardOutput(InheritRedirect.create()).standardError(InheritRedirect.create()).build());
}

// Compute the command name
// Compute the command name & initial arguments
commandInvocationBuilder.clearArguments();
final Command command = Metadata.getStandard().of(methodInvocation.getMethod()).get(Command.class);
if (command != null) Stream.of(command.value()).forEach(commandInvocationBuilder::argument);
else commandInvocationBuilder.argument(methodInvocation.getMethod().getName());
final List<String> commandArguments;
if (command != null) commandArguments = HCollection.asList(command.value());
else commandArguments = HCollection.asList(methodInvocation.getMethod().getName());
commandArguments.forEach(commandInvocationBuilder::argument);

// Compute the result generator
if (processInvocation.getResultSupplier() == null) {
Expand All @@ -189,24 +199,31 @@ public <T> ProcessInvocation<T> apply(ProcessInvocation<T> processInvocation, Me

// Generate the command & environment from the method arguments
final Parameter[] parameters = methodInvocation.getMethod().getParameters();
final List<Throwable> throwables = new ArrayList<>();
for (int i = 0; i < parameters.length; i++) {
final Object value = methodInvocation.getArguments().get(i);
final IMethodArgument<Object> methodArgument = new MethodArgument(value, parameters[i]);

final IArgumentRenderer<?> argumentRenderer = methodArgument.getMetadata().get(IArgumentRenderer.class);
if (argumentRenderer != null) {
if (methodArgument.getMetadata().isPresent(Environment.class)) throw new NotYetImplementedError("Parameters with custom argument renderers cannot be used as environment variables (yet)!");
@SuppressWarnings({ "unchecked", "rawtypes" })
final List<String> arguments = argumentRenderer.render((IMethodArgument) methodArgument);
commandInvocationBuilder.arguments(arguments);
} else {
final ArgumentContext argumentContext = new ArgumentContext(commandInvocationBuilder, environmentBuilder, methodArgument);
ARGUMENT_BUILDER.accept(argumentContext, value);
// Convert all the parameters and collect any exceptions, so that the final exception report is comprehensive
try {
final Object value = methodInvocation.getArguments().get(i);
final IMethodArgument<Object> methodArgument = new MethodArgument(value, parameters[i]);

final IArgumentRenderer<?> argumentRenderer = methodArgument.getMetadata().get(IArgumentRenderer.class);
if (argumentRenderer != null) {
if (methodArgument.getMetadata().isPresent(Environment.class)) throw new NotYetImplementedError("Parameters with custom argument renderers cannot be used as environment variables (yet)!");
@SuppressWarnings({ "unchecked", "rawtypes" })
final List<String> arguments = argumentRenderer.render((IMethodArgument) methodArgument);
commandInvocationBuilder.arguments(arguments);
} else {
final ArgumentContext argumentContext = new ArgumentContext(commandInvocationBuilder, environmentBuilder, methodArgument);
ARGUMENT_BUILDER.accept(argumentContext, value);
}

final Constant constant = methodArgument.getMetadata().get(Constant.class);
if ((constant != null) && (constant.value() != null)) commandInvocationBuilder.arguments(HCollection.asList(constant.value()));
} catch (Throwable throwable) {
throwables.add(throwable);
}

final Constant constant = methodArgument.getMetadata().get(Constant.class);
if ((constant != null) && (constant.value() != null)) commandInvocationBuilder.arguments(HCollection.asList(constant.value()));
}
if (!throwables.isEmpty()) throw HError.withSuppressed(new RuntimeException(String.format("Failed to convert parameters to arguments for %1$s", commandArguments.stream().collect(Collectors.joining(" ")))), throwables);

processInvocationBuilder.commandInvocation(commandInvocationBuilder.environment(environmentBuilder.build().simplify()).build());
return processInvocationBuilder.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,22 @@ public static void set(ArgumentContext argumentContext, IMethodArgument<?> argum
// Handle named arguments
final Named named = metadata.get(Named.class);
if (named != null) {
if (value == null) throw new NullPointerException("Named argument values cannot be null (though they can be the string spelling \"null\")!");
if (value == null) {
if (named.skipNull()) return;
else throw new NullPointerException("Named argument values cannot be null (though they can be the string spelling \"null\")!");
}
if (!named.joined()) {
command.argument(named.value());
command.argument(value);
} else {
command.argument(named.value() + value);
}
} else command.argument(named.value() + value);

return;
}

// Handle environment variables
final Environment environment = metadata.get(Environment.class);
if (environment != null) {
// Null environment values, do nothing
// Null environment values, do nothing
if (value != null) argumentContext.getEnvironment().modifier(environment.value(), prior -> value);
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@
public String value();

public boolean joined() default true;

public boolean skipNull() default false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ public interface IStringNamed extends ITestCommandInterface {
public String method(@Named("name=") String argument);
}

public interface IStringNamedSkipNull extends ITestCommandInterface {
public String method(@Named(value = "name=", skipNull = true) String argument);
}

public interface IStringNamedNonJoined extends ITestCommandInterface {
public String method(@Named(value = "name", joined = false) String argument);
}
Expand Down Expand Up @@ -184,7 +188,7 @@ public void pathWorking() {
assertCommand(IPathWorking.class, x -> x.method(Paths.get("A")), VoidResultSupplier.class, Paths.get("A"), "method");
}

@Test(expected = IllegalArgumentException.class)
@Test(expected = RuntimeException.class)
public void stringArrayNamed() {
assertCommand(IStringArrayNamed.class, x -> x.method("A", "B"), StringResultSupplier.class, null);
}
Expand All @@ -209,7 +213,12 @@ public void stringNamedNonJoined() {
@Test
public void stringNamedNull() {
final IStringNamed proxy = createProxy(IStringNamed.class);
HAssert.assertException(NullPointerException.class, () -> proxy.method(null));
HAssert.assertException(RuntimeException.class, () -> proxy.method(null));
}

@Test
public void stringNamedNullSkip() {
assertCommand(IStringNamedSkipNull.class, x -> x.method(null), StringResultSupplier.class, null, "method");
}

@Test
Expand All @@ -220,6 +229,6 @@ public void stringValue() {
@Test
public void stringValueNull() {
final IStringValue proxy = createProxy(IStringValue.class);
HAssert.assertException(NullPointerException.class, () -> proxy.method(null));
HAssert.assertException(RuntimeException.class, () -> proxy.method(null));
}
}

0 comments on commit d8a5f96

Please sign in to comment.