/*
* Copyright 2003-2016 JetBrains s.r.o.
*
* 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 jetbrains.mps.util;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
/**
* Support for generated classes with query methods, like QueriesGenerated in generator and actions.
* Refactored out from {@link QueryMethodGenerated} to facilitate reuse and better lifecycle control.
* {@implNote} Implementation of the class relies on unique method names.
* XXX perhaps, shall evolve into facility that would use annotations to find out proper methods
*
* @author Artem Tikhomirov
* @since 3.4
*/
public class QueryMethods {
private final Class<?> myQueriesClass;
private final Map<String, Method> myMethods;
public QueryMethods(@NotNull Class<?> queriesClass) {
myQueriesClass = queriesClass;
// to avoid extra synchronization, populate at construction time
HashMap<String, Method> methods = new HashMap<String, Method>();
for (Method declaredMethod : myQueriesClass.getDeclaredMethods()) {
if (!Modifier.isPublic(declaredMethod.getModifiers())) {
continue;
}
String name = declaredMethod.getName();
declaredMethod.setAccessible(true);
methods.put(name, declaredMethod);
}
myMethods = methods;
}
public Method getMethodPrim(String methodName) throws NoSuchMethodException {
final Method method = myMethods.get(methodName);
if (method == null) {
throw new NoSuchMethodException(String.format("couldn't find method '%s' in '%s'", methodName, myQueriesClass.getName()));
}
return method;
}
public boolean hasMethod(String methodName) {
return myMethods.containsKey(methodName);
}
public <T> QueryMethod<T> getMethod(final String methodName) {
return new QueryMethod<T>() {
@Override
@SuppressWarnings("unchecked")
public T invoke(Object contextObject) throws IllegalQueryMethodException, InvocationTargetException {
try {
final Method method = getMethodPrim(methodName);
return (T) method.invoke(null, contextObject);
} catch (IllegalArgumentException | IllegalAccessException | NoSuchMethodException e ) {
String s = String.format("method invocation error: '%s' in '%s'", methodName, myQueriesClass.getName());
throw new IllegalQueryMethodException(s, e);
}
}
};
}
/**
* Handy wrapper around {@link Method} to have typed return value and streamlined error message for errors.
* @param <T>
*/
public interface QueryMethod<T> {
/**
* @param contextObject whatever query method takes as an argument
* @return whatever query method returns
* @throws IllegalQueryMethodException if there were troubles accessing the method (missing, wrong arguments, non-accessible, etc).
* @throws InvocationTargetException if there were exceptions in the user-supplied method implementation code.
*/
T invoke(Object contextObject) throws IllegalQueryMethodException, InvocationTargetException;
}
public static class IllegalQueryMethodException extends Exception {
public IllegalQueryMethodException(String message, Throwable throwable) {
super(message, throwable);
}
}
}