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

Fix #1767 - NPE on OptionSpec.getValue() (IScoped) #1802

Merged
merged 2 commits into from
Sep 11, 2022
Merged
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
37 changes: 35 additions & 2 deletions src/main/java/picocli/CommandLine.java
Original file line number Diff line number Diff line change
Expand Up @@ -5995,6 +5995,16 @@ private Model() {}
* @since 4.0
*/
public interface IScope extends IGetter, ISetter {}

/** This interface provides access to an {@link IScope} instance.
* @since 4.7
rsenden marked this conversation as resolved.
Show resolved Hide resolved
*/
public interface IScoped {
/** Get the {@link IScope} instance.
*
* @return {@link IScope} instance */
IScope getScope();
}

/** Customizable getter for obtaining the current value of an option or positional parameter.
* When an option or positional parameter is matched on the command line, its getter or setter is invoked to capture the value.
Expand Down Expand Up @@ -9166,9 +9176,26 @@ private String defaultValueFromProvider() {
* @return whether this argument applies to all descendent subcommands of the command where it is defined
* @since 4.3 */
public ScopeType scopeType() { return scopeType; }

rsenden marked this conversation as resolved.
Show resolved Hide resolved
/** Check whether the {@link #getValue()} method is able to get an actual value from the current {@link #getter()}.
* @since 4.7 */
public boolean isValueGettable() {
if (getter instanceof IScoped) {
IScoped scoped = (IScoped) getter;
IScope scope = scoped.getScope();
if ( scope==null ) { return false; }
rsenden marked this conversation as resolved.
Show resolved Hide resolved
try {
return scope.get() != null;
} catch (Exception e) {
return false;
}
}
return true;
}

/** Returns the current value of this argument. Delegates to the current {@link #getter()}. */
public <T> T getValue() throws PicocliException {
if ( !isValueGettable() ) { return null; }
try {
return getter.<T>get();
} catch (PicocliException ex) { throw ex;
Expand Down Expand Up @@ -12018,11 +12045,14 @@ private static UnmatchedArgsBinding buildUnmatchedForMember(final IAnnotatedElem
}
}

static class FieldBinding implements IGetter, ISetter {
static class FieldBinding implements IGetter, ISetter, IScoped {
private final IScope scope;
private final Field field;
FieldBinding(Object scope, Field field) { this(ObjectScope.asScope(scope), field); }
FieldBinding(IScope scope, Field field) { this.scope = scope; this.field = field; }
public IScope getScope() {
return scope;
}
public <T> T get() throws PicocliException {
Object obj;
try { obj = scope.get(); }
Expand Down Expand Up @@ -12051,7 +12081,7 @@ public String toString() {
field.getDeclaringClass().getName(), field.getName());
}
}
static class MethodBinding implements IGetter, ISetter {
static class MethodBinding implements IGetter, ISetter, IScoped {
private final IScope scope;
private final Method method;
private final CommandSpec spec;
Expand All @@ -12061,6 +12091,9 @@ static class MethodBinding implements IGetter, ISetter {
this.method = method;
this.spec = spec;
}
public IScope getScope() {
return scope;
}
@SuppressWarnings("unchecked") public <T> T get() { return (T) currentValue; }
public <T> T set(T value) throws PicocliException {
Object obj;
Expand Down
41 changes: 41 additions & 0 deletions src/test/java/picocli/Issue1767.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package picocli;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static picocli.CommandLine.ScopeType.INHERIT;

import java.util.concurrent.Callable;

import org.junit.Test;

import picocli.CommandLine.ArgGroup;
import picocli.CommandLine.Command;
import picocli.CommandLine.IFactory;
import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Option;
import picocli.CommandLine.Spec;

public class Issue1767 {
@Command(name = "test")
static class TestCommand implements Callable<Integer> {
@ArgGroup TestArgGroup testArgGroup;
@Spec CommandSpec spec;

public static class TestArgGroup {
@Option(names = "-r")
public Integer option1;
}

@Override
public Integer call() throws Exception {
Integer value = spec.options().get(0).getValue();
return value==null ? 0 : value;
}
}

@Test
public void testIssue1772() {
assertEquals(5, new CommandLine(new TestCommand()).execute("-r", "5"));
assertEquals(0, new CommandLine(new TestCommand()).execute());
}
}