Skip to content

Commit 85976cf

Browse files
committed
Native support for JCommander
- Fixes #340
1 parent c532dc2 commit 85976cf

File tree

6 files changed

+160
-41
lines changed

6 files changed

+160
-41
lines changed

spring-shell-core/src/main/java/org/springframework/shell/Shell.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import javax.validation.ValidatorFactory;
3333

3434
import org.jline.utils.Signals;
35+
import org.slf4j.Logger;
36+
import org.slf4j.LoggerFactory;
3537

3638
import org.springframework.beans.factory.annotation.Autowired;
3739
import org.springframework.core.MethodParameter;
@@ -55,6 +57,7 @@
5557
*/
5658
public class Shell {
5759

60+
private final static Logger log = LoggerFactory.getLogger(Shell.class);
5861
private final ResultHandlerService resultHandlerService;
5962

6063
/**
@@ -287,14 +290,17 @@ private void validateArgs(Object[] args, MethodTarget methodTarget) {
287290
* parameters that could not be resolved
288291
*/
289292
private Object[] resolveArgs(Method method, List<String> wordsForArgs) {
293+
log.debug("Resolving args {} {}", method, wordsForArgs);
290294
List<MethodParameter> parameters = Utils.createMethodParameters(method).collect(Collectors.toList());
291295
Object[] args = new Object[parameters.size()];
292296
Arrays.fill(args, UNRESOLVED);
293297
for (ParameterResolver resolver : parameterResolvers) {
298+
log.debug("Resolving args with {}", resolver);
294299
for (int argIndex = 0; argIndex < args.length; argIndex++) {
295300
MethodParameter parameter = parameters.get(argIndex);
296301
if (args[argIndex] == UNRESOLVED && resolver.supports(parameter)) {
297302
args[argIndex] = resolver.resolve(parameter, wordsForArgs).resolvedValue();
303+
log.debug("Resolved {} {} {} {}", method, args[argIndex], resolver, parameter);
298304
}
299305
}
300306
}

spring-shell-jcommander-adapter/src/main/java/org/springframework/shell/jcommander/JCommanderParameterResolver.java

Lines changed: 36 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
/*
2-
* Copyright 2015 the original author or authors.
3-
*
4-
* Licensed under the Apache License, Version 2.0 (the "License");
5-
* you may not use this file except in compliance with the License.
6-
* You may obtain a copy of the License at
7-
*
8-
* https://www.apache.org/licenses/LICENSE-2.0
9-
*
10-
* Unless required by applicable law or agreed to in writing, software
11-
* distributed under the License is distributed on an "AS IS" BASIS,
12-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13-
* See the License for the specific language governing permissions and
14-
* limitations under the License.
15-
*/
16-
2+
* Copyright 2015-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
1716
package org.springframework.shell.jcommander;
1817

1918
import java.lang.annotation.Annotation;
@@ -34,6 +33,8 @@
3433
import com.beust.jcommander.Parameter;
3534
import com.beust.jcommander.ParameterException;
3635
import com.beust.jcommander.ParametersDelegate;
36+
import org.slf4j.Logger;
37+
import org.slf4j.LoggerFactory;
3738

3839
import org.springframework.beans.BeanUtils;
3940
import org.springframework.beans.factory.annotation.Autowired;
@@ -56,6 +57,7 @@
5657
*/
5758
public class JCommanderParameterResolver implements ParameterResolver {
5859

60+
private final static Logger log = LoggerFactory.getLogger(JCommanderParameterResolver.class);
5961
private static final Collection<Class<? extends Annotation>> JCOMMANDER_ANNOTATIONS = Arrays.asList(Parameter.class,
6062
DynamicParameter.class, ParametersDelegate.class);
6163

@@ -71,27 +73,25 @@ public boolean supports(MethodParameter parameter) {
7173
AtomicBoolean isSupported = new AtomicBoolean(false);
7274
Class<?> parameterType = parameter.getParameterType();
7375

74-
if (isLegalReflectiveAccess(parameterType)) {
75-
ReflectionUtils.doWithFields(parameterType, field -> {
76-
if (isLegalReflectiveAccess(field.getType())) {
77-
ReflectionUtils.makeAccessible(field);
78-
boolean hasAnnotation = Arrays.stream(field.getAnnotations())
79-
.map(Annotation::annotationType)
80-
.anyMatch(JCOMMANDER_ANNOTATIONS::contains);
81-
isSupported.compareAndSet(false, hasAnnotation);
82-
}
83-
});
84-
85-
ReflectionUtils.doWithMethods(parameterType, method -> {
86-
if (isLegalReflectiveAccess(method.getDeclaringClass())) {
87-
ReflectionUtils.makeAccessible(method);
88-
boolean hasAnnotation = Arrays.stream(method.getAnnotations())
89-
.map(Annotation::annotationType)
90-
.anyMatch(Parameter.class::equals);
91-
isSupported.compareAndSet(false, hasAnnotation);
92-
}
93-
});
94-
}
76+
log.debug("isLegalReflectiveAccess before");
77+
ReflectionUtils.doWithFields(parameterType, field -> {
78+
ReflectionUtils.makeAccessible(field);
79+
boolean hasAnnotation = Arrays.stream(field.getAnnotations())
80+
.map(Annotation::annotationType)
81+
.anyMatch(JCOMMANDER_ANNOTATIONS::contains);
82+
isSupported.compareAndSet(false, hasAnnotation);
83+
log.debug("isLegalReflectiveAccess fields {}", hasAnnotation);
84+
});
85+
86+
ReflectionUtils.doWithMethods(parameterType, method -> {
87+
ReflectionUtils.makeAccessible(method);
88+
boolean hasAnnotation = Arrays.stream(method.getAnnotations())
89+
.map(Annotation::annotationType)
90+
.anyMatch(Parameter.class::equals);
91+
isSupported.compareAndSet(false, hasAnnotation);
92+
log.debug("isLegalReflectiveAccess methods {}", hasAnnotation);
93+
});
94+
log.debug("isLegalReflectiveAccess supports {}", isSupported.get());
9595
return isSupported.get();
9696
}
9797

@@ -137,11 +137,6 @@ private Stream<com.beust.jcommander.ParameterDescription> streamAllJCommanderDes
137137
jCommander.getMainParameterValue() != null ? Stream.of(jCommander.getMainParameterValue()) : Stream.empty());
138138
}
139139

140-
// Java 9+ warn if you try to reflect on JDK types
141-
private static boolean isLegalReflectiveAccess(Class<?> clzz) {
142-
return (!clzz.getName().startsWith("java"));
143-
}
144-
145140
@Override
146141
public List<CompletionProposal> complete(MethodParameter parameter, CompletionContext context) {
147142
JCommander jCommander = createJCommander(parameter);
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[
2+
{
3+
"name":"com.beust.jcommander.JCommander"
4+
},
5+
{
6+
"name":"com.beust.jcommander.converters.IntegerConverter",
7+
"queryAllDeclaredConstructors":true,
8+
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
9+
},
10+
{
11+
"name":"com.beust.jcommander.validators.NoValidator",
12+
"methods":[{"name":"<init>","parameterTypes":[] }]
13+
},
14+
{
15+
"name":"com.beust.jcommander.validators.NoValueValidator",
16+
"methods":[{"name":"<init>","parameterTypes":[] }]
17+
}
18+
]
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.shell.samples.jcommander;
17+
18+
import java.util.ArrayList;
19+
import java.util.List;
20+
21+
import javax.validation.constraints.Min;
22+
23+
import com.beust.jcommander.Parameter;
24+
25+
/**
26+
* An example straight from the JCommander documentation.
27+
*
28+
* @author Eric Bottard
29+
* @author Cédric Beust
30+
*/
31+
public class Args {
32+
33+
@Parameter
34+
private List<String> parameters = new ArrayList<>();
35+
36+
@Min(3)
37+
@Parameter(names = { "-log", "-verbose" }, description = "Level of verbosity")
38+
private Integer verbose = 1;
39+
40+
@Parameter(names = "-groups", description = "Comma-separated list of group names to be run")
41+
private String groups;
42+
43+
@Parameter(names = "-debug", description = "Debug mode")
44+
private boolean debug = false;
45+
46+
@Override
47+
public String toString() {
48+
return "Args{" +
49+
"parameters=" + parameters +
50+
", verbose=" + verbose +
51+
", groups='" + groups + '\'' +
52+
", debug=" + debug +
53+
'}';
54+
}
55+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.shell.samples.jcommander;
17+
18+
import org.springframework.shell.standard.ShellComponent;
19+
import org.springframework.shell.standard.ShellMethod;
20+
import org.springframework.shell.standard.ShellOption;
21+
22+
import javax.validation.Valid;
23+
24+
/**
25+
* A class with JCommander commands.
26+
*
27+
* @author Eric Bottard
28+
*/
29+
@ShellComponent
30+
public class JCommanderCommands {
31+
32+
@ShellMethod("Bind parameters to JCommander POJO.")
33+
public String jcommander(@ShellOption(optOut = true) @Valid Args args) {
34+
return "You said " + args;
35+
}
36+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[
2+
{
3+
"name":"org.springframework.shell.samples.jcommander.Args",
4+
"allDeclaredFields":true,
5+
"queryAllDeclaredMethods":true,
6+
"queryAllDeclaredConstructors":true,
7+
"methods":[{"name":"<init>","parameterTypes":[] }]
8+
}
9+
]

0 commit comments

Comments
 (0)