Package com.google.sitebricks.routing

Source Code of com.google.sitebricks.routing.DefaultPageBook

package com.google.sitebricks.routing;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;

import javax.validation.ValidationException;

import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.ThreadSafe;

import org.jetbrains.annotations.Nullable;

import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.MapMaker;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.inject.BindingAnnotation;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
import com.google.sitebricks.ActionDescriptor;
import com.google.sitebricks.At;
import com.google.sitebricks.Bricks;
import com.google.sitebricks.Renderable;
import com.google.sitebricks.Show;
import com.google.sitebricks.client.Transport;
import com.google.sitebricks.conversion.TypeConverter;
import com.google.sitebricks.headless.Reply;
import com.google.sitebricks.headless.Request;
import com.google.sitebricks.headless.Service;
import com.google.sitebricks.http.As;
import com.google.sitebricks.http.Get;
import com.google.sitebricks.http.Head;
import com.google.sitebricks.http.Select;
import com.google.sitebricks.http.Trace;
import com.google.sitebricks.http.negotiate.ContentNegotiator;
import com.google.sitebricks.http.negotiate.Negotiation;
import com.google.sitebricks.rendering.Strings;
import com.google.sitebricks.rendering.control.DecorateWidget;
import com.google.sitebricks.transport.Form;

/**
* contains active uri/widget mappings
*
* @author Dhanji R. Prasanna (dhanji@gmail.com)
*/
@ThreadSafe @Singleton
public class DefaultPageBook implements PageBook {
  //multimaps TODO refactor to multimap?

  @GuardedBy("lock") // All three following fields
  private final Map<String, List<PageTuple>> pages = Maps.newHashMap();
  private final List<PageTuple> universalMatchingPages = Lists.newArrayList();
  private final Map<String, PageTuple> pagesByName = Maps.newHashMap();

  private final ConcurrentMap<Class<?>, PageTuple> classToPageMap =
      new MapMaker()
          .weakKeys()
          .weakValues()
          .makeMap();

  private final Object lock = new Object();
  private final Injector injector;

  @Inject
  public DefaultPageBook(Injector injector) {
    this.injector = injector;
  }

  @Override @SuppressWarnings("unchecked")
  public Collection<List<Page>> getPageMap() {
    return (Collection) pages.values();
  }

  // Page registration (internal) APIs
  public Page serviceAt(String uri, Class<?> pageClass) {
    // Handle subpaths, registering each as a separate instance of the page
    // tuple.
    for (Method method : pageClass.getDeclaredMethods()) {
      if (method.isAnnotationPresent(At.class)) {

        // This is a subpath expression.
        At at = method.getAnnotation(At.class);
        String subpath = at.value();

        // Validate subpath
        if (!subpath.startsWith("/") || subpath.isEmpty() || subpath.length() == 1) {
          throw new IllegalArgumentException(String.format(
              "Subpath At(\"%s\") on %s.%s() must begin with a \"/\" and must not be empty",
              subpath, pageClass.getName(), method.getName()));
        }

        subpath = uri + subpath;

        // Register as headless web service.
        doAt(subpath, pageClass, true);
      }
    }

    return doAt(uri, pageClass, true);
  }

  public PageTuple at(String uri, Class<?> clazz) {
    return at(uri, clazz, clazz.isAnnotationPresent(Service.class));
  }

  @Override
  public void at(String uri, List<ActionDescriptor> actionDescriptors,
                 Map<Class<? extends Annotation>, String> methodSet) {
    Multimap<String, Action> actions = HashMultimap.create();

    for (ActionDescriptor actionDescriptor : actionDescriptors) {
      for (Class<? extends Annotation> method : actionDescriptor.getMethods()) {
        String methodString = methodSet.get(method);
        Action action = actionDescriptor.getAction();

        if (null == action) {
          action = injector.getInstance(actionDescriptor.getActionKey());
        } else {
          injector.injectMembers(action);
        }

        actions.put(methodString, new SpiAction(action, actionDescriptor));
      }
    }

    // Register into the book!
    at(new PageTuple(uri, new PathMatcherChain(uri), null, true, false, injector, actions));
  }

  private void at(PageTuple page) {
    // Is Universal?
    synchronized (lock) {
      String key = firstPathElement(page.getUri());
      if (isVariable(key)) {
        universalMatchingPages.add(page);
      } else {
        multiput(pages, key, page);
      }
    }

    // Actions are not backed by classes.
    if (page.pageClass() != null)
      classToPageMap.put(page.pageClass(), page);
  }

  private PageTuple at(String uri, Class<?> clazz, boolean headless) {

      // Handle subpaths, registering each as a separate instance of the page
      // tuple.
      for (Method method : clazz.getDeclaredMethods()) {
        if (method.isAnnotationPresent(At.class)) {

          // This is a subpath expression.
          At at = method.getAnnotation(At.class);
          String subpath = at.value();

          // Validate subpath
          if (!subpath.startsWith("/") || subpath.isEmpty() || subpath.length() == 1) {
            throw new IllegalArgumentException(String.format(
                "Subpath At(\"%s\") on %s.%s() must begin with a \"/\" and must not be empty",
                subpath, clazz.getName(), method.getName()));
          }

          subpath = uri + subpath;

          // Register as headless web service.
          doAt(subpath, clazz, headless);
        }
      }
     
      return doAt(uri, clazz, headless);

  }

  private PageTuple doAt(String uri, Class<?> clazz, boolean headless) {

    final String key = firstPathElement(uri);
    final PageTuple pageTuple =
        new PageTuple(uri, new PathMatcherChain(uri), clazz, injector, headless, false);

    synchronized (lock) {
      //is universal? (i.e. first element is a variable)
      if (isVariable(key))
        universalMatchingPages.add(pageTuple);
      else {
        multiput(pages, key, pageTuple);
      }
    }

    // Does not need to be inside lock, as it is concurrent.
    classToPageMap.put(clazz, pageTuple);

    return pageTuple;
  }

  public Page embedAs(Class<?> clazz, String as) {
    Preconditions.checkArgument(null == clazz.getAnnotation(Service.class),
        "You cannot embed headless web services!");
    PageTuple pageTuple = new PageTuple("", PathMatcherChain.ignoring(), clazz, injector, false, false);

    synchronized (lock) {
      pagesByName.put(as.toLowerCase(), pageTuple);
    }

    return pageTuple;
  }

  public Page decorate(Class<?> pageClass) {
    Preconditions.checkArgument(null == pageClass.getAnnotation(Service.class),
      "You cannot extend headless web services!");
    PageTuple pageTuple = new PageTuple("", PathMatcherChain.ignoring(), pageClass, injector, false, true);

    // store page with a special name used by DecorateWidget
    String name = DecorateWidget.embedNameFor(pageClass);
    synchronized (lock) {
      pagesByName.put(name, pageTuple);
    }

    return pageTuple;
  }

  public Page nonCompilingGet(String uri) {
    // The regular get is non compiling, in our case. So these methods are identical.
    return get(uri);
  }

  private static void multiput(Map<String, List<PageTuple>> pages, String key,
                               PageTuple page) {
    List<PageTuple> list = pages.get(key);

    if (null == list) {
      list = new ArrayList<PageTuple>();
      pages.put(key, list);
    }

    list.add(page);
  }

  private static boolean isVariable(String key) {
    return key.length() > 0 && ':' == key.charAt(0);
  }

  String firstPathElement(String uri) {
    String shortUri = uri.substring(1);

    final int index = shortUri.indexOf("/");

    return (index >= 0) ? shortUri.substring(0, index) : shortUri;
  }

  @Nullable
  public Page get(String uri) {
    final String key = firstPathElement(uri);

    List<PageTuple> tuple = pages.get(key);

    //first try static first piece
    if (null != tuple) {

      //first try static first piece
      for (PageTuple pageTuple : tuple) {
        if (pageTuple.matcher.matches(uri))
          return pageTuple;
      }
    }

    //now try dynamic first piece (how can we make this faster?)
    for (PageTuple pageTuple : universalMatchingPages) {
      if (pageTuple.matcher.matches(uri))
        return pageTuple;
    }

    //nothing matched
    return null;
  }

  public Page forName(String name) {
    return pagesByName.get(name);
  }

  @Nullable
  public Page forInstance(Object instance) {
    Class<?> aClass = instance.getClass();
    PageTuple targetType = classToPageMap.get(aClass);

    // Do a super crawl to detect the target type.
    while (null == targetType) {
      aClass = aClass.getSuperclass();
      targetType = classToPageMap.get(aClass);

      // Stop at the root =D
      if (Object.class.equals(aClass)) {
        return null;
      }
    }

    return InstanceBoundPage.delegating(targetType, instance);
  }

  public Page forClass(Class<?> pageClass) {
    return classToPageMap.get(pageClass);
  }

  public static class InstanceBoundPage implements Page {
    private final Page delegate;
    private final Object instance;

    private InstanceBoundPage(Page delegate, Object instance) {
      this.delegate = delegate;
      this.instance = instance;
    }

    public Renderable widget() {
      return delegate.widget();
    }

    public Object instantiate() {
      return instance;
    }

    public Object doMethod(String httpMethod, Object page, String pathInfo, Request request)
        throws IOException {
      return delegate.doMethod(httpMethod, page, pathInfo, request);
    }

    public Class<?> pageClass() {
      return delegate.pageClass();
    }

    public void apply(Renderable widget) {
      delegate.apply(widget);
    }

    public String getUri() {
      return delegate.getUri();
    }

    public boolean isHeadless() {
      return delegate.isHeadless();
    }

    @Override
    public boolean isDecorated() {
      return delegate.isDecorated();
    }

    public Set<String> getMethod() {
      return delegate.getMethod();
    }

    public int compareTo(Page page) {
      return delegate.compareTo(page);
    }

    public static InstanceBoundPage delegating(Page delegate, Object instance) {
      return new InstanceBoundPage(delegate, instance);
    }

    @Override
    public Show getShow() {
        return delegate.getShow();
    }
  }

  @Select("") //the default select (hacky!!)
  public static class PageTuple implements Page {
    private final String uri;
    private final PathMatcher matcher;
    private final AtomicReference<Renderable> pageWidget = new AtomicReference<Renderable>();
    private final Class<?> clazz;
    private final boolean headless;
    private final boolean extension;
    private final Injector injector;

    private final Multimap<String, Action> methods;

    //dispatcher switch (select on request param by default)
    private final Select select;
    private static final Key<Map<String, Class<? extends Annotation>>> HTTP_METHODS_KEY =
        Key.get(new TypeLiteral<Map<String, Class<? extends Annotation>>>() {}, Bricks.class);

    // A map of http methods -> annotation types (e.g. "POST" -> @Post)
    private Map<String, Class<? extends Annotation>> httpMethods;

    public PageTuple(String uri, PathMatcher matcher, Class<?> clazz, boolean headless, boolean extension,
                     Injector injector, Multimap<String, Action> methods) {
      this.uri = uri;
      this.matcher = matcher;
      this.clazz = clazz;
      this.headless = headless;
      this.extension = extension;
      this.injector = injector;
      this.methods = methods;
      this.select = PageTuple.class.getAnnotation(Select.class);
      this.httpMethods = injector.getInstance(HTTP_METHODS_KEY);
    }

    public PageTuple(String uri, PathMatcher matcher, Class<?> clazz, Injector injector,
                     boolean headless, boolean extension) {
      this.uri = uri;
      this.matcher = matcher;
      this.clazz = clazz;
      this.injector = injector;
      this.headless = headless;
      this.extension = extension;

      this.select = discoverSelect(clazz);

      this.httpMethods = injector.getInstance(HTTP_METHODS_KEY);
      this.methods = reflectAndCache(uri, httpMethods);
    }

    //the @Select request parameter-based event dispatcher
    private Select discoverSelect(Class<?> clazz) {
      final Select select = clazz.getAnnotation(Select.class);
      if (null != select)
        return select;
      else
        return PageTuple.class.getAnnotation(Select.class);
    }

    /**
     * Returns a map of HTTP-method name to @Annotation-marked methods
     */
    @SuppressWarnings({"JavaDoc"})
    private Multimap<String, Action> reflectAndCache(String uri,
        Map<String, Class<? extends Annotation>> methodMap) {
      String tail = "";
      if (clazz.isAnnotationPresent(At.class)) {
        int length = clazz.getAnnotation(At.class).value().length();

        // It's possible that the uri being registered is shorter than the
        // class length, this can happen in the case of using the .at() module
        // directive to override @At() URI path mapping. In this case we treat
        // this call as a top-level path registration with no tail. Any
        // encountered subpath @At methods will be ignored for this URI.
        if (uri != null && length <= uri.length())
          tail = uri.substring(length);
      }

      Multimap<String, Action> map = HashMultimap.create();

      for (Map.Entry<String, Class<? extends Annotation>> entry : methodMap.entrySet()) {

          Class<? extends Annotation> get = entry.getValue();
          // First search any available public methods and store them (including inherited ones)
          for (Method method : clazz.getMethods()) {
            if (method.isAnnotationPresent(get)) {
              if (!method.isAccessible())
                method.setAccessible(true); //ugh

              // Be defensive about subpaths.
              if (method.isAnnotationPresent(At.class)) {
                // Skip any at-annotated methods for a top-level path registration.
                if (tail.isEmpty()) {
                  continue;
                }

                // Skip any at-annotated methods that do not exactly match the path.
                if (!tail.equals(method.getAnnotation(At.class).value())) {
                  continue;
                }
              } else if (!tail.isEmpty()) {
                // If this is the top-level method we're scanning, but their is a tail, i.e.
                // this is not intended to be served by the top-level method, then skip.
                continue;
              }

              // Otherwise register this method for firing...

              //remember default value is empty string
              String value = getValue(get, method);
              String key = (Strings.empty(value)) ? entry.getKey() : entry.getKey() + value;
              map.put(key, new MethodTuple(method, injector));
            }
          }

          // Then search class's declared methods only (these take precedence)
          for (Method method : clazz.getDeclaredMethods()) {
            if (method.isAnnotationPresent(get)) {
              if (!method.isAccessible())
                method.setAccessible(true); //ugh

              // Be defensive about subpaths.
              if (method.isAnnotationPresent(At.class)) {
                // Skip any at-annotated methods for a top-level path registration.
                if (tail.isEmpty()) {
                  continue;
                }

                // Skip any at-annotated methods that do not exactly match the path.
                if (!tail.equals(method.getAnnotation(At.class).value())) {
                  continue;
                }
              } else if (!tail.isEmpty()) {
                // If this is the top-level method we're scanning, but their is a tail, i.e.
                // this is not intended to be served by the top-level method, then skip.
                continue;
              }

              // Otherwise register this method for firing...

              //remember default value is empty string
              String value = getValue(get, method);
              String key = (Strings.empty(value)) ? entry.getKey() : entry.getKey() + value;
              map.put(key, new MethodTuple(method, injector));
            }
          }
      }

      return map;
    }

    private String getValue(Class<? extends Annotation> get, Method method) {
      return readAnnotationValue(method.getAnnotation(get));
    }

    public Renderable widget() {
      return pageWidget.get();
    }

    public Object instantiate() {
      return clazz == null ? Collections.emptyMap() : injector.getInstance(clazz);
    }

    public boolean isHeadless() {
      return headless;
    }

    @Override
    public boolean isDecorated() {
      return extension;
    }

    public Set<String> getMethod() {
      return methods.keySet();
    }

    public int compareTo(Page page) {
      return uri.compareTo(page.getUri());
    }

    public Object doMethod(String httpMethod, Object page, String pathInfo,
                           Request request) throws IOException {

      //nothing to fire
      if (Strings.empty(httpMethod)) {
        return null;
      }

      // NOTE(dhanji): This slurps the entire Map. It could potentially be optimized...
      Multimap<String, String> params = request.params();

      // Extract injectable pieces of the pathInfo.
      final Map<String, String> map = matcher.findMatches(pathInfo);

      // Find method(s) to dispatch to.
      Collection<String> events = params.get(select.value());
      if (null != events) {
        boolean matched = false;
        for (String event : events) {
          String key = httpMethod + event;
          Collection<Action> tuples = methods.get(key);
          Object redirect = null;

          if (null != tuples) {
            for (Action action : tuples) {
              if (action.shouldCall(request)) {
                matched = true;
                redirect = action.call(request, page, map);
                break;
              }
            }
          }

          // Redirects interrupt the event dispatch sequence. Note this might cause inconsistent
          // behaviour depending on the order of processing for events.
          if (null != redirect) {
            return redirect;
          }
        }

        // no matched events. Fire default handler
        if (!matched) {
          return callAction(httpMethod, page, map, request);
        }

      } else {
        // Fire default handler (no events defined)
        return callAction(httpMethod, page, map, request);
      }

      //no redirects, render normally
      return null;
    }

    private Object callAction(String httpMethod, Object page, Map<String, String> pathMap,
                              Request request) throws IOException {

      // There may be more than one default handler
      Collection<Action> tuple = methods.get(httpMethod);
      Object redirect = null;
      if (null != tuple) {
        for (Action action : tuple) {
          if (action.shouldCall(request)) {
            redirect = action.call(request, page, pathMap);
            break;
          }
        }
      }
      return redirect;
    }

    public Class<?> pageClass() {
      return clazz;
    }

    public void apply(Renderable widget) {
      this.pageWidget.set(widget);
    }

    public String getUri() {
      return uri;
    }

    @Override
    public Show getShow() {
        for (String httpMethod: methods.keySet()) {
            Collection<Action> actions = methods.get(httpMethod);
            if (actions != null) {
                for (Action action: actions) {
                    Method method = action.getMethod();
                    if (method != null) {
                        Show show = action.getMethod().getAnnotation(Show.class);
                        if (show != null) {
                            return show;
                        }
                    }
                }
            }
        }
        return null;
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (!(o instanceof Page)) return false;

      Page that = (Page) o;

      return this.clazz.equals(that.pageClass()) && isDecorated() == that.isDecorated();
    }

    @Override
    public int hashCode() {
      return clazz.hashCode();
    }

    @Override
    public String toString() {
        return Objects.toStringHelper(PageTuple.class).add("clazz", clazz).add("isDecorated", extension)
                .add("uri", uri).add("methods", methods).toString();
    }

  }

  private static class MethodTuple implements Action {
    private final Method method;
    private final Injector injector;
    private final List<Object> args;
    private final Map<String, String> negotiates;
    private final ContentNegotiator negotiator;
    private final TypeConverter converter;
    private final As returnAs;

    private MethodTuple(Method method, Injector injector) {
      this.method = method;
      this.injector = injector;
      this.args = reflect(method);
      this.negotiates = discoverNegotiates(method, injector);
      this.negotiator = injector.getInstance(ContentNegotiator.class);
      this.converter = injector.getInstance(TypeConverter.class);
      this.returnAs = method.getAnnotation(As.class);
    }

    private List<Object> reflect(Method method) {

      final Annotation[][] annotationsGrid = method.getParameterAnnotations();
     
      if (null == annotationsGrid)
        return Collections.emptyList();

      List<Object> args = new ArrayList<Object>();
     
      for (int i = 0; i < annotationsGrid.length; i++) {

        Annotation[] annotations = annotationsGrid[i];

        Annotation bindingAnnotation = null;
        boolean preInjectableFound = false;
        for (Annotation annotation : annotations) {
          if (Named.class.isInstance(annotation)) {
            Named named = (Named) annotation;
            args.add(new NamedParameter(named.value(), method.getGenericParameterTypes()[i]));
            preInjectableFound = true;
            break;
          }
          if (javax.inject.Named.class.isInstance(annotation)) {
            javax.inject.Named named = (javax.inject.Named) annotation;
                  args.add(new NamedParameter(named.value(), method.getGenericParameterTypes()[i]));
            preInjectableFound = true;
            break;
          }
          else if (annotation.annotationType().isAnnotationPresent(BindingAnnotation.class)) {
            bindingAnnotation = annotation;
          }
          else if (As.class.isInstance(annotation)) {
            As as = (As) annotation;
            if (method.isAnnotationPresent(Get.class)
              || method.isAnnotationPresent(Head.class)
              || method.isAnnotationPresent(Trace.class)) {
              if (! as.value().equals(Form.class)) {
                throw new IllegalArgumentException("Cannot accept a @As(...) request body from" +
                  " method marked @Get, @Head or @Trace: "
                  + method.getDeclaringClass().getName() + "#" + method.getName() + "()");
                }
            }

            preInjectableFound = true;
            args.add(new AsParameter(as.value(), TypeLiteral.get(method.getGenericParameterTypes()[i])));
            break;
          }
        }

        if (!preInjectableFound) {

          Type genericParameterType = method.getGenericParameterTypes()[i];
           
          Key<?> key = (null != bindingAnnotation)
              ? Key.get(genericParameterType, bindingAnnotation)
              : Key.get(genericParameterType);

          args.add(key);
         
          if (null == injector.getBindings().get(key)) {
             
              throw new InvalidEventHandlerException(
                      "Encountered an argument not annotated with @Named and not a valid injection key"
                      + " in event handler method: " + method + " " + key);

          }

        }
       
      }

      return Collections.unmodifiableList(args);
    }

    /**
     * @return true if this method tuple can be validly called against this request.
     * Used to select for content negotiation.
     */
    @Override
    public boolean shouldCall(Request request) {
      return negotiator.shouldCall(negotiates, request);
    }


    @Override
    public Object call(Request request, Object page, Map<String, String> map) throws IOException {
      List<Object> arguments = new ArrayList<Object>();
      for (Object arg : args) {
        if (arg instanceof AsParameter) {
          AsParameter as = (AsParameter) arg;
          arguments.add(request.read(as.type).as(as.transport));
        } else if (arg instanceof NamedParameter) {
          NamedParameter np = (NamedParameter) arg;
          String text = map.get(np.getName());
          Object value = converter.convert(text, np.getType());
          arguments.add(value);
        } else
          arguments.add(injector.getInstance((Key<?>) arg));
      }

      Object result = call(page, method, arguments.toArray());
      if (returnAs != null && result instanceof Reply) {
        ((Reply) result).as(returnAs.value());
      }
      return result;
    }

    @Override
    public Method getMethod() {
      return this.method;
    }

    private static Object call(Object page, final Method method,
                               Object[] args) {
      try {
        return method.invoke(page, args);
      } catch (IllegalAccessException e) {
        throw new EventDispatchException(
            "Could not access event method (appears to be a security problem): " + method, e);
      } catch (InvocationTargetException e) {
        Throwable cause = e.getCause();
        if (cause instanceof ValidationException) {
            throw (ValidationException) cause;
        }
        StackTraceElement[] stackTrace = cause.getStackTrace();
        throw new EventDispatchException(String.format(
            "Exception [%s - \"%s\"] thrown by event method [%s]\n\nat %s\n"
            + "(See below for entire trace.)\n",
            cause.getClass().getSimpleName(),
            cause.getMessage(), method,
            stackTrace[0]), e);
      }
    }

      //the @Accept request header-based event dispatcher
    private Map<String, String> discoverNegotiates(Method method, Injector injector) {
      // This ugly gunk gets us the map of headers to negotiation annotations
      Map<String, Class<? extends Annotation>> negotiationsMap = injector.getInstance(
          Key.get(new TypeLiteral<Map<String, Class<? extends Annotation>>>(){ }, Negotiation.class));

      Map<String, String> negotiations = Maps.newHashMap();
      // Gather all the negotiation annotations in this class.
      for (Map.Entry<String, Class<? extends Annotation>> headerAnn : negotiationsMap.entrySet()) {
        Annotation annotation = method.getAnnotation(headerAnn.getValue());
        if (annotation != null) {
          negotiations.put(headerAnn.getKey(), readAnnotationValue(annotation));
        }
      }

      return negotiations;
    }

    public class NamedParameter {
      private final String name;
      private final Type type;

      public NamedParameter(String name, Type type) {
        this.name = name;
        this.type = type;
      }

      public String getName() {
        return name;
      }

      public Type getType() {
        return type;
      }
    }

    public class AsParameter {
      private final Class<? extends Transport> transport;
      private final TypeLiteral<?> type;

      public AsParameter(Class<? extends Transport> transport, TypeLiteral<?> type) {
        this.transport = transport;
        this.type = type;
      }
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;

      MethodTuple that = (MethodTuple) o;

      if (!method.equals(that.method)) return false;

      return true;
    }

    @Override
    public int hashCode() {
      return method.hashCode();
    }

    @Override
    public String toString() {
        return "MethodTuple [method=" + method + ", args=" + args + "]";
    }

  }

  /**
   * A simple utility method that reads the String value attribute of any annotation
   * instance.
   */
  static String readAnnotationValue(Annotation annotation) {
       try {
         Method m = annotation.getClass().getMethod("value");

         return (String) m.invoke(annotation);

       } catch (NoSuchMethodException e) {
         throw new IllegalStateException("Encountered a configured annotation that " +
             "has no value parameter. This should never happen. " + annotation, e);
       } catch (InvocationTargetException e) {
         throw new IllegalStateException("Encountered a configured annotation that " +
             "could not be read." + annotation, e);
       } catch (IllegalAccessException e) {
         throw new IllegalStateException("Encountered a configured annotation that " +
             "could not be read." + annotation, e);
       }
     }

}
TOP

Related Classes of com.google.sitebricks.routing.DefaultPageBook

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.