All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.api.tools.framework.util.Dispatcher Maven / Gradle / Ivy

/*
 * Copyright (C) 2016 Google 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.
 */

// Copyright 2012 Google Inc. All Rights Reserved.

package com.google.api.tools.framework.util;

import com.google.auto.value.AutoValue;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import net.sf.cglib.reflect.FastClass;
import net.sf.cglib.reflect.FastMethod;

/**
 * A helper class for {@link GenericVisitor} which implements dispatching
 * of methods based on parameter type and annotations.
 */
public final class Dispatcher {

  /**
   * A helper class defining a key to lookup a dispatcher in a cache.
   */
  @AutoValue
  abstract static class Key {

    abstract Class baseType();
    abstract Class marker();
    abstract Class provider();

    static Key create(Class baseType, Class marker, Class provider) {
      return new AutoValue_Dispatcher_Key(baseType, marker, provider);
    }
  }

  /**
   * A loading cache for dispatchers. We cache dispatchers as building up the dispatching
   * table is expensive; also, dispatchers 'learn' over time and we want to preserve this
   * knowledge.
   */
  private static final LoadingCache>
    CACHE = CacheBuilder.newBuilder().build(new CacheLoader>() {
      @Override
      public Dispatcher load(Key key) throws Exception {
        return createDispatcher(key.baseType(), key.marker(), key.provider());
      }
    });

  // A helper to go from ? to some T
  private static  Dispatcher createDispatcher(Class baseType,
      Class marker, Class provider) {
    return new Dispatcher(baseType, marker, provider);
  }

  private final Class baseType;
  private final LoadingCache, Optional> dispatchTable;

  /**
   * Creates a dispatcher which dispatches over methods which are annotated with
   * {@code marker} and have an instance of {@code BaseType} as their only parameter.
   * This method will reuse an existing dispatcher from a cache or construct a new one.
   */
  @SuppressWarnings("unchecked") // valid by construction
  public static  Dispatcher getDispatcher(Class baseType,
      Class marker, Class provider) {
    try {
      return (Dispatcher) CACHE.get(Key.create(baseType, marker, provider));
    } catch (Exception e) {
      throw Throwables.propagate(e);
    }
  }

  private Dispatcher(Class baseType, Class marker,
      Class provider) {
    this.baseType = baseType;
    this.dispatchTable = CacheBuilder.newBuilder().build(new IncrementalTableLoader());
    initialize(marker, provider);
  }

  /**
   * Initializes the dispatcher table. This maps every type
   * to precisely the method which handles it. At lookup time we
   * will incrementally populate the table with additional entries
   * for super-types.
   */
  private void initialize(Class marker, Class provider) {
    Class providerSuper = provider.getSuperclass();
    if (providerSuper != null && providerSuper != Object.class) {
      // First get methods from super class. They can be overridden later.
      initialize(marker, providerSuper);
    }
    // Now get methods from this provider.
    FastClass fastProvider = FastClass.create(provider);
    for (Method method : provider.getDeclaredMethods()) {
      Annotation dispatched = method.getAnnotation(marker);
      if (dispatched == null) {
        continue;
      }

      Preconditions.checkState((method.getModifiers() & Modifier.STATIC) == 0,
          "%s must not be static", method);
      Preconditions.checkState(method.getParameterTypes().length == 1,
          "%s must have exactly one parameter", method);
      @SuppressWarnings("unchecked") // checked at runtime
      Class dispatchedOn =
          (Class) method.getParameterTypes()[0];
      Preconditions.checkState(baseType.isAssignableFrom(dispatchedOn),
          "%s parameter must be assignable to %s", method, baseType);

      Optional oldMethod = dispatchTable.getIfPresent(dispatchedOn);
      if (oldMethod != null && oldMethod.get().getDeclaringClass() == provider) {
        throw new IllegalStateException(String.format(
            "%s clashes with already configured %s from same class %s",
            method, oldMethod.get().getJavaMethod(), provider));
      }
      dispatchTable.put(dispatchedOn, Optional.of(fastProvider.getMethod(method)));
    }
  }

  /**
   * Delivers the {@link FastMethod} which can handle an object of given type.
   * Delivers the method with most specific type, or null, if none exists.
   */
  public FastMethod getMethod(Class type) {
    Optional result = dispatchTable.getUnchecked(type);
    return result.isPresent() ? result.get() : null;
  }

  /**
   * A helper class which populates the dispatch table with mappings for
   * methods defined by super types.
   */
  private class IncrementalTableLoader
      extends CacheLoader, Optional> {

    @Override
    public Optional load(Class type) throws Exception {
      // try to use super type
      Class rawSuperType = type.getSuperclass();
      if (rawSuperType == null || !baseType.isAssignableFrom(rawSuperType)) {
        return Optional.absent();
      }
      @SuppressWarnings("unchecked") // checked at runtime
      Class superType = (Class) rawSuperType;
      return dispatchTable.getUnchecked(superType);
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy