/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2011-2013 ForgeRock AS. All Rights Reserved
*
* The contents of this file are subject to the terms
* of the Common Development and Distribution License
* (the License). You may not use this file except in
* compliance with the License.
*
* You can obtain a copy of the License at
* http://forgerock.org/license/CDDLv1.0.html
* See the License for the specific language governing
* permission and limitations under the License.
*
* When distributing Covered Code, include this CDDL
* Header Notice in each file and include the License file
* at http://forgerock.org/license/CDDLv1.0.html
* If applicable, add the following below the CDDL Header,
* with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*/
package org.forgerock.openidm.shell;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import org.apache.commons.lang3.StringUtils;
import org.apache.felix.service.command.Descriptor;
import org.apache.felix.service.command.Parameter;
import org.forgerock.openidm.shell.felixgogo.MetaVar;
/**
* An abstract command scope for the Felix Gogo command processor.
*/
public abstract class CustomCommandScope {
/** formatting whitespace constant to insert before the option. */
protected static final String LEAD_OPTION_SPACE = " ";
/** formatting whitespace constant used to pad the option's description. */
protected static final String OPTIONS_SPACE = " ";
/**
* Get the {@link org.apache.felix.service.command.CommandProcessor#COMMAND_FUNCTION} value.
* <p/>
* TODO add description
*
* @return retrun a new map where the key is the command name and the value is the description.
*/
public abstract Map<String, String> getFunctionMap();
/**
* Get the {@link org.apache.felix.service.command.CommandProcessor#COMMAND_SCOPE} value.
* <p/>
* TODO add description
*
* @return the scope value
*/
public abstract String getScope();
/**
* Gets usage information for method by name. Fetches the method with the longest list of arguments.
*
* @param name Name of the method to search for
* @return String containing usage information
* @throws NoSuchMethodException if no such method can be found
*/
public String getUsage(String name) throws NoSuchMethodException {
Method m = getLongestMethodByName(name);
return getUsage(m);
}
/**
* Gets usage information for a specified method.
*
* @param method Method object to pull information from.
* @return String containing usage information.
*/
public String getUsage(Method method) {
StringBuilder usage = new StringBuilder("Usage: ").append(method.getName());
List<String> args = getArguments(method);
List<String> opts = getParameters(method);
if (opts.size() > 0) {
usage.append(" [options]");
}
for (String arg : args) {
usage.append(" <").append(arg).append(">");
}
usage.append("\nScope: ").append(getScope());
if (!opts.isEmpty()) {
usage.append("\nOptions:");
}
for (String opt : opts) {
usage.append("\n").append(opt);
}
return usage.toString();
}
/**
* Fetches the header for the method with the longest argument list.
*
* @param name Name of the method to search for
* @return String containing header information for the method
*/
protected String getLongHeader(String name) {
String header;
try {
Method method = getLongestMethodByName(name);
header = getHeader(method);
} catch (NoSuchMethodException e) {
header = "[WARNING] No such method exists.";
}
return header;
}
/**
* Fetches the header for the method with the shortest argument list.
*
* @param name Name of the method to search for
* @return String containing header information for the method
*/
protected String getShortHeader(String name) {
String header;
try {
Method method = getShortestMethodByName(name);
header = getHeader(method);
} catch (NoSuchMethodException e) {
header = "[WARNING] No such method exists.";
}
return header;
}
/**
* Fetches the header from a specified method.
*
* @param method method to pull a header from
* @return String containing the header for the specified method
*/
protected String getHeader(Method method) {
Descriptor desc = method.getAnnotation(Descriptor.class);
return desc.value();
}
/**
* Fetches the list of arguments from a specified method.
* Arguments are defined by annotated method arguments with only a @Descriptor.
* @param method method to fetch the list of arguments from
* @return a list of Strings containing argument descriptions
*/
protected List<String> getArguments(Method method) {
List<String> args = new ArrayList<String>();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
for (Annotation[] annotations : parameterAnnotations) {
String desc = null;
boolean foundParam = false;
for (Annotation a : annotations) {
if (a instanceof Parameter) {
foundParam = true;
} else if (a instanceof Descriptor) {
Descriptor d = (Descriptor) a;
desc = d.value();
}
}
if (desc != null && !foundParam) {
args.add(desc);
}
}
return args;
}
/**
* Fetches the list of parameters from a specified method.
* <p>
* Parameters are defined as by method arguments annotated with @Parameter
*
* @param method method to fetch the list of parameters from
* @return a list of Strings containing parameter descriptions
*/
protected List<String> getParameters(Method method) {
List<String> opts = new ArrayList<String>();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
for (Annotation[] annotations : parameterAnnotations) {
String names = null;
String desc = "";
String metaVar = null;
for (Annotation a : annotations) {
if (a instanceof Parameter) {
Parameter param = (Parameter) a;
names = StringUtils.join(param.names(), ", ");
} else if (a instanceof MetaVar) {
MetaVar m = (MetaVar) a;
metaVar = m.value();
} else if (a instanceof Descriptor) {
Descriptor d = (Descriptor) a;
desc = d.value();
}
}
String namesWithMeta = StringUtils.isBlank(metaVar) ? names : names + " " + metaVar;
if (names != null) {
String str = LEAD_OPTION_SPACE
+ namesWithMeta
+ OPTIONS_SPACE.substring(Math.min(namesWithMeta.length(), OPTIONS_SPACE.length()))
+ desc;
opts.add(str);
}
}
return opts;
}
/**
* Fetches a sorted list of methods all sharing a specified name.
*
* This list is returned in increasing order of parameters.
* @param name the name of the method list to retrieve
* @return a sorted list of methods
*/
protected List<Method> getAllMethodsByName(String name) {
Method[] allMethods = this.getClass().getDeclaredMethods();
List<Method> allNamedMethods = new ArrayList<Method>();
for (Method m : allMethods) {
if (m.getName().equals(name)) {
allNamedMethods.add(m);
}
}
Collections.sort(allNamedMethods, new Comparator<Method>() {
public int compare(Method o1, Method o2) {
Integer l1 = o1.getParameterTypes().length;
Integer l2 = o2.getParameterTypes().length;
return l1.compareTo(l2);
}
});
return allNamedMethods;
}
/**
* Fetch a method specified by name (gets the method with the largest number of parameters).
*
* @param name of the method to fetch
* @return the method with the largest number of parameters by name
* @throws NoSuchMethodException if no such method exists
*/
protected Method getLongestMethodByName(String name) throws NoSuchMethodException {
List<Method> methods = getAllMethodsByName(name);
if (methods.isEmpty()) {
throw new NoSuchMethodException();
}
return methods.get(methods.size() - 1);
}
/**
* Fetch a method specified by name (gets the method with the shortest number of parameters).
*
* @param name of the method to fetch
* @return the method with the largest number of parameters by name
* @throws NoSuchMethodException if no such method exists
*/
protected Method getShortestMethodByName(String name) throws NoSuchMethodException {
List<Method> methods = getAllMethodsByName(name);
if (methods.isEmpty()) {
throw new NoSuchMethodException();
}
return methods.get(0);
}
}