diff --git a/spring-shell-core/src/main/java/org/springframework/shell/Shell.java b/spring-shell-core/src/main/java/org/springframework/shell/Shell.java index 1d15705a..3faec219 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/Shell.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/Shell.java @@ -32,6 +32,8 @@ import javax.validation.Validator; import javax.validation.ValidatorFactory; import org.jline.utils.Signals; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.MethodParameter; @@ -55,6 +57,7 @@ import org.springframework.util.ReflectionUtils; */ public class Shell { + private final static Logger log = LoggerFactory.getLogger(Shell.class); private final ResultHandlerService resultHandlerService; /** @@ -287,14 +290,17 @@ public class Shell { * parameters that could not be resolved */ private Object[] resolveArgs(Method method, List wordsForArgs) { + log.debug("Resolving args {} {}", method, wordsForArgs); List parameters = Utils.createMethodParameters(method).collect(Collectors.toList()); Object[] args = new Object[parameters.size()]; Arrays.fill(args, UNRESOLVED); for (ParameterResolver resolver : parameterResolvers) { + log.debug("Resolving args with {}", resolver); for (int argIndex = 0; argIndex < args.length; argIndex++) { MethodParameter parameter = parameters.get(argIndex); if (args[argIndex] == UNRESOLVED && resolver.supports(parameter)) { args[argIndex] = resolver.resolve(parameter, wordsForArgs).resolvedValue(); + log.debug("Resolved {} {} {} {}", method, args[argIndex], resolver, parameter); } } } diff --git a/spring-shell-jcommander-adapter/src/main/java/org/springframework/shell/jcommander/JCommanderParameterResolver.java b/spring-shell-jcommander-adapter/src/main/java/org/springframework/shell/jcommander/JCommanderParameterResolver.java index 737f8837..00b32086 100644 --- a/spring-shell-jcommander-adapter/src/main/java/org/springframework/shell/jcommander/JCommanderParameterResolver.java +++ b/spring-shell-jcommander-adapter/src/main/java/org/springframework/shell/jcommander/JCommanderParameterResolver.java @@ -1,19 +1,18 @@ /* - * Copyright 2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - + * Copyright 2015-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.springframework.shell.jcommander; import java.lang.annotation.Annotation; @@ -34,6 +33,8 @@ import com.beust.jcommander.JCommander; import com.beust.jcommander.Parameter; import com.beust.jcommander.ParameterException; import com.beust.jcommander.ParametersDelegate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -56,6 +57,7 @@ import static org.springframework.shell.Utils.unCamelify; */ public class JCommanderParameterResolver implements ParameterResolver { + private final static Logger log = LoggerFactory.getLogger(JCommanderParameterResolver.class); private static final Collection> JCOMMANDER_ANNOTATIONS = Arrays.asList(Parameter.class, DynamicParameter.class, ParametersDelegate.class); @@ -71,27 +73,25 @@ public class JCommanderParameterResolver implements ParameterResolver { AtomicBoolean isSupported = new AtomicBoolean(false); Class parameterType = parameter.getParameterType(); - if (isLegalReflectiveAccess(parameterType)) { - ReflectionUtils.doWithFields(parameterType, field -> { - if (isLegalReflectiveAccess(field.getType())) { - ReflectionUtils.makeAccessible(field); - boolean hasAnnotation = Arrays.stream(field.getAnnotations()) - .map(Annotation::annotationType) - .anyMatch(JCOMMANDER_ANNOTATIONS::contains); - isSupported.compareAndSet(false, hasAnnotation); - } - }); + log.debug("isLegalReflectiveAccess before"); + ReflectionUtils.doWithFields(parameterType, field -> { + ReflectionUtils.makeAccessible(field); + boolean hasAnnotation = Arrays.stream(field.getAnnotations()) + .map(Annotation::annotationType) + .anyMatch(JCOMMANDER_ANNOTATIONS::contains); + isSupported.compareAndSet(false, hasAnnotation); + log.debug("isLegalReflectiveAccess fields {}", hasAnnotation); + }); - ReflectionUtils.doWithMethods(parameterType, method -> { - if (isLegalReflectiveAccess(method.getDeclaringClass())) { - ReflectionUtils.makeAccessible(method); - boolean hasAnnotation = Arrays.stream(method.getAnnotations()) - .map(Annotation::annotationType) - .anyMatch(Parameter.class::equals); - isSupported.compareAndSet(false, hasAnnotation); - } - }); - } + ReflectionUtils.doWithMethods(parameterType, method -> { + ReflectionUtils.makeAccessible(method); + boolean hasAnnotation = Arrays.stream(method.getAnnotations()) + .map(Annotation::annotationType) + .anyMatch(Parameter.class::equals); + isSupported.compareAndSet(false, hasAnnotation); + log.debug("isLegalReflectiveAccess methods {}", hasAnnotation); + }); + log.debug("isLegalReflectiveAccess supports {}", isSupported.get()); return isSupported.get(); } @@ -137,11 +137,6 @@ public class JCommanderParameterResolver implements ParameterResolver { jCommander.getMainParameterValue() != null ? Stream.of(jCommander.getMainParameterValue()) : Stream.empty()); } - // Java 9+ warn if you try to reflect on JDK types - private static boolean isLegalReflectiveAccess(Class clzz) { - return (!clzz.getName().startsWith("java")); - } - @Override public List complete(MethodParameter parameter, CompletionContext context) { JCommander jCommander = createJCommander(parameter); diff --git a/spring-shell-jcommander-adapter/src/main/resources/META-INF/native-image/reflect-config.json b/spring-shell-jcommander-adapter/src/main/resources/META-INF/native-image/reflect-config.json new file mode 100644 index 00000000..3c883bcc --- /dev/null +++ b/spring-shell-jcommander-adapter/src/main/resources/META-INF/native-image/reflect-config.json @@ -0,0 +1,18 @@ +[ + { + "name":"com.beust.jcommander.JCommander" + }, + { + "name":"com.beust.jcommander.converters.IntegerConverter", + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] + }, + { + "name":"com.beust.jcommander.validators.NoValidator", + "methods":[{"name":"","parameterTypes":[] }] + }, + { + "name":"com.beust.jcommander.validators.NoValueValidator", + "methods":[{"name":"","parameterTypes":[] }] + } +] diff --git a/spring-shell-samples/src/main/java/org/springframework/shell/samples/jcommander/Args.java b/spring-shell-samples/src/main/java/org/springframework/shell/samples/jcommander/Args.java new file mode 100644 index 00000000..774853d4 --- /dev/null +++ b/spring-shell-samples/src/main/java/org/springframework/shell/samples/jcommander/Args.java @@ -0,0 +1,55 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.shell.samples.jcommander; + +import java.util.ArrayList; +import java.util.List; + +import javax.validation.constraints.Min; + +import com.beust.jcommander.Parameter; + +/** + * An example straight from the JCommander documentation. + * + * @author Eric Bottard + * @author Cédric Beust + */ +public class Args { + + @Parameter + private List parameters = new ArrayList<>(); + + @Min(3) + @Parameter(names = { "-log", "-verbose" }, description = "Level of verbosity") + private Integer verbose = 1; + + @Parameter(names = "-groups", description = "Comma-separated list of group names to be run") + private String groups; + + @Parameter(names = "-debug", description = "Debug mode") + private boolean debug = false; + + @Override + public String toString() { + return "Args{" + + "parameters=" + parameters + + ", verbose=" + verbose + + ", groups='" + groups + '\'' + + ", debug=" + debug + + '}'; + } +} diff --git a/spring-shell-samples/src/main/java/org/springframework/shell/samples/jcommander/JCommanderCommands.java b/spring-shell-samples/src/main/java/org/springframework/shell/samples/jcommander/JCommanderCommands.java new file mode 100644 index 00000000..e9d4d1e1 --- /dev/null +++ b/spring-shell-samples/src/main/java/org/springframework/shell/samples/jcommander/JCommanderCommands.java @@ -0,0 +1,36 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.shell.samples.jcommander; + +import org.springframework.shell.standard.ShellComponent; +import org.springframework.shell.standard.ShellMethod; +import org.springframework.shell.standard.ShellOption; + +import javax.validation.Valid; + +/** + * A class with JCommander commands. + * + * @author Eric Bottard + */ +@ShellComponent +public class JCommanderCommands { + + @ShellMethod("Bind parameters to JCommander POJO.") + public String jcommander(@ShellOption(optOut = true) @Valid Args args) { + return "You said " + args; + } +} diff --git a/spring-shell-samples/src/main/resources/META-INF/native-image/reflect-config.json b/spring-shell-samples/src/main/resources/META-INF/native-image/reflect-config.json new file mode 100644 index 00000000..3ac7faeb --- /dev/null +++ b/spring-shell-samples/src/main/resources/META-INF/native-image/reflect-config.json @@ -0,0 +1,9 @@ +[ + { + "name":"org.springframework.shell.samples.jcommander.Args", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] + } +]