blob: cd4954ce8d4ef67792611400e77162640feb1b8a [file] [log] [blame]
/*
* 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 org.apache.ode.jacob.oo;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
import org.apache.ode.jacob.ChannelRef;
import org.apache.ode.jacob.Expression;
import org.apache.ode.jacob.Message;
import org.apache.ode.jacob.soup.CommChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class ClassUtil {
public static final Method RUN_METHOD;
public static final String RUN_METHOD_NAME = "run";
public static final String RUN_METHOD_ACTION = "java.lang.Runnable#run";
public static final String SYNCH_RET_METHOD_ACTION = "org.apache.ode.jacob.oo.Synch#ret";
private static final Set<Method> RUN_METHOD_SET = new HashSet<Method>();
private static final Logger LOG = LoggerFactory.getLogger(ClassUtil.class);
static {
try {
// Resolve the {@link Runnable#run} method once statically
RUN_METHOD = Runnable.class.getMethod(RUN_METHOD_NAME, new Class[]{});
RUN_METHOD_SET.add(RUN_METHOD);
} catch (Exception e) {
throw new Error("Cannot resolve 'run()' method", e);
}
}
private ClassUtil() {
// Utility class
}
public static Set<Method> runMethodSet() {
return RUN_METHOD_SET;
}
public static Message createMessage(Runnable target, String action, Object[] args, Channel replyTo) {
Message message = new Message();
message.setTo(new ChannelRef(target));
message.setReplyTo(replyTo == null ? null : new ChannelRef(replyTo));
message.setAction(action);
message.setBody(args);
return message;
}
public static Message createMessage(CommChannel target, String action, Object[] args, CommChannel replyTo) {
Message message = new Message();
message.setTo(new ChannelRef(target));
message.setReplyTo(replyTo == null ? null : new ChannelRef(replyTo));
message.setAction(action);
message.setBody(args);
return message;
}
public static String getActionForMethod(Method channelMethod) {
if (channelMethod == null) {
return null;
}
MessageHandler handler = channelMethod.getAnnotation(MessageHandler.class);
if (handler != null) {
return handler.value();
}
Class<?> clazz = channelMethod.getDeclaringClass();
if (Runnable.class.isAssignableFrom(clazz)
&& channelMethod.getName() == "run"
&& channelMethod.getParameterTypes().length == 0) {
return RUN_METHOD_ACTION;
}
if (!Channel.class.isAssignableFrom(clazz)) {
LOG.trace("Action '{}' can only be defined for a Channel extension", channelMethod.getName());
return null;
}
LOG.trace("Probing Channel class '{}' for declaration of method '{}'", clazz.getName(), channelMethod.getName());
for (Class<?> c : clazz.getInterfaces()) {
String action = getChannelMethodAction(c, channelMethod);
if (action != null) {
return action;
}
}
// ... if clazz is a Channel interface itself
return getChannelMethodAction(clazz, channelMethod);
}
/**
* @param clazz
* @param method
* @return
*
* The default action associated with a Channel method is defined as Class#method
* following a convention similar to javadoc.
*/
private static String getChannelMethodAction(Class<?> clazz, Method method) {
if (Channel.class.isAssignableFrom(clazz)) {
try {
Method m = clazz.getMethod(method.getName(), method.getParameterTypes());
return m.getDeclaringClass().getName() + "#" + m.getName();
} catch (SecurityException e) { // ignore
} catch (NoSuchMethodException e) { // ignore
}
}
return null;
}
public static Expression findActionMethod(final Set<Method> implementedMethods) {
return new Expression() {
@SuppressWarnings("unchecked")
public <T> T evaluate(Message message, Class<T> type) {
String action = message.getAction();
if (Method.class.equals(type) && action != null) {
for (Method m : implementedMethods) {
if (action.equals(ClassUtil.getActionForMethod(m))) {
return (T)m;
}
}
}
return null;
}
};
}
public static Set<Method> getImplementedMethods(Set<Method> methods, Class<?> clazz) {
// TODO: this can be optimized (some 20 times faster in my tests) by keeping a private
// map of interfaces to methods: Map<Class<?>, Method[]> and just do lookups
Class<?>[] interfaces = clazz.getInterfaces();
for (Class<?> iface : interfaces) {
// TODO: the test below could be more generic...
if (iface != ChannelProxy.class) {
for (Method method : iface.getDeclaredMethods()) {
methods.add(method);
}
getImplementedMethods(methods, iface);
}
}
return methods;
}
}