/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 c10n.share.utils;
import c10n.C10NKey;
import java.lang.reflect.Method;
import java.util.*;
public final class ReflectionUtils {
private static final String KEY_DELIM = ".";
/**
* <p>Work out method's bundle key.</p>
* <p/>
* <h2>Bundle key resolution</h2>
* <p>Bundle key is generated as follows:
* <ul>
* <li>If there are no {@link C10NKey} annotations, key is the <code>Class FQDN '.' Method Name</code>.
* If method has arguments, method name is post-fixed with argument types delimited
* with '_', e.g. <code>myMethod_String_int</code></li>
* <p/>
* <li>If declaring interface or any of the super-interfaces contain {@link C10NKey}
* annotation <code>C</code> then
* <ul>
* <li>For methods without {@link C10NKey} annotation, key becomes <code>C '.' Method Name</code></li>
* <li>For methods with {@link C10NKey} annotation <code>M</code>, key is <code>C '.' M</code></li>
* <li>For methods with {@link C10NKey} annotation <code>M</code>, value for which starts with
* a '.', the key is just <code>M</code> (i.e. key is assumed to be absolute)</li>
* </ul>
* </li>
* <li>If no declaring interfaces have {@link C10NKey} annotation, but a method contains annotation
* <code>M</code>, then key is just <code>M</code>.</li>
* <li>Lastly, if global key prefix is specified, it is always prepended to the final key, delimited by '.'</li>
* </ul>
* </p>
* <p/>
* <h2>Looking for c10n key in parent interfaces</h2>
* <p>The lookup of c10n key in parent interfaces is done breadth-first, starting from the declaring class.
* That is, if the declaring class does not have c10n key, all interfaces it extends are checked in declaration
* order first. If no key is found, this check is repeated for each of the super interfaces in the same order.
* </p>
*
* @param keyPrefix global key prefix
* @param method method to extract the key from
* @return method c10n bundle key (not null)
*/
public static String getC10NKey(String keyPrefix, Method method) {
String key = getKeyAnnotationBasedKey(method);
if (null == key) {
//fallback to default key based on class FQDN and method name
key = ReflectionUtils.getDefaultKey(method);
}
if (keyPrefix.length() > 0) {
key = keyPrefix + "." + key;
}
return key;
}
public static String getKeyAnnotationBasedKey(Method method) {
String parentKey = findParentKey(method);
String c10NKey = getKeyAnnotationValue(method);
if (null == parentKey && null == c10NKey) {
//C10NKey-based key is not available
return null;
}
String methodKey;
if (null != c10NKey) {
methodKey = c10NKey;
if (methodKey.startsWith(KEY_DELIM)) {
//found an absolute key. Get rid of the
//leading dot and look no further
return methodKey.substring(1, methodKey.length());
}
} else {
//method key based on methodName & arguments
methodKey = getMethodKey(method);
}
if (parentKey != null) {
return parentKey + KEY_DELIM + methodKey;
}
return methodKey;
}
/**
* <p>Get the value provided with the {@link c10n.C10NKey} annotation. If annotation
* is not declared returns <code>null</code></p>
*
* @param method method for which to retrieve the value {@link c10n.C10NKey} annotation
* @return value of the declared annotation. <code>null</code> if not present.
*/
public static String getKeyAnnotationValue(Method method) {
C10NKey c10NKey = method.getAnnotation(C10NKey.class);
if (null != c10NKey) {
return c10NKey.value();
}
return null;
}
/**
* <p>Works out the non-{@link C10NKey} bundle key for method, based on its
* class FQDN and method name (plus argument types),
* e.g. <code>com.myCompany.MyClass.myMethod_String_boolean</code></p>
*
* @param method the method for which to work out the key(not null)
* @return method's default bundle key(not null)
*/
public static String getDefaultKey(Method method) {
StringBuilder sb = new StringBuilder();
getDefaultKey(method, sb);
return sb.toString();
}
public static void getDefaultKey(Method method, StringBuilder sb) {
getFQNString(method.getDeclaringClass(), sb);
sb.append(KEY_DELIM);
getMethodKey(method, sb);
}
private static String getMethodKey(Method method) {
StringBuilder sb = new StringBuilder();
getMethodKey(method, sb);
return sb.toString();
}
private static void getMethodKey(Method method, StringBuilder sb) {
sb.append(method.getName());
Class<?>[] params = method.getParameterTypes();
if (params.length > 0) {
sb.append('_');
for (int i = 0; i < params.length; i++) {
sb.append(params[i].getSimpleName());
if (i + 1 < params.length) {
sb.append("_");
}
}
}
}
public static String getFQNString(Class<?> clazz) {
StringBuilder sb = new StringBuilder();
getFQNString(clazz, sb);
return sb.toString();
}
public static void getFQNString(Class<?> clazz, StringBuilder sb) {
sb.append(clazz.getPackage().getName()).append(KEY_DELIM);
getClassFQNString(clazz, sb);
}
private static void getClassFQNString(Class<?> clazz, StringBuilder sb) {
Iterator<Class<?>> it = typeEnclosureHierarchy(clazz).descendingIterator();
while (it.hasNext()) {
sb.append(it.next().getSimpleName());
if (it.hasNext()) {
sb.append(KEY_DELIM);
}
}
}
private static String findParentKey(Method method) {
for (Class<?> clazz : expandInterfaceHierarchy(method.getDeclaringClass())) {
C10NKey key = clazz.getAnnotation(C10NKey.class);
if (null != key) {
return key.value();
}
}
return null;
}
private static LinkedList<Class<?>> typeEnclosureHierarchy(Class<?> clazz) {
LinkedList<Class<?>> typeHierarchy = new LinkedList<Class<?>>();
do {
typeHierarchy.add(clazz);
} while ((clazz = clazz.getEnclosingClass()) != null);
return typeHierarchy;
}
/*
* Returns a list of super-interface breadth-first.
*/
private static List<Class<?>> expandInterfaceHierarchy(Class<?> clazz) {
List<Class<?>> res = new ArrayList<Class<?>>();
res.add(clazz);
expandInterfaceHierarchy(clazz, res);
return res;
}
private static void expandInterfaceHierarchy(Class<?> clazz, List<Class<?>> res) {
Class<?>[] intfs = clazz.getInterfaces();
if (null != intfs) {
Collections.addAll(res, intfs);
for (Class<?> intf : intfs) {
expandInterfaceHierarchy(intf, res);
}
}
}
}