/***********************************************************************************************************************
*
* Copyright (C) 2010-2013 by the Stratosphere project (http://stratosphere.eu)
*
* 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 eu.stratosphere.sopremo.packages;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import eu.stratosphere.sopremo.aggregation.Aggregation;
import eu.stratosphere.sopremo.aggregation.AggregationFunction;
import eu.stratosphere.sopremo.function.Callable;
import eu.stratosphere.sopremo.function.JavaMethod;
import eu.stratosphere.sopremo.function.SopremoFunction;
import eu.stratosphere.sopremo.pact.SopremoUtil;
import eu.stratosphere.sopremo.type.IJsonNode;
import eu.stratosphere.util.reflect.ReflectUtil;
/**
*/
public class DefaultFunctionRegistry extends DefaultRegistry<Callable<?, ?>> implements IFunctionRegistry {
private final Map<String, Callable<?, ?>> methods = new HashMap<String, Callable<?, ?>>();
/**
* Initializes DefaultFunctionRegistry.
*/
public DefaultFunctionRegistry() {
}
public DefaultFunctionRegistry(final NameChooser nameChooser) {
super(nameChooser);
}
/*
* (non-Javadoc)
* @see
* eu.stratosphere.sopremo.ISopremoType#toString(java.lang.StringBuilder)
*/
@Override
public void appendAsString(final Appendable appendable) throws IOException {
appendable.append("Method registry: {");
boolean first = true;
for (final Entry<String, Callable<?, ?>> method : this.methods.entrySet()) {
if (first)
first = false;
else
appendable.append(", ");
appendable.append(method.getKey()).append(": ");
method.getValue().appendAsString(appendable);
}
appendable.append("}");
}
@Override
public boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (this.getClass() != obj.getClass())
return false;
final DefaultFunctionRegistry other = (DefaultFunctionRegistry) obj;
return this.methods.equals(other.methods);
}
/*
* (non-Javadoc)
* @see
* eu.stratosphere.sopremo.packages.AbstractMethodRegistry#findMethod(java
* .lang.String)
*/
@Override
public Callable<?, ?> get(final String name) {
return this.methods.get(name);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + this.methods.hashCode();
return result;
}
/*
* (non-Javadoc)
* @see
* eu.stratosphere.sopremo.packages.IMethodRegistry#getRegisteredMethods()
*/
@Override
public Set<String> keySet() {
return Collections.unmodifiableSet(this.methods.keySet());
}
@Override
public void put(final Class<?> javaFunctions) {
final List<Method> functions =
this.getCompatibleMethods(ReflectUtil.getMethods(javaFunctions, null, Modifier.STATIC | Modifier.PUBLIC));
for (final Method method : functions)
this.put(method);
final Class<?>[] declaredClasses = javaFunctions.getDeclaredClasses();
for (final Class<?> innerClass : declaredClasses)
if ((innerClass.getModifiers() & Modifier.STATIC) != 0)
try {
if (Aggregation.class.isAssignableFrom(innerClass)) {
final Aggregation aggregation = (Aggregation) ReflectUtil.newInstance(innerClass);
for (final String name : this.getNames(innerClass))
this.put(name, new AggregationFunction(aggregation));
} else if (SopremoFunction.class.isAssignableFrom(innerClass)) {
final SopremoFunction function = (SopremoFunction) ReflectUtil.newInstance(innerClass);
for (final String name : this.getNames(innerClass))
this.put(name, function);
}
} catch (final Exception e) {
SopremoUtil.LOG.warn(String.format("Cannot access inner class %s: %s", innerClass, e));
}
final List<Field> declaredFields =
ReflectUtil.getFields(javaFunctions, null, Modifier.STATIC | Modifier.PUBLIC | Modifier.FINAL);
for (final Field field : declaredFields)
try {
if (Aggregation.class.isAssignableFrom(field.getType())) {
final Aggregation aggregation = (Aggregation) field.get(null);
for (final String name : this.getNames(field))
this.put(name, new AggregationFunction(aggregation));
} else if (SopremoFunction.class.isAssignableFrom(field.getType())) {
final SopremoFunction function = (SopremoFunction) field.get(null);
for (final String name : this.getNames(field))
this.put(name, function);
}
} catch (final Throwable e) {
SopremoUtil.LOG.warn(String.format("Cannot access field %s: %s", field, e));
}
if (FunctionRegistryCallback.class.isAssignableFrom(javaFunctions))
((FunctionRegistryCallback) ReflectUtil.newInstance(javaFunctions)).registerFunctions(this);
}
@Override
public void put(final Method method) {
String[] names = this.getNames(method);
if (names == null)
names = new String[] { method.getName() };
for (final String name : names) {
Callable<?, ?> javaMethod = this.get(name);
if (javaMethod == null || !(javaMethod instanceof JavaMethod))
this.put(name, javaMethod = this.createJavaMethod(name, method));
((JavaMethod) javaMethod).addSignature(method);
}
}
/*
* (non-Javadoc)
* @see
* eu.stratosphere.sopremo.packages.AbstractMethodRegistry#registerMethod
* (java.lang.String, eu.stratosphere.sopremo.function.MeteorMethod)
*/
@Override
public void put(final String name, final Callable<?, ?> method) {
this.methods.put(name, method);
}
/*
* (non-Javadoc)
* @see
* eu.stratosphere.sopremo.packages.IFunctionRegistry#put(java.lang.String,
* java.lang.Class, java.lang.String)
*/
@Override
public void put(final String registeredName, final Class<?> clazz, final String staticMethodName) {
final List<Method> functions =
this.getCompatibleMethods(ReflectUtil.getMethods(clazz, staticMethodName, Modifier.STATIC | Modifier.PUBLIC));
if (functions.isEmpty())
throw new IllegalArgumentException(
String.format("Method %s not found in class %s", staticMethodName, clazz));
Callable<?, ?> javaMethod = this.get(registeredName);
if (javaMethod == null || !(javaMethod instanceof JavaMethod))
this.put(registeredName, javaMethod = this.createJavaMethod(registeredName, functions.get(0)));
for (final Method method : functions)
((JavaMethod) javaMethod).addSignature(method);
}
protected JavaMethod createJavaMethod(final String registeredName, final Method implementation) {
final JavaMethod javaMethod = new JavaMethod(registeredName);
javaMethod.addSignature(implementation);
return javaMethod;
}
private List<Method> getCompatibleMethods(final List<Method> methods) {
final List<Method> functions = new ArrayList<Method>();
for (final Method method : methods) {
final boolean compatibleSignature = isCompatibleSignature(method);
if (SopremoUtil.LOG.isDebugEnabled())
SopremoUtil.LOG.debug(String.format("Method %s is %s compatible", method, compatibleSignature ? ""
: " not"));
if (compatibleSignature)
functions.add(method);
}
return functions;
}
private static boolean isCompatibleSignature(final Method method) {
final Class<?> returnType = method.getReturnType();
if (!IJsonNode.class.isAssignableFrom(returnType))
return false;
final Class<?>[] parameterTypes = method.getParameterTypes();
// check if the individual parameters match
for (int index = 0; index < parameterTypes.length; index++)
if (!IJsonNode.class.isAssignableFrom(parameterTypes[index])
&&
!(index == parameterTypes.length - 1 && method.isVarArgs() && IJsonNode.class.isAssignableFrom(parameterTypes[index].getComponentType())))
return false;
return true;
}
}