/**
* 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
*
* http://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.deephacks.tools4j.cli;
import org.deephacks.tools4j.cli.Command.Argument;
import org.deephacks.tools4j.cli.Command.Option;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Utility code are kept here to avoid distorting readability of other
* classes with code that is irrelevant to their domain.
*/
final class Utils {
/** name of the validator class */
public static final String VALIDATOR_CLASSNAME = "org.deephacks.tools4j.cli.Validator";
/** API class for JSR 303 1.0 bean validation */
public static final String JSR303_1_0_CLASSNAME = "javax.validation.Validation";
/** API class for JSR 303 1.1 bean validation, class only exist in 1.1 */
public static final String JSR303_1_1_CLASSNAME = "javax.validation.metadata.MethodDescriptor";
/** the instance of the Validator object */
private static Object validator;
/** new line character */
public static final String NEWLINE = System.getProperty("line.separator");
static final String AVAILABLE_CMDS_MSG = "Available commands are:";
static Object newInstance(String className) {
try {
Class<?> type = Thread.currentThread().getContextClassLoader().loadClass(className);
Class<?> enclosing = type.getEnclosingClass();
if (enclosing == null) {
Constructor<?> c = type.getDeclaredConstructor();
c.setAccessible(true);
return type.cast(c.newInstance());
}
Object o = enclosing.newInstance();
Constructor<?> cc = type.getDeclaredConstructor(enclosing);
cc.setAccessible(true);
return type.cast(cc.newInstance(o));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
static String stripTrailingWhitespace(String str) {
if (str == null || "".equals(str.trim())) {
return "";
}
StringBuffer sb = new StringBuffer(str);
int pos = str.length() - 1;
while (true) {
if (Character.isWhitespace(sb.charAt(pos))) {
sb.deleteCharAt(pos--);
} else {
break;
}
}
return sb.toString();
}
/**
* Validate that the method parameters if Bean Validation 1.1 is available
* on classpath.
*/
static void validateArgs(List<Object> args, Object instance, Method m, Command cmd) {
if (!onClasspath(JSR303_1_1_CLASSNAME)) {
return;
}
try {
Object validator = getValidator();
Method validate = validator.getClass().getMethod("validateArgs", List.class,
Object.class, Method.class, Command.class);
validate.invoke(validator, args, instance, m, cmd);
} catch (InvocationTargetException e) {
if (e.getTargetException() instanceof RuntimeException) {
throw (RuntimeException) e.getCause();
}
throw new RuntimeException(e.getCause());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Validate that the options if Bean Validation is available on classpath.
*/
static void validateOpts(Object instance) {
if (!onClasspath(JSR303_1_0_CLASSNAME)) {
return;
}
try {
Object validator = getValidator();
Method validate = validator.getClass().getMethod("validateOpts", Object.class);
validate.invoke(validator, instance);
} catch (InvocationTargetException e) {
if (e.getTargetException() instanceof RuntimeException) {
throw (RuntimeException) e.getTargetException();
}
throw new RuntimeException(e.getTargetException());
} catch (RuntimeException e) {
throw e;
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
private static Object getValidator() throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
if (validator == null) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
validator = cl.loadClass(VALIDATOR_CLASSNAME).newInstance();
}
return validator;
}
/**
* Checks to see if JSR303 implementation is
* available on classpath.
*/
static boolean onClasspath(String className) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
try {
cl.loadClass(className);
} catch (ClassNotFoundException e) {
return false;
}
return true;
}
public static HashMap<String, String> parseParamsJavadoc(String javadoc) {
if (javadoc == null || "".equals(javadoc.trim())) {
return new HashMap<String, String>();
}
final HashMap<String, String> params = new HashMap<String, String>();
final StringBuffer sb = new StringBuffer(javadoc.trim());
int pos;
while ((pos = sb.indexOf("@param")) > -1) {
sb.delete(0, pos + 1);
int end = sb.indexOf(" ");
sb.delete(0, end + 1);
end = sb.indexOf(" ");
String param = sb.substring(0, end);
sb.delete(0, end + 1);
int next = sb.indexOf("@");
if (next == -1) {
next = sb.length();
}
params.put(param, Utils.stripTrailingWhitespace(sb.substring(0, next)));
sb.delete(0, next);
}
return params;
}
public static String parseJavadoc(String javadoc) {
if (javadoc == null || "".equals(javadoc.trim())) {
return "";
}
javadoc = javadoc.trim();
int pos = javadoc.indexOf('@');
if (pos < 0) {
return javadoc;
}
javadoc = javadoc.substring(0, pos);
return Utils.stripTrailingWhitespace(javadoc);
}
public static void printAvailableCommandsHelp(Map<String, Command> commands) {
StringBuilder sb = new StringBuilder();
sb.append(AVAILABLE_CMDS_MSG).append(NEWLINE).append(NEWLINE);
int maxlength = getMaxCmdLength(commands);
for (Command cmd : commands.values()) {
StringBuilder sentence = new StringBuilder();
for (char c : cmd.getDoc().toCharArray()) {
sentence.append(c);
if (c == '.') {
break;
}
}
sb.append(String.format(" %-" + maxlength + "s : %s %n", cmd.getCommand(), sentence));
}
sb.append(NEWLINE).append(" Try `[command] --help' for more information.");
System.out.println(sb.toString());
}
public static void printCommandHelp(Command cmd) {
StringBuilder sb = new StringBuilder();
sb.append("usage: ").append(cmd.getCommand());
if (cmd.getOptions().size() > 0) {
sb.append(" [OPTION]... ");
}
for (Argument arg : cmd.getArguments()) {
sb.append(arg.getName()).append(" ");
}
int optlength = getMaxOptLength(cmd.getOptions());
sb.append(NEWLINE).append(NEWLINE);
sb.append(" ").append(cmd.getDoc()).append(NEWLINE).append(NEWLINE);
sb.append("OPTIONS").append(NEWLINE).append(NEWLINE);
for (Option opt : cmd.getOptions()) {
StringBuilder optstr = new StringBuilder();
optstr.append(" ");
optstr.append("-").append(opt.getShortName()).append(",");
optstr.append("--").append(opt.getLongName());
sb.append(String.format(" %-" + (optlength + 3) + "s : ", optstr.toString()));
List<String> lines = splitAndIndent(opt.getDoc(), optlength + 6);
for (String line : lines) {
sb.append(line).append(NEWLINE);
}
sb.append(NEWLINE);
}
if (cmd.getArguments().size() > 0) {
sb.append("ARGUMENTS").append(NEWLINE);
}
int arglength = getMaxArgLength(cmd.getArguments());
sb.append(NEWLINE);
for (Argument arg : cmd.getArguments()) {
sb.append(String.format(" %-" + arglength + "s : ", arg.getName()));
List<String> lines = splitAndIndent(arg.getDoc(), arglength + 3);
for (String line : lines) {
sb.append(line).append(NEWLINE);
}
sb.append(NEWLINE);
}
System.out.println(sb.toString());
}
private static List<String> splitAndIndent(String str, int indentLength) {
String[] split = str.split(System.getProperty("line.separator"));
List<String> lines = new ArrayList<String>();
for (int i = 0; i < split.length; i++) {
if (i == 0) {
// dont indent first line, it already is
lines.add(split[i]);
continue;
}
StringBuilder sb = new StringBuilder();
for (int j = 0; j < indentLength; j++) {
sb.append(' ');
}
sb.append(split[i]);
lines.add(sb.toString());
}
return lines;
}
private static int getMaxCmdLength(Map<String, Command> cmds) {
int length = 0;
for (Command cmd : cmds.values()) {
int l = cmd.getCommand().length();
if (l > length) {
length = l;
}
}
return length;
}
private static int getMaxArgLength(List<Argument> args) {
int length = 0;
for (Argument arg : args) {
int l = arg.getName().length();
if (l > length) {
length = l;
}
}
return length;
}
private static int getMaxOptLength(List<Option> args) {
int length = 0;
for (Option opt : args) {
int l = opt.getLongName().length() + opt.getShortName().length() + 3;
if (l > length) {
length = l;
}
}
return length;
}
}