/*
* Copyright [1999-2015] Wellcome Trust Sanger Institute and the EMBL-European Bioinformatics Institute
* Copyright [2016-2017] EMBL-European Bioinformatics Institute
*
* 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.ensembl.healthcheck.configurationmanager;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.ensembl.healthcheck.configurationmanager.AbstractAliasAwareConfigurationBacking.configurationDataType;
import uk.co.flamingpenguin.jewel.cli.Option;
/**
*
* Provides methods for classes that operate on objects implementing the
* configuration interface. These objects are the objects proxying the
* configuration interface and the ConfigurationDumper.
*
*/
public class ConfigurationProcessor<T> {
/**
* @param m
* method of type java.lang.reflect.Method
* @return true, if the method expects a java.util.List of Strings, false
* otherwise.
*/
boolean listOfStringsExpected(Method m) {
boolean listOfStringsExpected = false;
Type t = m.getGenericReturnType();
if (t instanceof ParameterizedType) {
Type rawType = ((ParameterizedType) t).getRawType();
Type actualType = ((ParameterizedType) t).getActualTypeArguments()[0];
listOfStringsExpected = rawType.equals(java.util.List.class)
&& actualType.equals(java.lang.String.class);
}
return listOfStringsExpected;
}
/**
* @param m
* - A method
* @return True, if the method name begins with get, false otherwise
*/
boolean isGetMethod(Method m) {
return isGetMethod(m.getName());
}
boolean isGetMethod(String m) {
return m.startsWith("get");
}
/**
* @param m
* - A method
* @return True, if the method name begins with is, false otherwise
*/
boolean isIsMethod(Method m) {
return isIsMethod(m.getName());
}
boolean isIsMethod(String m) {
return m.startsWith("is");
}
/**
* @param m
* @return
*
* returns the name of a variable requested by a method.
*
* getHost will return host getDatabase will return database etc.
*
*/
String getVariableRequested(Method m) {
return getVariableRequested(m.getName());
}
String getVariableRequested(String methodName) {
if (isGetMethod(methodName)) {
return methodName.substring(3).toLowerCase();
}
if (isIsMethod(methodName)) {
return methodName.substring(2).toLowerCase();
}
throw new IllegalArgumentException("Method " + methodName
+ " is an unknown type of method!");
}
/**
* <p>
* Creates a map that maps the name of a variable, as it may be requested in
* a get method like configuration.get*someVariable* to all its possible
* aliases. Aliases for variables can be specified in the configuration
* interface as long- or short name annotations like these:
* </p>
*
* <pre>
* @Option(longName = "output.databases")
* List<String> getOutputDatabases();
* </pre>
*
* or
*
* <pre>
* @Option(shortName = "c")
* List<File> getConf();
* </pre>
*
*/
public Map<String, Set<String>> createVarName2AllAliases(
Class<T> configurationClass) {
Map<String, Set<String>> varName2AllAliases = new HashMap<String, Set<String>>();
List<Method> getMethods = getGetMethods(configurationClass);
for (Method getMethod : getMethods) {
varName2AllAliases.put(getVariableRequested(getMethod),
getAliases(getMethod));
}
return varName2AllAliases;
}
public Map<String, String> createAlias2CanonicalVarName(
Class<T> configurationClass) {
Map<String, String> alias2MethodName = new HashMap<String, String>();
List<Method> getMethods = getGetMethods(configurationClass);
for (Method getMethod : getMethods) {
String methodName = getVariableRequested(getMethod);
for (String possibleName : getAliases(getMethod)) {
alias2MethodName.put(possibleName, methodName);
}
}
return alias2MethodName;
}
public Map<String, configurationDataType> createVarName2DataType(
Class<T> configurationClass) {
Map<String, configurationDataType> varName2DataType = new HashMap<String, configurationDataType>();
List<Method> getMethods = getGetMethods(configurationClass);
for (Method getMethod : getMethods) {
configurationDataType returnTypeExpected = null;
if (getMethod.getReturnType().equals(String.class)) {
returnTypeExpected = configurationDataType.String;
}
if (getMethod.getReturnType().equals(java.util.List.class)) {
returnTypeExpected = configurationDataType.List_Of_Strings;
}
if (returnTypeExpected == null) {
throw new RuntimeException("Unknown return type "
+ getMethod.getReturnType().getName() + " for "
+ getMethod.getName() + "!");
}
varName2DataType.put(getVariableRequested(getMethod),
returnTypeExpected);
}
return varName2DataType;
}
/**
* <p>
* This method takes a getMethod from the configuration interface as a
* parameter and returns the names (aliases) by which this parameter could
* be requested for as a set of strings.
* </p>
*
* <p>
* The aliases are taken from the annotations in the configuration
* interface. The names are in the "shortName" and the "longName"
* Annotations.
* </p>
*
* @param getMethod
* - A method
* @return A list of names of configuration variables that can be returned
*
*/
Set<String> getAliases(Method getMethod) {
Set<String> possibleRequestedNames = new HashSet<String>();
String variableRequested = getVariableRequested(getMethod);
possibleRequestedNames.add(variableRequested);
Annotation[] annotations = getMethod.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof uk.co.flamingpenguin.jewel.cli.Option) {
Option o = (uk.co.flamingpenguin.jewel.cli.Option) annotation;
for (String sn : o.shortName()) {
if (sn.length() > 0) {
possibleRequestedNames.add(sn);
}
}
if (o.longName().length() > 0) {
possibleRequestedNames.add(o.longName());
}
}
}
return possibleRequestedNames;
}
/**
* @param configurationClass
* @return A Map that maps a the name of a parameter from the get* or is*
* methods of a configuration class to a set of Strings under which
* this parameter may be known in the properties file.
*
* For example the getOutputDatabase method is for the parameter
* outputDatabase which may be represented in the properties file as
* "outputDatabase" or "output.database". The alternatives can be
* specified as an Option annotation in the configuration interface
* like this
*
* @Option(longName="output.databases") List<String> getOutputDatabases();
*
* or this:
*
* @Option(shortName="c") List<File> getConf();
*
*/
public Map<String, Set<String>> createParameterAliasesMap(
Class<T> configurationClass) {
List<Method> getMethods = getGetMethods(configurationClass);
Map<String, Set<String>> parameterAliasesMap = new HashMap<String, Set<String>>();
for (Method getMethod : getMethods) {
parameterAliasesMap.put(getVariableRequested(getMethod),
getAliases(getMethod));
}
return parameterAliasesMap;
}
/**
* @param configurationClass
* @return A list of get methods. The get methods returned are the ones
* meant for accessing configuration parameters.
*/
public List<Method> getGetMethods(Class<T> configurationClass) {
Method[] methods = configurationClass.getMethods();
// These get methods are not about configuration parameters.
Set<String> specialGetMethodNames = new HashSet<String>();
// Java methods that start with get, but have nothing to do with
// accessing parameters.
//
specialGetMethodNames.add("getInvocationHandler");
specialGetMethodNames.add("getProxyClass");
specialGetMethodNames.add("getClass");
//
// Special method, when called and delegated to jewelcli causes it to
// autogenerate a help message. It doese not provide access to
// parameters so should be ignored when dealing with them.
//
specialGetMethodNames.add("getHelp");
List<Method> methodList = new ArrayList<Method>();
for (Method method : methods) {
String methodName = method.getName();
boolean isGetMethodForConfiguration = methodName.startsWith("get")
&& !(specialGetMethodNames.contains(methodName));
if (isGetMethodForConfiguration) {
methodList.add(method);
}
}
return methodList;
}
}