/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.foundationdb.server.types.service;
import com.foundationdb.server.error.AkibanInternalException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;
public class ReflectiveInstanceFinder implements InstanceFinder
{
private static final Logger logger = LoggerFactory.getLogger(ReflectiveInstanceFinder.class);
private final Set<Class<?>> searchClasses;
private static final int SKIP = -1;
private static final int FIELD = 0;
private static final int ARRAY = 1;
private static final int COLLECTION = 2;
public ReflectiveInstanceFinder()
throws IllegalArgumentException, IllegalAccessException, InvocationTargetException
{
this(new ConfigurableClassFinder("typedirs.txt"));
}
public ReflectiveInstanceFinder(ClassFinder classFinder)
throws IllegalArgumentException, IllegalAccessException, InvocationTargetException
{
// collect all scalar TOverload instances
searchClasses = classFinder.findClasses();
}
@Override
public <T> Collection<? extends T> find(Class<? extends T> targetClass) {
return collectInstances(searchClasses, targetClass);
}
private static <T> Collection<T> collectInstances(Collection<Class<?>> classes, Class<T> target)
{
List<T> ret = new ArrayList<>();
for (Class<?> cls : classes) {
if (!Modifier.isPublic(cls.getModifiers()))
continue;
else
doCollecting(ret, cls, target);
}
return ret;
}
private static <T> void doCollecting(Collection<T> ret, Class<?> cls, Class<T> target)
{
try
{
// grab the static INSTANCEs fields
for (Field field : cls.getFields())
{
if (isRegistered(field))
switch(validateField(field, target))
{
case FIELD:
putItem(ret, field.get(null), target, field);
break;
case ARRAY:
for (Object item : (Object[])field.get(null)) {
if (target.isInstance(item))
putItem(ret, item, target, field);
}
break;
case COLLECTION:
try
{
for (Object raw : (Collection<?>)field.get(null)) {
if (target.isInstance(raw))
putItem(ret, raw, target, field);
}
break;
}
catch (ClassCastException e) {/* fall thru */}
default:
// SKIP (does nothing)
}
}
// grab the static methods that create instances
for (Method method : cls.getMethods())
{
if (isRegistered(method))
switch(validateMethod(method, target))
{
case FIELD:
putItem(ret, method.invoke(null), target, method);
break;
case ARRAY:
for (Object item : (Object[])method.invoke(null))
putItem(ret, item, target, method);
break;
case COLLECTION:
try
{
for (Object raw : (Collection<?>)method.invoke(null))
putItem(ret, raw, target, method);
break;
}
catch (ClassCastException e) {/* fall thru */}
default:
// SKIP (does nothing)
}
}
}
catch (IllegalAccessException e)
{
throw new FunctionsRegistryException(e.getMessage());
}
catch (InvocationTargetException ex)
{
throw new FunctionsRegistryException(ex.getMessage());
}
}
public static class FunctionsRegistryException extends AkibanInternalException {
public FunctionsRegistryException(String message) {
super(message);
}
}
private static <T> int validateMethod(Method method, Class<T> target)
{
int modifiers = method.getModifiers();
if (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers)
&& method.getParameterTypes().length == 0)
return assignable(method.getReturnType(), target, method.getGenericReturnType());
return SKIP;
}
private static <T> int validateField(Field field, Class<T> target)
{
int modifiers = field.getModifiers();
if (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers))
return assignable(field.getType(), target, field.getGenericType());
return SKIP;
}
private static <T> int assignable (Class<?> c, Class<T> target, Type genericTarget)
{
if (c.isArray() && target.isAssignableFrom(c.getComponentType())) {
return ARRAY;
}
else if (target.isAssignableFrom(c)) {
return FIELD;
}
else if (Collection.class.isAssignableFrom(c)) {
if (genericTarget instanceof ParameterizedType) {
ParameterizedType targetParams = (ParameterizedType) genericTarget;
Type[] genericArgs = targetParams.getActualTypeArguments();
assert genericArgs.length == 1 : Arrays.toString(genericArgs);
Type genericArg = genericArgs[0];
if (genericArg instanceof WildcardType) {
Type[] upperBounds = ((WildcardType)genericArg).getUpperBounds();
if (upperBounds.length > 1)
logger.debug("multiple upper bounds for {}: {}", genericTarget, Arrays.toString(upperBounds));
for (Type upperBound : upperBounds) {
if (isAssignableFrom(target, upperBound))
return COLLECTION;
}
}
else if (isAssignableFrom(target, genericArg))
return COLLECTION;
}
}
return SKIP;
}
private static boolean isAssignableFrom(Class<?> target, Type actualType) {
return (actualType instanceof Class<?>) && target.isAssignableFrom((Class<?>) actualType);
}
private static <T> void putItem(Collection<T> list, Object item, Class<T> targetClass, Object source)
{
T cast;
try {
cast = targetClass.cast(item);
} catch (ClassCastException e) {
String err = "while casting " + item + " from " + source + " to " + targetClass;
logger.error(err, e);
throw new ClassCastException(err);
}
list.add(cast);
}
private static boolean isRegistered(AccessibleObject field)
{
return field.getAnnotation(DontRegister.class) == null;
}
}