Package reactor.core.dynamic

Source Code of reactor.core.dynamic.DynamicReactorFactory$DynamicMethod

/*
* Copyright (c) 2011-2014 Pivotal Software, Inc.
*
*  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 reactor.core.dynamic;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;

import reactor.convert.Converter;
import reactor.core.Environment;
import reactor.core.Reactor;
import reactor.core.dynamic.annotation.Dispatcher;
import reactor.core.dynamic.annotation.Notify;
import reactor.core.dynamic.annotation.On;
import reactor.core.dynamic.reflect.MethodNotificationKeyResolver;
import reactor.core.dynamic.reflect.MethodSelectorResolver;
import reactor.core.dynamic.reflect.SimpleMethodNotificationKeyResolver;
import reactor.core.dynamic.reflect.SimpleMethodSelectorResolver;
import reactor.core.spec.ReactorSpec;
import reactor.core.spec.Reactors;
import reactor.event.Event;
import reactor.event.dispatch.SynchronousDispatcher;
import reactor.event.registry.Registration;
import reactor.event.routing.ArgumentConvertingConsumerInvoker;
import reactor.event.routing.ConsumerInvoker;
import reactor.event.selector.Selector;
import reactor.function.Consumer;
import reactor.function.Function;

/**
* A {@literal DynamicReactorFactory} is responsible for generating a {@link Proxy} based on the given interface, that
* intercepts calls to the interface and translates them into the appropriate {@link
* Reactor#on(reactor.event.selector.Selector, reactor.function.Consumer)} or {@link Reactor#notify(Object, Event)}
* calls. Methods that are translated to {@code on} calls should take a single argument that is a {@link Consumer},
* {@link Function}, {@link Runnable}, or {@link Callable}. Methods that are translated to {@code notify} calls should
* take zero or one arguments. If the method takes zero arguments it will be translated to {@link
* Reactor#notify(Object)}. IF the method takes one argument it will be translated to {@link Reactor#notify(Object,
* Event)}, {@link Event#wrap(Object) wrapping} the argument in an {@link Event} if necessary.
* <p/>
* The translation of calls on the interface to calls to {@code on} and the selector that is used is determined by the
* {@link MethodSelectorResolver}s. The translation of calls on the interface to calls to {@link Reactor#notify(Object,
* Event) notify} is determined by the {@link MethodNotificationKeyResolver}s.
* <p/>
* By default, the creation of the selector for {@code on} calls will look for the {@link On} annotation. In its
* absence, if the method name begins with {@code on}, the method name minus the on prefix and with the first character
* lower-cased, will be used to create the selector. For example, the selector for a method named {@code onAlpha} will
* be {@code "alpha"}.
* <p/>
* By default, the translation for {@code notify} calls will look for the {@link Notify} annotation. In its absence, if
* the method name begins with {@code notify}, the method name minus the notify prefix and with the first character
* lower-cased, will be used to create the notification key. For example, the notification key for a method named
* {@code notifyAlpha} will be {@code "alpha"}.
*
* @param <T>
*     The type to proxy
*
* @author Jon Brisbin
* @author Stephane Maldini
* @author Andy Wilkinson
*/
public class DynamicReactorFactory<T extends DynamicReactor> {

  private final Environment                         env;
  private final Class<T>                            type;
  private final List<MethodSelectorResolver>        selectorResolvers;
  private final List<MethodNotificationKeyResolver> notificationKeyResolvers;
  private final ConsumerInvoker                     consumerInvoker;
  private final Converter                           converter;
  private final Map<Method, DynamicMethod> dynamicMethods = new HashMap<Method, DynamicMethod>();


  /**
   * Creates a new DynamicReactorFactory that will use the given {@code env} when creating its {@link Reactor}. The
   * proxy
   * that is generated will be based upon the given {@code type}. A {@link SimpleMethodSelectorResolver} will be used to
   * create selectors for proxied methods. A {@link SimpleMethodNotificationKeyResolver} will be used to create
   * notification keys for proxied methods.
   *
   * @param env
   *     The Environment to use when creating the underlying Reactor.
   * @param type
   *     The type of the proxy
   */
  public DynamicReactorFactory(Environment env,
                               Class<T> type) {
    this(env,
         type,
         Arrays.<MethodSelectorResolver>asList(new SimpleMethodSelectorResolver()),
         Arrays.<MethodNotificationKeyResolver>asList(new SimpleMethodNotificationKeyResolver()),
         new ArgumentConvertingConsumerInvoker(null),
         null);
  }

  /**
   * Creates a new DynamicReactorFactory that will use the given {@code env} when creating its {@link Reactor}. The
   * proxy
   * that is generated will be based upon the given {@code type}. The {@code selectorResolvers} will be used to create
   * selectors for proxied methods and the {@code notificationKeyResolvers} will be used to create notification keys for
   * proxied methods.
   *
   * @param env
   *     The Environment to use when creating the underlying Reactor.
   * @param type
   *     The type of the proxy
   * @param selectorResolvers
   *     used to resolve the selectors for proxied methods
   * @param notificationKeyResolvers
   *     used to resolve the notification keys for proxied methods
   */
  public DynamicReactorFactory(Environment env,
                               Class<T> type,
                               List<MethodSelectorResolver> selectorResolvers,
                               List<MethodNotificationKeyResolver> notificationKeyResolvers,
                               ConsumerInvoker consumerInvoker,
                               Converter converter) {
    this.env = env;
    this.type = type;
    this.selectorResolvers = selectorResolvers;
    this.notificationKeyResolvers = notificationKeyResolvers;
    this.consumerInvoker = consumerInvoker;
    this.converter = converter;
  }

  /**
   * Get the list of {@link MethodSelectorResolver}s in use.
   *
   * @return The {@link MethodSelectorResolver}s in use.
   */
  public List<MethodSelectorResolver> getSelectorResolvers() {
    return Collections.unmodifiableList(selectorResolvers);
  }

  /**
   * Get the {@link ConsumerInvoker} to use when invoking {@link Consumer Consumers}.
   *
   * @return The {@link ConsumerInvoker} in use.
   */
  public ConsumerInvoker getConsumerInvoker() {
    return consumerInvoker;
  }

  /**
   * Get the {@link Converter} to use when coercing arguments.
   *
   * @return The {@link Converter} to use.
   */
  public Converter getConverter() {
    return converter;
  }

  /**
   * Generate a {@link Proxy} based on the type used to create this factory.
   *
   * @return A proxy based on the type used to create the factory
   */
  @SuppressWarnings("unchecked")
  public T create() {
    return (T)Proxy.newProxyInstance(
        DynamicReactorFactory.class.getClassLoader(),
        new Class[]{type},
        new ReactorInvocationHandler<T>(type)
    );
  }

  private class ReactorInvocationHandler<U> implements InvocationHandler {
    private final Map<Method, Selector> selectors        = new HashMap<Method, Selector>();
    private final Map<Method, Object>   notificationKeys = new HashMap<Method, Object>();

    private final Reactor reactor;

    private ReactorInvocationHandler(Class<U> type) {
      Dispatcher d = find(type, Dispatcher.class);
      this.reactor = createReactor(d);

      for(Method m : type.getDeclaredMethods()) {
        if(m.getDeclaringClass() == Object.class || m.getName().contains("$")) {
          continue;
        }

        DynamicMethod dm = new DynamicMethod();

        dm.returnsRegistration = Registration.class.isAssignableFrom(m.getReturnType());
        dm.returnsProxy = type.isAssignableFrom(m.getReturnType());

        if(isOn(m)) {
          for(MethodSelectorResolver msr : selectorResolvers) {
            if(msr.supports(m)) {
              Selector sel = msr.apply(m);
              if(null != sel) {
                selectors.put(m, sel);
                dynamicMethods.put(m, dm);
                break;
              }
            }
          }
        } else if(isNotify(m)) {
          for(MethodNotificationKeyResolver notificationKeyResolver : notificationKeyResolvers) {
            if(notificationKeyResolver.supports(m)) {
              String notificationKey = notificationKeyResolver.apply(m);
              if(null != notificationKey) {
                notificationKeys.put(m, notificationKey);
                dynamicMethods.put(m, dm);
                break;
              }
            }
          }
        }
      }
    }

    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public Object invoke(Object proxy, Method method, final Object[] args) throws Throwable {
      final DynamicMethod dm = dynamicMethods.get(method);

      if(isOn(method)) {
        Selector sel = selectors.get(method);
        if(null == sel) {
          return proxy;
        }

        if(args.length > 1) {
          throw new IllegalArgumentException("Only pass a single Consumer, Function, Runnable, or Callable");
        }

        final Object arg = args[0];

        Registration reg = null;
        if(Consumer.class.isInstance(arg)) {
          reg = reactor.on(sel, (Consumer)arg);
        } else if(Function.class.isInstance(arg)) {
          reg = reactor.receive(sel, (Function)arg);
        } else if(Runnable.class.isInstance(arg)) {
          reg = reactor.on(sel, new Consumer<Event<?>>() {
            @Override
            public void accept(Event<?> event) {
              ((Runnable)arg).run();
            }
          });
        } else if(Callable.class.isInstance(arg)) {
          reg = reactor.receive(sel, new Function<Event<?>, Object>() {
            @Override
            public Object apply(Event<?> event) {
              try {
                return ((Callable)arg).call();
              } catch(Exception e) {
                reactor.notify(e.getClass(), Event.wrap(e));
              }
              return null;
            }
          });
        } else if(null == converter || !converter.canConvert(arg.getClass(), Consumer.class)) {
          throw new IllegalArgumentException(
              String.format("No Converter available to convert '%s' to Consumer", arg.getClass().getName())
          );
        }

        return (dm.returnsRegistration ? reg : dm.returnsProxy ? proxy : null);
      } else if(isNotify(method)) {
        Object key = notificationKeys.get(method);
        if(null == key) {
          return proxy;
        }

        if(args.length == 0) {
          reactor.notify(key);
        } else if(args.length == 1) {
          reactor.notify(key, (Event.class.isInstance(args[0]) ? (Event)args[0] : Event.wrap(args[0])));
        } else {
          // TODO: handle multiple args
        }

        return (dm.returnsProxy ? proxy : null);
      } else {
        throw new NoSuchMethodError(method.getName());
      }
    }

    private Reactor createReactor(Dispatcher dispatcherAnnotation) {
      ReactorSpec reactorSpec = Reactors.reactor().env(env);
      if(dispatcherAnnotation != null) {
        if("sync".equals(dispatcherAnnotation.value())) {
          reactorSpec.dispatcher(SynchronousDispatcher.INSTANCE);
        } else {
          reactorSpec.dispatcher(dispatcherAnnotation.value());
        }
      }
      return reactorSpec.get();
    }
  }

  private static boolean isOn(Method m) {
    return m.getName().startsWith("on") || null != m.getAnnotation(On.class);
  }

  private static boolean isNotify(Method m) {
    return m.getName().startsWith("notify") || null != m.getAnnotation(Notify.class);
  }

  @SuppressWarnings("unchecked")
  private static <T extends Annotation> T find(Class<?> type, Class<T> annoType) {
    if(type.getDeclaredAnnotations().length > 0) {
      for(Annotation anno : type.getDeclaredAnnotations()) {
        if(annoType.isAssignableFrom(anno.getClass())) {
          return ((T)anno);
        }
      }
    }
    return null;
  }

  private static final class DynamicMethod {
    boolean returnsRegistration = false;
    boolean returnsProxy        = true;
  }

}
TOP

Related Classes of reactor.core.dynamic.DynamicReactorFactory$DynamicMethod

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.