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

package com.google.inject.internal;

import com.google.common.base.Objects;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.Scope;
import com.google.inject.Scopes;
import com.google.inject.Singleton;
import com.google.inject.Stage;
import com.google.inject.binder.ScopedBindingBuilder;
import com.google.inject.spi.BindingScopingVisitor;
import com.google.inject.spi.ScopeBinding;
import java.lang.annotation.Annotation;

/**
 * References a scope, either directly (as a scope instance), or indirectly (as a scope annotation).
 * The scope's eager or laziness is also exposed.
 *
 * @author jessewilson@google.com (Jesse Wilson)
 */
public abstract class Scoping {

  /**
   * No scoping annotation has been applied. Note that this is different from {@code
   * in(Scopes.NO_SCOPE)}, where the 'NO_SCOPE' has been explicitly applied.
   */
  public static final Scoping UNSCOPED =
      new Scoping() {
        @Override
        public <V> V acceptVisitor(BindingScopingVisitor<V> visitor) {
          return visitor.visitNoScoping();
        }

        @Override
        public Scope getScopeInstance() {
          return Scopes.NO_SCOPE;
        }

        @Override
        public String toString() {
          return Scopes.NO_SCOPE.toString();
        }

        @Override
        public void applyTo(ScopedBindingBuilder scopedBindingBuilder) {
          // do nothing
        }
      };

  /**
   * No scoping annotation has been applied explicitly. Note that this is is the same as {@code
   * in(Scopes.NO_SCOPE)}.
   */
  private static final Scoping EXPLICITLY_UNSCOPED =
      new Scoping() {
        @Override
        public <V> V acceptVisitor(BindingScopingVisitor<V> visitor) {
          return visitor.visitNoScoping();
        }

        @Override
        public Scope getScopeInstance() {
          return Scopes.NO_SCOPE;
        }

        @Override
        public String toString() {
          return Scopes.NO_SCOPE.toString();
        }

        @Override
        public void applyTo(ScopedBindingBuilder scopedBindingBuilder) {
          scopedBindingBuilder.in(Scopes.NO_SCOPE);
        }
      };

  public static final Scoping SINGLETON_ANNOTATION =
      new Scoping() {
        @Override
        public <V> V acceptVisitor(BindingScopingVisitor<V> visitor) {
          return visitor.visitScopeAnnotation(Singleton.class);
        }

        @Override
        public Class<? extends Annotation> getScopeAnnotation() {
          return Singleton.class;
        }

        @Override
        public String toString() {
          return Singleton.class.getName();
        }

        @Override
        public void applyTo(ScopedBindingBuilder scopedBindingBuilder) {
          scopedBindingBuilder.in(Singleton.class);
        }
      };

  public static final Scoping SINGLETON_INSTANCE =
      new Scoping() {
        @Override
        public <V> V acceptVisitor(BindingScopingVisitor<V> visitor) {
          return visitor.visitScope(Scopes.SINGLETON);
        }

        @Override
        public Scope getScopeInstance() {
          return Scopes.SINGLETON;
        }

        @Override
        public String toString() {
          return Scopes.SINGLETON.toString();
        }

        @Override
        public void applyTo(ScopedBindingBuilder scopedBindingBuilder) {
          scopedBindingBuilder.in(Scopes.SINGLETON);
        }
      };

  public static final Scoping EAGER_SINGLETON =
      new Scoping() {
        @Override
        public <V> V acceptVisitor(BindingScopingVisitor<V> visitor) {
          return visitor.visitEagerSingleton();
        }

        @Override
        public Scope getScopeInstance() {
          return Scopes.SINGLETON;
        }

        @Override
        public String toString() {
          return "eager singleton";
        }

        @Override
        public void applyTo(ScopedBindingBuilder scopedBindingBuilder) {
          scopedBindingBuilder.asEagerSingleton();
        }
      };

  public static Scoping forAnnotation(final Class<? extends Annotation> scopingAnnotation) {
    if (scopingAnnotation == Singleton.class || scopingAnnotation == javax.inject.Singleton.class) {
      return SINGLETON_ANNOTATION;
    }

    return new Scoping() {
      @Override
      public <V> V acceptVisitor(BindingScopingVisitor<V> visitor) {
        return visitor.visitScopeAnnotation(scopingAnnotation);
      }

      @Override
      public Class<? extends Annotation> getScopeAnnotation() {
        return scopingAnnotation;
      }

      @Override
      public String toString() {
        return scopingAnnotation.getName();
      }

      @Override
      public void applyTo(ScopedBindingBuilder scopedBindingBuilder) {
        scopedBindingBuilder.in(scopingAnnotation);
      }
    };
  }

  public static Scoping forInstance(final Scope scope) {
    if (scope == Scopes.SINGLETON) {
      return SINGLETON_INSTANCE;
    } else if (scope == Scopes.NO_SCOPE) {
      return EXPLICITLY_UNSCOPED;
    }

    return new Scoping() {
      @Override
      public <V> V acceptVisitor(BindingScopingVisitor<V> visitor) {
        return visitor.visitScope(scope);
      }

      @Override
      public Scope getScopeInstance() {
        return scope;
      }

      @Override
      public String toString() {
        return scope.toString();
      }

      @Override
      public void applyTo(ScopedBindingBuilder scopedBindingBuilder) {
        scopedBindingBuilder.in(scope);
      }
    };
  }

  /**
   * Returns true if this scope was explicitly applied. If no scope was explicitly applied then the
   * scoping annotation will be used.
   */
  public boolean isExplicitlyScoped() {
    return this != UNSCOPED;
  }

  /**
   * Returns true if this is the default scope. In this case a new instance will be provided for
   * each injection.
   */
  public boolean isNoScope() {
    return getScopeInstance() == Scopes.NO_SCOPE;
  }

  /** Returns true if this scope is a singleton that should be loaded eagerly in {@code stage}. */
  public boolean isEagerSingleton(Stage stage) {
    if (this == EAGER_SINGLETON) {
      return true;
    }

    if (stage == Stage.PRODUCTION) {
      return this == SINGLETON_ANNOTATION || this == SINGLETON_INSTANCE;
    }

    return false;
  }

  /** Returns the scope instance, or {@code null} if that isn't known for this instance. */
  public Scope getScopeInstance() {
    return null;
  }

  /** Returns the scope annotation, or {@code null} if that isn't known for this instance. */
  public Class<? extends Annotation> getScopeAnnotation() {
    return null;
  }

  @Override
  public boolean equals(Object obj) {
    if (obj instanceof Scoping) {
      Scoping o = (Scoping) obj;
      return Objects.equal(getScopeAnnotation(), o.getScopeAnnotation())
          && Objects.equal(getScopeInstance(), o.getScopeInstance());
    } else {
      return false;
    }
  }

  @Override
  public int hashCode() {
    return Objects.hashCode(getScopeAnnotation(), getScopeInstance());
  }

  public abstract <V> V acceptVisitor(BindingScopingVisitor<V> visitor);

  public abstract void applyTo(ScopedBindingBuilder scopedBindingBuilder);

  private Scoping() {}

  /** Scopes an internal factory. */
  static <T> InternalFactory<? extends T> scope(
      Key<T> key,
      InjectorImpl injector,
      InternalFactory<? extends T> creator,
      Object source,
      Scoping scoping) {

    if (scoping.isNoScope()) {
      return creator;
    }

    Scope scope = scoping.getScopeInstance();

    // NOTE: SingletonScope relies on the fact that we are passing a
    // ProviderToInternalFactoryAdapter here.  If you change the type make sure to update
    // SingletonScope as well.
    Provider<T> scoped =
        scope.scope(key, new ProviderToInternalFactoryAdapter<T>(injector, creator));
    return new InternalFactoryToProviderAdapter<T>(scoped, source);
  }

  /**
   * Replaces annotation scopes with instance scopes using the Injector's annotation-to-instance
   * map. If the scope annotation has no corresponding instance, an error will be added and unscoped
   * will be retuned.
   */
  static Scoping makeInjectable(Scoping scoping, InjectorImpl injector, Errors errors) {
    Class<? extends Annotation> scopeAnnotation = scoping.getScopeAnnotation();
    if (scopeAnnotation == null) {
      return scoping;
    }

    ScopeBinding scope = injector.state.getScopeBinding(scopeAnnotation);
    if (scope != null) {
      return forInstance(scope.getScope());
    }

    errors.scopeNotFound(scopeAnnotation);
    return UNSCOPED;
  }
}


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

package com.google.inject.servlet;

import static com.google.common.base.Charsets.UTF_8;
import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.net.UrlEscapers;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.servlet.http.HttpServletRequest;

/**
 * Some servlet utility methods.
 *
 * @author ntang@google.com (Michael Tang)
 */
final class ServletUtils {
  private static final Splitter SLASH_SPLITTER = Splitter.on('/');
  private static final Joiner SLASH_JOINER = Joiner.on('/');

  private ServletUtils() {
    // private to prevent instantiation.
  }

  /**
   * Gets the context path relative path of the URI. Returns the path of the resource relative to
   * the context path for a request's URI, or null if no path can be extracted.
   *
   *
Also performs url decoding and normalization of the path.
   */
  // @Nullable
  static String getContextRelativePath(
      // @Nullable
      final HttpServletRequest request) {
    if (request != null) {
      String contextPath = request.getContextPath();
      String requestURI = request.getRequestURI();
      if (contextPath.length() < requestURI.length()) {
        String suffix = requestURI.substring(contextPath.length());
        return normalizePath(suffix);
      } else if (requestURI.trim().length() > 0 && contextPath.length() == requestURI.length()) {
        return "/";
      }
    }
    return null;
  }

  /** Normalizes a path by unescaping all safe, percent encoded characters. */
  static String normalizePath(String path) {
    StringBuilder sb = new StringBuilder(path.length());
    int queryStart = path.indexOf('?');
    String query = null;
    if (queryStart != -1) {
      query = path.substring(queryStart);
      path = path.substring(0, queryStart);
    }
    // Normalize the path.  we need to decode path segments, normalize and rejoin in order to
    // 1. decode and normalize safe percent escaped characters.  e.g. %70 -> 'p'
    // 2. decode and interpret dangerous character sequences. e.g. /%2E/ -> '/./' -> '/'
    // 3. preserve dangerous encoded characters. e.g. '/%2F/' -> '///' -> '/%2F'
    List<String> segments = new ArrayList<>();
    for (String segment : SLASH_SPLITTER.split(path)) {
      // This decodes all non-special characters from the path segment.  so if someone passes
      // /%2E/foo we will normalize it to /./foo and then /foo
      String normalized =
          UrlEscapers.urlPathSegmentEscaper().escape(lenientDecode(segment, UTF_8, false));
      if (".".equals(normalized)) {
        // skip
      } else if ("..".equals(normalized)) {
        if (segments.size() > 1) {
          segments.remove(segments.size() - 1);
        }
      } else {
        segments.add(normalized);
      }
    }
    SLASH_JOINER.appendTo(sb, segments);
    if (query != null) {
      sb.append(query);
    }
    return sb.toString();
  }

  /**
   * Percent-decodes a US-ASCII string into a Unicode string. The specified encoding is used to
   * determine what characters are represented by any consecutive sequences of the form
   * "%XX". This is the lenient kind of decoding that will simply ignore and copy as-is any
   * "%XX" sequence that is invalid (for example, "%HH").
   *
   * @param string a percent-encoded US-ASCII string
   * @param encoding a character encoding
   * @param decodePlus boolean to indicate whether to decode '+' as ' '
   * @return a Unicode string
   */
  private static String lenientDecode(String string, Charset encoding, boolean decodePlus) {

    checkNotNull(string);
    checkNotNull(encoding);

    if (decodePlus) {
      string = string.replace('+', ' ');
    }

    int firstPercentPos = string.indexOf('%');

    if (firstPercentPos < 0) {
      return string;
    }

    ByteAccumulator accumulator = new ByteAccumulator(string.length(), encoding);
    StringBuilder builder = new StringBuilder(string.length());

    if (firstPercentPos > 0) {
      builder.append(string, 0, firstPercentPos);
    }

    for (int srcPos = firstPercentPos; srcPos < string.length(); srcPos++) {

      char c = string.charAt(srcPos);

      if (c < 0x80) { // ASCII
        boolean processed = false;

        if (c == '%' && string.length() >= srcPos + 3) {
          String hex = string.substring(srcPos + 1, srcPos + 3);

          try {
            int encoded = Integer.parseInt(hex, 16);

            if (encoded >= 0) {
              accumulator.append((byte) encoded);
              srcPos += 2;
              processed = true;
            }
          } catch (NumberFormatException ignore) {
            // Expected case (badly formatted % group)
          }
        }

        if (!processed) {
          if (accumulator.isEmpty()) {
            // We're not accumulating elements of a multibyte encoded
            // char, so just toss it right into the result string.

            builder.append(c);
          } else {
            accumulator.append((byte) c);
          }
        }
      } else { // Non-ASCII
        // A non-ASCII char marks the end of a multi-char encoding sequence,
        // if one is in progress.

        accumulator.dumpTo(builder);
        builder.append(c);
      }
    }

    accumulator.dumpTo(builder);

    return builder.toString();
  }

  /** Accumulates byte sequences while decoding strings, and encodes them into a StringBuilder. */
  private static class ByteAccumulator {
    private byte[] bytes;
    private int length;
    private final Charset encoding;

    ByteAccumulator(int capacity, Charset encoding) {
      this.bytes = new byte[Math.min(16, capacity)];
      this.encoding = encoding;
    }

    void append(byte b) {
      ensureCapacity(length + 1);
      bytes[length++] = b;
    }

    void dumpTo(StringBuilder dest) {
      if (length != 0) {
        dest.append(new String(bytes, 0, length, encoding));
        length = 0;
      }
    }

    boolean isEmpty() {
      return length == 0;
    }

    private void ensureCapacity(int minCapacity) {
      int cap = bytes.length;
      if (cap >= minCapacity) {
        return;
      }
      int newCapacity = cap + (cap >> 1); // *1.5
      if (newCapacity < minCapacity) {
        // we are close to overflowing, grow by smaller steps
        newCapacity = minCapacity;
      }
      // in other cases, we will naturally throw an OOM from here
      bytes = Arrays.copyOf(bytes, newCapacity);
    }
  }

}


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

package com.google.inject.internal;

import com.google.common.collect.ImmutableSet;
import com.google.inject.AbstractModule;
import com.google.inject.Binder;
import com.google.inject.Binding;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.MembersInjector;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.Scope;
import com.google.inject.Stage;
import com.google.inject.TypeLiteral;
import com.google.inject.spi.DefaultBindingTargetVisitor;

/**
 * Guarantees that processing of Binding elements happens in a sane way.
 *
 * @author sameb@google.com (Sam Berlin)
 */
abstract class AbstractBindingProcessor extends AbstractProcessor {

  // It's unfortunate that we have to maintain a blacklist of specific
  // classes, but we can't easily block the whole package because of
  // all our unit tests.
  private static final ImmutableSet<Class<?>> FORBIDDEN_TYPES =
      ImmutableSet.<Class<?>>of(
          AbstractModule.class,
          Binder.class,
          Binding.class,
          Injector.class,
          Key.class,
          MembersInjector.class,
          Module.class,
          Provider.class,
          Scope.class,
          Stage.class,
          TypeLiteral.class);

  protected final ProcessedBindingData bindingData;

  AbstractBindingProcessor(Errors errors, ProcessedBindingData bindingData) {
    super(errors);
    this.bindingData = bindingData;
  }

  protected <T> UntargettedBindingImpl<T> invalidBinding(
      InjectorImpl injector, Key<T> key, Object source) {
    return new UntargettedBindingImpl<T>(injector, key, source);
  }

  protected void putBinding(BindingImpl<?> binding) {
    Key<?> key = binding.getKey();

    Class<?> rawType = key.getTypeLiteral().getRawType();
    if (FORBIDDEN_TYPES.contains(rawType)) {
      errors.cannotBindToGuiceType(rawType.getSimpleName());
      return;
    }

    BindingImpl<?> original = injector.getExistingBinding(key);
    if (original != null) {
      // If it failed because of an explicit duplicate binding...
      if (injector.state.getExplicitBinding(key) != null) {
        try {
          if (!isOkayDuplicate(original, binding, injector.state)) {
            errors.bindingAlreadySet(key, original.getSource());
            return;
          }
        } catch (Throwable t) {
          errors.errorCheckingDuplicateBinding(key, original.getSource(), t);
          return;
        }
      } else {
        // Otherwise, it failed because of a duplicate JIT binding
        // in the parent
        errors.jitBindingAlreadySet(key);
        return;
      }
    }

    // prevent the parent from creating a JIT binding for this key
    injector.state.parent().blacklist(key, injector.state, binding.getSource());
    injector.state.putBinding(key, binding);
  }

  /**
   * We tolerate duplicate bindings if one exposes the other or if the two bindings are considered
   * duplicates (see {@link Bindings#areDuplicates(BindingImpl, BindingImpl)}.
   *
   * @param original the binding in the parent injector (candidate for an exposing binding)
   * @param binding the binding to check (candidate for the exposed binding)
   */
  private boolean isOkayDuplicate(BindingImpl<?> original, BindingImpl<?> binding, State state) {
    if (original instanceof ExposedBindingImpl) {
      ExposedBindingImpl exposed = (ExposedBindingImpl) original;
      InjectorImpl exposedFrom = (InjectorImpl) exposed.getPrivateElements().getInjector();
      return (exposedFrom == binding.getInjector());
    } else {
      original = (BindingImpl<?>) state.getExplicitBindingsThisLevel().get(binding.getKey());
      // If no original at this level, the original was on a parent, and we don't
      // allow deduplication between parents & children.
      if (original == null) {
        return false;
      } else {
        return original.equals(binding);
      }
    }
  }

  private <T> void validateKey(Object source, Key<T> key) {
    Annotations.checkForMisplacedScopeAnnotations(
        key.getTypeLiteral().getRawType(), source, errors);
  }

  /**
   * Processor for visiting bindings. Each overriden method that wants to actually process the
   * binding should call prepareBinding first.
   */
  abstract class Processor<T, V> extends DefaultBindingTargetVisitor<T, V> {
    final Object source;
    final Key<T> key;
    final Class<? super T> rawType;
    Scoping scoping;

    Processor(BindingImpl<T> binding) {
      source = binding.getSource();
      key = binding.getKey();
      rawType = key.getTypeLiteral().getRawType();
      scoping = binding.getScoping();
    }

    protected void prepareBinding() {
      validateKey(source, key);
      scoping = Scoping.makeInjectable(scoping, injector, errors);
    }

    /**
     * Schedule initialization of this binding to occur immediately after all bindings have been
     * initialially processed.
     */
    protected void scheduleInitialization(BindingImpl<?> binding) {
      bindingData.addUninitializedBinding(asRunnable(binding));
    }

    /**
     * Schedule initialization for this binding to occur after all other static initialization of
     * bindings.
     */
    protected void scheduleDelayedInitialization(BindingImpl<?> binding) {
      bindingData.addDelayedUninitializedBinding(asRunnable(binding));
    }

    private Runnable asRunnable(final BindingImpl<?> binding) {
      return () -> {
        try {
          binding.getInjector().initializeBinding(binding, errors.withSource(source));
        } catch (ErrorsException e) {
          errors.merge(e.getErrors());
        }
      };
    }
  }
}


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

package com.google.inject.internal;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

import com.google.common.base.Joiner;
import com.google.common.base.Joiner.MapJoiner;
import com.google.common.base.Preconditions;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.inject.BindingAnnotation;
import com.google.inject.Key;
import com.google.inject.ScopeAnnotation;
import com.google.inject.TypeLiteral;
import com.google.inject.internal.util.Classes;
import com.google.inject.name.Named;
import com.google.inject.name.Names;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import javax.inject.Qualifier;

/**
 * Annotation utilities.
 *
 * @author crazybob@google.com (Bob Lee)
 */
public class Annotations {

  /** Returns {@code true} if the given annotation type has no attributes. */
  public static boolean isMarker(Class<? extends Annotation> annotationType) {
    return annotationType.getDeclaredMethods().length == 0;
  }

  public static boolean isAllDefaultMethods(Class<? extends Annotation> annotationType) {
    boolean hasMethods = false;
    for (Method m : annotationType.getDeclaredMethods()) {
      hasMethods = true;
      if (m.getDefaultValue() == null) {
        return false;
      }
    }
    return hasMethods;
  }

  private static final LoadingCache<Class<? extends Annotation>, Annotation> cache =
      CacheBuilder.newBuilder()
          .weakKeys()
          .build(
              new CacheLoader<Class<? extends Annotation>, Annotation>() {
                @Override
                public Annotation load(Class<? extends Annotation> input) {
                  return generateAnnotationImpl(input);
                }
              });

  /**
   * Generates an Annotation for the annotation class. Requires that the annotation is all
   * optionals.
   */
  public static <T extends Annotation> T generateAnnotation(Class<T> annotationType) {
    Preconditions.checkState(
        isAllDefaultMethods(annotationType), "%s is not all default methods", annotationType);
    return (T) cache.getUnchecked(annotationType);
  }

  private static <T extends Annotation> T generateAnnotationImpl(final Class<T> annotationType) {
    final Map<String, Object> members = resolveMembers(annotationType);
    return annotationType.cast(
        Proxy.newProxyInstance(
            annotationType.getClassLoader(),
            new Class<?>[] {annotationType},
            new InvocationHandler() {
              @Override
              public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
                String name = method.getName();
                if (name.equals("annotationType")) {
                  return annotationType;
                } else if (name.equals("toString")) {
                  return annotationToString(annotationType, members);
                } else if (name.equals("hashCode")) {
                  return annotationHashCode(annotationType, members);
                } else if (name.equals("equals")) {
                  return annotationEquals(annotationType, members, args[0]);
                } else {
                  return members.get(name);
                }
              }
            }));
  }

  private static ImmutableMap<String, Object> resolveMembers(
      Class<? extends Annotation> annotationType) {
    ImmutableMap.Builder<String, Object> result = ImmutableMap.builder();
    for (Method method : annotationType.getDeclaredMethods()) {
      result.put(method.getName(), method.getDefaultValue());
    }
    return result.build();
  }

  /** Implements {@link Annotation#equals}. */
  private static boolean annotationEquals(
      Class<? extends Annotation> type, Map<String, Object> members, Object other)
      throws Exception {
    if (!type.isInstance(other)) {
      return false;
    }
    for (Method method : type.getDeclaredMethods()) {
      String name = method.getName();
      if (!Arrays.deepEquals(
          new Object[] {method.invoke(other)}, new Object[] {members.get(name)})) {
        return false;
      }
    }
    return true;
  }

  /** Implements {@link Annotation#hashCode}. */
  private static int annotationHashCode(
      Class<? extends Annotation> type, Map<String, Object> members) throws Exception {
    int result = 0;
    for (Method method : type.getDeclaredMethods()) {
      String name = method.getName();
      Object value = members.get(name);
      result += (127 * name.hashCode()) ^ (Arrays.deepHashCode(new Object[] {value}) - 31);
    }
    return result;
  }

  private static final MapJoiner JOINER = Joiner.on(", ").withKeyValueSeparator("=");

  /** Implements {@link Annotation#toString}. */
  private static String annotationToString(
      Class<? extends Annotation> type, Map<String, Object> members) throws Exception {
    StringBuilder sb = new StringBuilder().append("@").append(type.getName()).append("(");
    JOINER.appendTo(
        sb,
        Maps.transformValues(
            members,
            arg -> {
              String s = Arrays.deepToString(new Object[] {arg});
              return s.substring(1, s.length() - 1); // cut off brackets
            }));
    return sb.append(")").toString();
  }

  /** Returns true if the given annotation is retained at runtime. */
  public static boolean isRetainedAtRuntime(Class<? extends Annotation> annotationType) {
    Retention retention = annotationType.getAnnotation(Retention.class);
    return retention != null && retention.value() == RetentionPolicy.RUNTIME;
  }

  /** Returns the scope annotation on {@code type}, or null if none is specified. */
  public static Class<? extends Annotation> findScopeAnnotation(
      Errors errors, Class<?> implementation) {
    return findScopeAnnotation(errors, implementation.getAnnotations());
  }

  /** Returns the scoping annotation, or null if there isn't one. */
  public static Class<? extends Annotation> findScopeAnnotation(
      Errors errors, Annotation[] annotations) {
    Class<? extends Annotation> found = null;

    for (Annotation annotation : annotations) {
      Class<? extends Annotation> annotationType = annotation.annotationType();
      if (isScopeAnnotation(annotationType)) {
        if (found != null) {
          errors.duplicateScopeAnnotations(found, annotationType);
        } else {
          found = annotationType;
        }
      }
    }

    return found;
  }

  static boolean containsComponentAnnotation(Annotation[] annotations) {
    for (Annotation annotation : annotations) {
      // TODO(user): Should we scope this down to dagger.Component?
      if (annotation.annotationType().getSimpleName().equals("Component")) {
        return true;
      }
    }

    return false;
  }

  private static final boolean QUOTE_MEMBER_VALUES = determineWhetherToQuote();

  /**
   * Returns {@code value}, quoted if annotation implementations quote their member values. In Java
   * 9, annotations quote their string members.
   */
  public static String memberValueString(String value) {
    return QUOTE_MEMBER_VALUES ? "\"" + value + "\"" : value;
  }

  @Retention(RUNTIME)
  private @interface TestAnnotation {
    String value();
  }

  @TestAnnotation("determineWhetherToQuote")
  private static boolean determineWhetherToQuote() {
    try {
      String annotation =
          Annotations.class
              .getDeclaredMethod("determineWhetherToQuote")
              .getAnnotation(TestAnnotation.class)
              .toString();
      return annotation.contains("\"determineWhetherToQuote\"");
    } catch (NoSuchMethodException e) {
      throw new AssertionError(e);
    }
  }

  /** Checks for the presence of annotations. Caches results because Android doesn't. */
  static class AnnotationChecker {
    private final Collection<Class<? extends Annotation>> annotationTypes;

    /** Returns true if the given class has one of the desired annotations. */
    private CacheLoader<Class<? extends Annotation>, Boolean> hasAnnotations =
        new CacheLoader<Class<? extends Annotation>, Boolean>() {
          @Override
          public Boolean load(Class<? extends Annotation> annotationType) {
            for (Annotation annotation : annotationType.getAnnotations()) {
              if (annotationTypes.contains(annotation.annotationType())) {
                return true;
              }
            }
            return false;
          }
        };

    final LoadingCache<Class<? extends Annotation>, Boolean> cache =
        CacheBuilder.newBuilder().weakKeys().build(hasAnnotations);

    /** Constructs a new checker that looks for annotations of the given types. */
    AnnotationChecker(Collection<Class<? extends Annotation>> annotationTypes) {
      this.annotationTypes = annotationTypes;
    }

    /** Returns true if the given type has one of the desired annotations. */
    boolean hasAnnotations(Class<? extends Annotation> annotated) {
      return cache.getUnchecked(annotated);
    }
  }

  private static final AnnotationChecker scopeChecker =
      new AnnotationChecker(Arrays.asList(ScopeAnnotation.class, javax.inject.Scope.class));

  public static boolean isScopeAnnotation(Class<? extends Annotation> annotationType) {
    return scopeChecker.hasAnnotations(annotationType);
  }

  /**
   * Adds an error if there is a misplaced annotations on {@code type}. Scoping annotations are not
   * allowed on abstract classes or interfaces.
   */
  public static void checkForMisplacedScopeAnnotations(
      Class<?> type, Object source, Errors errors) {
    if (Classes.isConcrete(type)) {
      return;
    }

    Class<? extends Annotation> scopeAnnotation = findScopeAnnotation(errors, type);
    if (scopeAnnotation != null
        // We let Dagger Components through to aid migrations.
        && !containsComponentAnnotation(type.getAnnotations())) {
      errors.withSource(type).scopeAnnotationOnAbstractType(scopeAnnotation, type, source);
    }
  }

  // NOTE: getKey/findBindingAnnotation are used by Gin which is abandoned.  So changing this API
  // will prevent Gin users from upgrading Guice version.

  /** Gets a key for the given type, member and annotations. */
  public static Key<?> getKey(
      TypeLiteral<?> type, Member member, Annotation[] annotations, Errors errors)
      throws ErrorsException {
    int numErrorsBefore = errors.size();
    Annotation found = findBindingAnnotation(errors, member, annotations);
    errors.throwIfNewErrors(numErrorsBefore);
    return found == null ? Key.get(type) : Key.get(type, found);
  }

  /** Returns the binding annotation on {@code member}, or null if there isn't one. */
  public static Annotation findBindingAnnotation(
      Errors errors, Member member, Annotation[] annotations) {
    Annotation found = null;

    for (Annotation annotation : annotations) {
      Class<? extends Annotation> annotationType = annotation.annotationType();
      if (isBindingAnnotation(annotationType)) {
        if (found != null) {
          errors.duplicateBindingAnnotations(member, found.annotationType(), annotationType);
        } else {
          found = annotation;
        }
      }
    }

    return found;
  }

  private static final AnnotationChecker bindingAnnotationChecker =
      new AnnotationChecker(Arrays.asList(BindingAnnotation.class, Qualifier.class));

  /** Returns true if annotations of the specified type are binding annotations. */
  public static boolean isBindingAnnotation(Class<? extends Annotation> annotationType) {
    return bindingAnnotationChecker.hasAnnotations(annotationType);
  }

  /**
   * If the annotation is an instance of {@code javax.inject.Named}, canonicalizes to
   * com.google.guice.name.Named. Returns the given annotation otherwise.
   */
  public static Annotation canonicalizeIfNamed(Annotation annotation) {
    if (annotation instanceof javax.inject.Named) {
      return Names.named(((javax.inject.Named) annotation).value());
    } else {
      return annotation;
    }
  }

  /**
   * If the annotation is the class {@code javax.inject.Named}, canonicalizes to
   * com.google.guice.name.Named. Returns the given annotation class otherwise.
   */
  public static Class<? extends Annotation> canonicalizeIfNamed(
      Class<? extends Annotation> annotationType) {
    if (annotationType == javax.inject.Named.class) {
      return Named.class;
    } else {
      return annotationType;
    }
  }

  /**
   * Returns the name the binding should use. This is based on the annotation. If the annotation has
   * an instance and is not a marker annotation, we ask the annotation for its toString. If it was a
   * marker annotation or just an annotation type, we use the annotation's name. Otherwise, the name
   * is the empty string.
   */
  public static String nameOf(Key<?> key) {
    Annotation annotation = key.getAnnotation();
    Class<? extends Annotation> annotationType = key.getAnnotationType();
    if (annotation != null && !isMarker(annotationType)) {
      return key.getAnnotation().toString();
    } else if (key.getAnnotationType() != null) {
      return "@" + key.getAnnotationType().getName();
    } else {
      return "";
    }
  }
}


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

package com.google.inject.spi;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Binder;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.internal.Errors;
import com.google.inject.util.Types;
import java.util.Set;

/**
 * A lookup of the provider for a type. Lookups are created explicitly in a module using {@link
 * com.google.inject.Binder#getProvider(Class) getProvider()} statements:
 *
 *

 *     Provider<PaymentService> paymentServiceProvider
 *         = getProvider(PaymentService.class);

 *
 * @author jessewilson@google.com (Jesse Wilson)
 * @since 2.0
 */
public final class ProviderLookup<T> implements Element {
  private final Object source;
  private final Dependency<T> dependency;
  private Provider<T> delegate;

  public ProviderLookup(Object source, Key<T> key) {
    this(source, Dependency.get(checkNotNull(key, "key")));
  }

  /** @since 4.0 */
  public ProviderLookup(Object source, Dependency<T> dependency) {
    this.source = checkNotNull(source, "source");
    this.dependency = checkNotNull(dependency, "dependency");
  }

  @Override
  public Object getSource() {
    return source;
  }

  public Key<T> getKey() {
    return dependency.getKey();
  }

  /** @since 4.0 */
  public Dependency<T> getDependency() {
    return dependency;
  }

  @Override
  public <T> T acceptVisitor(ElementVisitor<T> visitor) {
    return visitor.visit(this);
  }

  /**
   * Sets the actual provider.
   *
   * @throws IllegalStateException if the delegate is already set
   */
  public void initializeDelegate(Provider<T> delegate) {
    checkState(this.delegate == null, "delegate already initialized");
    this.delegate = checkNotNull(delegate, "delegate");
  }

  @Override
  public void applyTo(Binder binder) {
    initializeDelegate(binder.withSource(getSource()).getProvider(dependency));
  }

  /**
   * Returns the delegate provider, or {@code null} if it has not yet been initialized. The delegate
   * will be initialized when this element is processed, or otherwise used to create an injector.
   */
  public Provider<T> getDelegate() {
    return delegate;
  }

  /**
   * Returns the looked up provider. The result is not valid until this lookup has been initialized,
   * which usually happens when the injector is created. The provider will throw an {@code
   * IllegalStateException} if you try to use it beforehand.
   */
  public Provider<T> getProvider() {
    return new ProviderWithDependencies<T>() {
      @Override
      public T get() {
        Provider<T> local = delegate;
        if (local == null) {
          throw new IllegalStateException(
              "This Provider cannot be used until the Injector has been created.");
        }
        return local.get();
      }

      @Override
      public Set<Dependency<?>> getDependencies() {
        // We depend on Provider<T>, not T directly.  This is an important distinction
        // for dependency analysis tools that short-circuit on providers.
        Key<?> providerKey = getKey().ofType(Types.providerOf(getKey().getTypeLiteral().getType()));
        return ImmutableSet.<Dependency<?>>of(Dependency.get(providerKey));
      }

      @Override
      public String toString() {
        return "Provider<" + getKey().getTypeLiteral() + ">";
      }
    };
  }

  @Override
  public String toString() {
    return MoreObjects.toStringHelper(ProviderLookup.class)
        .add("dependency", dependency)
        .add("source", Errors.convert(source))
        .toString();
  }

  @Override
  public boolean equals(Object obj) {
    return obj instanceof ProviderLookup
        && ((ProviderLookup<?>) obj).dependency.equals(dependency)
        && ((ProviderLookup<?>) obj).source.equals(source);
  }

  @Override
  public int hashCode() {
    return Objects.hashCode(dependency, source);
  }
}


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

package com.google.inject.internal.util;

import static com.google.common.base.Preconditions.checkNotNull;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/** Class utilities. */
public final class Classes {

  public static boolean isInnerClass(Class<?> clazz) {
    return !Modifier.isStatic(clazz.getModifiers()) && clazz.getEnclosingClass() != null;
  }

  public static boolean isConcrete(Class<?> clazz) {
    int modifiers = clazz.getModifiers();
    return !clazz.isInterface() && !Modifier.isAbstract(modifiers);
  }

  /**
   * Formats a member as concise string, such as {@code java.util.ArrayList.size}, {@code
   * java.util.ArrayList<init>()} or {@code java.util.List.remove()}.
   */
  public static String toString(Member member) {
    Class<? extends Member> memberType = Classes.memberType(member);

    if (memberType == Method.class) {
      return member.getDeclaringClass().getName() + "." + member.getName() + "()";
    } else if (memberType == Field.class) {
      return member.getDeclaringClass().getName() + "." + member.getName();
    } else if (memberType == Constructor.class) {
      return member.getDeclaringClass().getName() + ".<init>()";
    } else {
      throw new AssertionError();
    }
  }

  /** Returns {@code Field.class}, {@code Method.class} or {@code Constructor.class}. */
  public static Class<? extends Member> memberType(Member member) {
    checkNotNull(member, "member");

    if (member instanceof Field) {
      return Field.class;

    } else if (member instanceof Method) {
      return Method.class;

    } else if (member instanceof Constructor) {
      return Constructor.class;

    } else {
      throw new IllegalArgumentException(
          "Unsupported implementation class for Member, " + member.getClass());
    }
  }
}


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

package com.google.inject.multibindings;

import static com.google.inject.internal.RealMapBinder.newMapRealBinder;
import static com.google.inject.internal.RealMapBinder.newRealMapBinder;

import com.google.inject.Binder;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.internal.RealMapBinder;
import java.lang.annotation.Annotation;
import java.util.Map;

/**
 * An API to bind multiple map entries separately, only to later inject them as a complete map.
 * MapBinder is intended for use in your application's module:
 *
 *

 * public class SnacksModule extends AbstractModule {
 *   protected void configure() {
 *     MapBinder<String, Snack> mapbinder
 *         = MapBinder.newMapBinder(binder(), String.class, Snack.class);
 *     mapbinder.addBinding("twix").toInstance(new Twix());
 *     mapbinder.addBinding("snickers").toProvider(SnickersProvider.class);
 *     mapbinder.addBinding("skittles").to(Skittles.class);
 *   }
 * }

 *
 *
With this binding, a {@link Map}{@code <String, Snack>} can now be injected:
 *
 *

 * class SnackMachine {
 *   {@literal @}Inject
 *   public SnackMachine(Map<String, Snack> snacks) { ... }
 * }

 *
 *
In addition to binding {@code Map<K, V>}, a mapbinder will also bind {@code Map<K,
 * Provider<V>>} for lazy value provision:
 *
 *

 * class SnackMachine {
 *   {@literal @}Inject
 *   public SnackMachine(Map<String, Provider<Snack>> snackProviders) { ... }
 * }

 *
 *
Contributing mapbindings from different modules is supported. For example, it is okay to have
 * both {@code CandyModule} and {@code ChipsModule} both create their own {@code MapBinder<String,
 * Snack>}, and to each contribute bindings to the snacks map. When that map is injected, it will
 * contain entries from both modules.
 *
 *
The map's iteration order is consistent with the binding order. This is convenient when
 * multiple elements are contributed by the same module because that module can order its bindings
 * appropriately. Avoid relying on the iteration order of elements contributed by different modules,
 * since there is no equivalent mechanism to order modules.
 *
 *
The map is unmodifiable. Elements can only be added to the map by configuring the MapBinder.
 * Elements can never be removed from the map.
 *
 *
Values are resolved at map injection time. If a value is bound to a provider, that provider's
 * get method will be called each time the map is injected (unless the binding is also scoped, or a
 * map of providers is injected).
 *
 *
Annotations are used to create different maps of the same key/value type. Each distinct
 * annotation gets its own independent map.
 *
 *
Keys must be distinct. If the same key is bound more than once, map injection
 * will fail. However, use {@link #permitDuplicates()} in order to allow duplicate keys; extra
 * bindings to {@code Map<K, Set<V>>} and {@code Map<K, Set<Provider<V>>} will be added.
 *
 *
Keys must be non-null. {@code addBinding(null)} will throw an unchecked
 * exception.
 *
 *
Values must be non-null to use map injection. If any value is null, map
 * injection will fail (although injecting a map of providers will not).
 *
 * @author dpb@google.com (David P. Baker)
 */
public class MapBinder<K, V> {
  // This class is non-final due to users mocking this in tests :(

  /**
   * Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a {@link
   * Map} that is itself bound with no binding annotation.
   */
  public static <K, V> MapBinder<K, V> newMapBinder(
      Binder binder, TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
    return new MapBinder<K, V>(
        newMapRealBinder(binder.skipSources(MapBinder.class), keyType, valueType));
  }

  /**
   * Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a {@link
   * Map} that is itself bound with no binding annotation.
   */
  public static <K, V> MapBinder<K, V> newMapBinder(
      Binder binder, Class<K> keyType, Class<V> valueType) {
    return newMapBinder(binder, TypeLiteral.get(keyType), TypeLiteral.get(valueType));
  }

  /**
   * Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a {@link
   * Map} that is itself bound with {@code annotation}.
   */
  public static <K, V> MapBinder<K, V> newMapBinder(
      Binder binder, TypeLiteral<K> keyType, TypeLiteral<V> valueType, Annotation annotation) {
    return new MapBinder<K, V>(
        newRealMapBinder(binder.skipSources(MapBinder.class), keyType, valueType, annotation));
  }

  /**
   * Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a {@link
   * Map} that is itself bound with {@code annotation}.
   */
  public static <K, V> MapBinder<K, V> newMapBinder(
      Binder binder, Class<K> keyType, Class<V> valueType, Annotation annotation) {
    return newMapBinder(binder, TypeLiteral.get(keyType), TypeLiteral.get(valueType), annotation);
  }

  /**
   * Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a {@link
   * Map} that is itself bound with {@code annotationType}.
   */
  public static <K, V> MapBinder<K, V> newMapBinder(
      Binder binder,
      TypeLiteral<K> keyType,
      TypeLiteral<V> valueType,
      Class<? extends Annotation> annotationType) {
    return new MapBinder<K, V>(
        newRealMapBinder(binder.skipSources(MapBinder.class), keyType, valueType, annotationType));
  }

  /**
   * Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a {@link
   * Map} that is itself bound with {@code annotationType}.
   */
  public static <K, V> MapBinder<K, V> newMapBinder(
      Binder binder,
      Class<K> keyType,
      Class<V> valueType,
      Class<? extends Annotation> annotationType) {
    return newMapBinder(
        binder, TypeLiteral.get(keyType), TypeLiteral.get(valueType), annotationType);
  }

  private final RealMapBinder<K, V> delegate;

  private MapBinder(RealMapBinder<K, V> delegate) {
    this.delegate = delegate;
  }

  /**
   * Configures the {@code MapBinder} to handle duplicate entries.
   *
   *
When multiple equal keys are bound, the value that gets included in the map is arbitrary.
   *
   *
In addition to the {@code Map<K, V>} and {@code Map<K, Provider<V>>} maps that are normally
   * bound, a {@code Map<K, Set<V>>} and {@code Map<K, Set<Provider<V>>>} are also bound,
   * which contain all values bound to each key.
   *
   *
When multiple modules contribute elements to the map, this configuration option impacts all
   * of them.
   *
   * @return this map binder
   * @since 3.0
   */
  public MapBinder<K, V> permitDuplicates() {
    delegate.permitDuplicates();
    return this;
  }

  /**
   * Returns a binding builder used to add a new entry in the map. Each key must be distinct (and
   * non-null). Bound providers will be evaluated each time the map is injected.
   *
   *
It is an error to call this method without also calling one of the {@code to} methods on the
   * returned binding builder.
   *
   *
Scoping elements independently is supported. Use the {@code in} method to specify a binding
   * scope.
   */
  public LinkedBindingBuilder<V> addBinding(K key) {
    return delegate.addBinding(key);
  }

  // Some tests rely on MapBinder implementing equals/hashCode

  @Override
  public boolean equals(Object obj) {
    if (obj instanceof MapBinder) {
      return delegate.equals(((MapBinder<?, ?>) obj).delegate);
    }
    return false;
  }

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


/*
 * 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.
 */

package com.google.inject.internal;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.inject.internal.Errors.checkConfiguration;
import static com.google.inject.util.Types.newParameterizedType;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import com.google.common.base.Optional;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.inject.Binder;
import com.google.inject.Binding;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.internal.InternalProviderInstanceBindingImpl.InitializationTiming;
import com.google.inject.multibindings.MultibindingsTargetVisitor;
import com.google.inject.multibindings.OptionalBinderBinding;
import com.google.inject.spi.BindingTargetVisitor;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.Element;
import com.google.inject.spi.ProviderInstanceBinding;
import com.google.inject.spi.ProviderWithExtensionVisitor;
import com.google.inject.util.Types;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.reflect.Type;
import java.util.Set;
import javax.inject.Qualifier;

/**
 * The actual OptionalBinder plays several roles. It implements Module to hide that fact from the
 * public API, and installs the various bindings that are exposed to the user.
 */
public final class RealOptionalBinder<T> implements Module {
  public static <T> RealOptionalBinder<T> newRealOptionalBinder(Binder binder, Key<T> type) {
    binder = binder.skipSources(RealOptionalBinder.class);
    RealOptionalBinder<T> optionalBinder = new RealOptionalBinder<>(binder, type);
    binder.install(optionalBinder);
    return optionalBinder;
  }

  @SuppressWarnings("unchecked")
  static <T> TypeLiteral<Optional<T>> optionalOf(TypeLiteral<T> type) {
    return (TypeLiteral<Optional<T>>)
        TypeLiteral.get(Types.newParameterizedType(Optional.class, type.getType()));
  }

  @SuppressWarnings("unchecked")
  static <T> TypeLiteral<java.util.Optional<T>> javaOptionalOf(TypeLiteral<T> type) {
    return (TypeLiteral<java.util.Optional<T>>)
        TypeLiteral.get(Types.newParameterizedType(java.util.Optional.class, type.getType()));
  }

  @SuppressWarnings("unchecked")
  static <T> TypeLiteral<Optional<javax.inject.Provider<T>>> optionalOfJavaxProvider(
      TypeLiteral<T> type) {
    return (TypeLiteral<Optional<javax.inject.Provider<T>>>)
        TypeLiteral.get(
            Types.newParameterizedType(
                Optional.class, newParameterizedType(javax.inject.Provider.class, type.getType())));
  }

  @SuppressWarnings("unchecked")
  static <T> TypeLiteral<java.util.Optional<javax.inject.Provider<T>>> javaOptionalOfJavaxProvider(
      TypeLiteral<T> type) {
    return (TypeLiteral<java.util.Optional<javax.inject.Provider<T>>>)
        TypeLiteral.get(
            Types.newParameterizedType(
                java.util.Optional.class,
                newParameterizedType(javax.inject.Provider.class, type.getType())));
  }

  @SuppressWarnings("unchecked")
  static <T> TypeLiteral<Optional<Provider<T>>> optionalOfProvider(TypeLiteral<T> type) {
    return (TypeLiteral<Optional<Provider<T>>>)
        TypeLiteral.get(
            Types.newParameterizedType(
                Optional.class, newParameterizedType(Provider.class, type.getType())));
  }

  @SuppressWarnings("unchecked")
  static <T> TypeLiteral<java.util.Optional<Provider<T>>> javaOptionalOfProvider(
      TypeLiteral<T> type) {
    return (TypeLiteral<java.util.Optional<Provider<T>>>)
        TypeLiteral.get(
            Types.newParameterizedType(
                java.util.Optional.class, newParameterizedType(Provider.class, type.getType())));
  }

  @SuppressWarnings("unchecked")
  static <T> Key<Provider<T>> providerOf(Key<T> key) {
    Type providerT = Types.providerOf(key.getTypeLiteral().getType());
    return (Key<Provider<T>>) key.ofType(providerT);
  }

  enum Source {
    DEFAULT,
    ACTUAL
  }

  @Retention(RUNTIME)
  @Qualifier
  @interface Default {
    String value();
  }

  @Retention(RUNTIME)
  @Qualifier
  @interface Actual {
    String value();
  }

  private final BindingSelection<T> bindingSelection;
  private final Binder binder;

  private RealOptionalBinder(Binder binder, Key<T> typeKey) {
    this.bindingSelection = new BindingSelection<>(typeKey);
    this.binder = binder;
  }

  /**
   * Adds a binding for T. Multiple calls to this are safe, and will be collapsed as duplicate
   * bindings.
   */
  private void addDirectTypeBinding(Binder binder) {
    binder
        .bind(bindingSelection.getDirectKey())
        .toProvider(new RealDirectTypeProvider<T>(bindingSelection));
  }

  /**
   * Returns the key to use for the default binding.
   *
   *
As a side effect this installs support for the 'direct type', so a binding for {@code T}
   * will be made available.
   */
  Key<T> getKeyForDefaultBinding() {
    bindingSelection.checkNotInitialized();
    addDirectTypeBinding(binder);
    return bindingSelection.getKeyForDefaultBinding();
  }

  public LinkedBindingBuilder<T> setDefault() {
    return binder.bind(getKeyForDefaultBinding());
  }

  /**
   * Returns the key to use for the actual binding, overrides the default if set.
   *
   *
As a side effect this installs support for the 'direct type', so a binding for {@code T}
   * will be made available.
   */
  Key<T> getKeyForActualBinding() {
    bindingSelection.checkNotInitialized();
    addDirectTypeBinding(binder);
    return bindingSelection.getKeyForActualBinding();
  }

  public LinkedBindingBuilder<T> setBinding() {
    return binder.bind(getKeyForActualBinding());
  }

  @Override
  public void configure(Binder binder) {
    bindingSelection.checkNotInitialized();
    Key<T> key = bindingSelection.getDirectKey();
    TypeLiteral<T> typeLiteral = key.getTypeLiteral();
    // Every OptionalBinder gets the following types bound
    // * {cgcb,ju}.Optional<Provider<T>>
    // * {cgcb,ju}.Optional<javax.inject.Provider<T>>
    // * {cgcb,ju}.Optional<T>
    // If setDefault() or setBinding() is called then also
    // * T is bound

    // cgcb.Optional<Provider<T>>
    InternalProviderInstanceBindingImpl.Factory<Optional<Provider<T>>> optionalProviderFactory =
        new RealOptionalProviderProvider<>(bindingSelection);
    binder.bind(key.ofType(optionalOfProvider(typeLiteral))).toProvider(optionalProviderFactory);
    // ju.Optional<Provider<T>>
    InternalProviderInstanceBindingImpl.Factory<java.util.Optional<Provider<T>>>
        javaOptionalProviderFactory = new JavaOptionalProviderProvider<>(bindingSelection);
    binder
        .bind(key.ofType(javaOptionalOfProvider(typeLiteral)))
        .toProvider(javaOptionalProviderFactory);

    // Provider is assignable to javax.inject.Provider and the provider that the factory contains
    // cannot be modified so we can use some rawtypes hackery to share the same implementation.

    // cgcb.Optional<ji.Provider<T>>
    @SuppressWarnings("unchecked")
    InternalProviderInstanceBindingImpl.Factory<Optional<javax.inject.Provider<T>>>
        optionalJavaxProviderFactory =
            (InternalProviderInstanceBindingImpl.Factory) optionalProviderFactory;
    binder
        .bind(key.ofType(optionalOfJavaxProvider(typeLiteral)))
        .toProvider(optionalJavaxProviderFactory);
    // ju.Optional<ji.Provider<T>>
    @SuppressWarnings("unchecked")
    InternalProviderInstanceBindingImpl.Factory<java.util.Optional<javax.inject.Provider<T>>>
        javaOptionalJavaxProviderFactory =
            (InternalProviderInstanceBindingImpl.Factory) javaOptionalProviderFactory;
    binder
        .bind(key.ofType(javaOptionalOfJavaxProvider(typeLiteral)))
        .toProvider(javaOptionalJavaxProviderFactory);

    // cgcb.Optional<T>
    Key<Optional<T>> optionalKey = key.ofType(optionalOf(typeLiteral));
    binder
        .bind(optionalKey)
        .toProvider(new RealOptionalKeyProvider<>(bindingSelection, optionalKey));
    // ju.Optional<T>
    Key<java.util.Optional<T>> javaOptionalKey = key.ofType(javaOptionalOf(typeLiteral));
    binder
        .bind(javaOptionalKey)
        .toProvider(new JavaOptionalProvider<>(bindingSelection, javaOptionalKey));
  }

  /** Provides the binding for java.util.Optional<T>. */
  private static final class JavaOptionalProvider<T>
      extends RealOptionalBinderProviderWithDependencies<T, java.util.Optional<T>>
      implements ProviderWithExtensionVisitor<java.util.Optional<T>>,
          OptionalBinderBinding<java.util.Optional<T>> {

    private final Key<java.util.Optional<T>> optionalKey;

    private Dependency<?> targetDependency;
    private InternalFactory<? extends T> target;

    JavaOptionalProvider(
        BindingSelection<T> bindingSelection, Key<java.util.Optional<T>> optionalKey) {
      super(bindingSelection);
      this.optionalKey = optionalKey;
    }

    @Override
    void doInitialize() {
      if (bindingSelection.getBinding() != null) {
        target = bindingSelection.getBinding().getInternalFactory();
        targetDependency = bindingSelection.getDependency();
      }
    }

    @Override
    protected java.util.Optional<T> doProvision(
        InternalContext context, Dependency<?> currentDependency)
        throws InternalProvisionException {
      InternalFactory<? extends T> local = target;
      if (local == null) {
        return java.util.Optional.empty();
      }
      Dependency<?> localDependency = targetDependency;
      T result;
      Dependency previous = context.pushDependency(localDependency, getSource());

      try {
        // See comments in RealOptionalKeyProvider, about how localDependency may be more specific
        // than what we actually need.
        result = local.get(context, localDependency, false);
      } catch (InternalProvisionException ipe) {
        throw ipe.addSource(localDependency);
        } finally {
          context.popStateAndSetDependency(previous);

      }
      return java.util.Optional.ofNullable(result);
    }

    @Override
    public Set<Dependency<?>> getDependencies() {
      return bindingSelection.dependencies;
    }

    @SuppressWarnings("unchecked")
    @Override
    public  R acceptExtensionVisitor(
        BindingTargetVisitor visitor, ProviderInstanceBinding<? extends B> binding) {
      if (visitor instanceof MultibindingsTargetVisitor) {
        return ((MultibindingsTargetVisitor<java.util.Optional<T>, R>) visitor).visit(this);
      } else {
        return visitor.visit(binding);
      }
    }

    @Override
    public boolean containsElement(Element element) {
      return bindingSelection.containsElement(element);
    }

    @Override
    public Binding<?> getActualBinding() {
      return bindingSelection.getActualBinding();
    }

    @Override
    public Binding<?> getDefaultBinding() {
      return bindingSelection.getDefaultBinding();
    }

    @Override
    public Key<java.util.Optional<T>> getKey() {
      return optionalKey;
    }

    @Override
    public Set<Key<?>> getAlternateKeys() {
      Key<?> key = bindingSelection.getDirectKey();
      TypeLiteral<?> typeLiteral = key.getTypeLiteral();
      return ImmutableSet.of(
          (Key<?>) key.ofType(javaOptionalOfProvider(typeLiteral)),
          (Key<?>) key.ofType(javaOptionalOfJavaxProvider(typeLiteral)));
    }
  }

  /** Provides the binding for java.util.Optional<Provider<T>>. */
  private static final class JavaOptionalProviderProvider<T>
      extends RealOptionalBinderProviderWithDependencies<T, java.util.Optional<Provider<T>>> {
    private java.util.Optional<Provider<T>> value;

    JavaOptionalProviderProvider(BindingSelection<T> bindingSelection) {
      super(bindingSelection);
    }

    @Override
    void doInitialize() {
      if (bindingSelection.getBinding() == null) {
        value = java.util.Optional.empty();
      } else {
        value = java.util.Optional.of(bindingSelection.getBinding().getProvider());
      }
    }

    @Override
    protected java.util.Optional<Provider<T>> doProvision(
        InternalContext context, Dependency<?> dependency) {
      return value;
    }

    @Override
    public Set<Dependency<?>> getDependencies() {
      return bindingSelection.providerDependencies();
    }
  }

  /** Provides the binding for T, conditionally installed by calling setBinding/setDefault. */
  private static final class RealDirectTypeProvider<T>
      extends RealOptionalBinderProviderWithDependencies<T, T> {
    private Key<? extends T> targetKey;

    private Object targetSource;

    private InternalFactory<? extends T> targetFactory;

    RealDirectTypeProvider(BindingSelection<T> bindingSelection) {
      super(bindingSelection);
    }

    @Override
    void doInitialize() {
      BindingImpl<T> targetBinding = bindingSelection.getBinding();
      // we only install this factory if they call setBinding()/setDefault() so we know that
      // targetBinding will be non-null.
      this.targetKey = targetBinding.getKey();
      this.targetSource = targetBinding.getSource();
      this.targetFactory = targetBinding.getInternalFactory();
    }

    @Override
    protected T doProvision(InternalContext context, Dependency<?> dependency)
        throws InternalProvisionException {
      // This is what linked bindings do (see FactoryProxy), and we are pretty similar.
      context.pushState(targetKey, targetSource);

      try {
        return targetFactory.get(context, dependency, true);
      } catch (InternalProvisionException ipe) {
        throw ipe.addSource(targetKey);
        } finally {
          context.popState();

      }
    }

    @Override
    public Set<Dependency<?>> getDependencies() {
      return bindingSelection.dependencies;
    }
  }

  /** Provides the binding for Optional<Provider<T>>. */
  private static final class RealOptionalProviderProvider<T>
      extends RealOptionalBinderProviderWithDependencies<T, Optional<Provider<T>>> {
    private Optional<Provider<T>> value;

    RealOptionalProviderProvider(BindingSelection<T> bindingSelection) {
      super(bindingSelection);
    }

    @Override
    void doInitialize() {
      if (bindingSelection.getBinding() == null) {
        value = Optional.absent();
      } else {
        value = Optional.of(bindingSelection.getBinding().getProvider());
      }
    }

    @Override
    protected Optional<Provider<T>> doProvision(InternalContext context, Dependency<?> dependency) {
      return value;
    }

    @Override
    public Set<Dependency<?>> getDependencies() {
      return bindingSelection.providerDependencies();
    }
  }

  /** Provides the binding for Optional<T>. */
  private static final class RealOptionalKeyProvider<T>
      extends RealOptionalBinderProviderWithDependencies<T, Optional<T>>
      implements ProviderWithExtensionVisitor<Optional<T>>, OptionalBinderBinding<Optional<T>> {

    private final Key<Optional<T>> optionalKey;

    // These are assigned to non-null values during initialization if and only if we have a binding
    // to delegate to.
    private Dependency<?> targetDependency;
    private InternalFactory<? extends T> delegate;

    RealOptionalKeyProvider(BindingSelection<T> bindingSelection, Key<Optional<T>> optionalKey) {
      super(bindingSelection);
      this.optionalKey = optionalKey;
    }

    @Override
    void doInitialize() {
      if (bindingSelection.getBinding() != null) {
        delegate = bindingSelection.getBinding().getInternalFactory();
        targetDependency = bindingSelection.getDependency();
      }
    }

    @Override
    protected Optional<T> doProvision(InternalContext context, Dependency<?> currentDependency)
        throws InternalProvisionException {
      InternalFactory<? extends T> local = delegate;
      if (local == null) {
        return Optional.absent();
      }
      Dependency<?> localDependency = targetDependency;
      T result;
      Dependency previous = context.pushDependency(localDependency, getSource());

      try {
        // currentDependency is Optional<? super T>, so we really just need to set the target
        // dependency to ? super T, but we are currently setting it to T.  We could hypothetically
        // make it easier for our delegate to generate proxies by modifying the dependency, but that
        // would also require us to rewrite the key on each call.  So for now we don't do it.
        result = local.get(context, localDependency, false);
      } catch (InternalProvisionException ipe) {
        throw ipe.addSource(localDependency);
        } finally {
          context.popStateAndSetDependency(previous);

      }
      return Optional.fromNullable(result);
    }

    @Override
    public Set<Dependency<?>> getDependencies() {
      return bindingSelection.dependencies();
    }

    @SuppressWarnings("unchecked")
    @Override
    public  R acceptExtensionVisitor(
        BindingTargetVisitor visitor, ProviderInstanceBinding<? extends B> binding) {
      if (visitor instanceof MultibindingsTargetVisitor) {
        return ((MultibindingsTargetVisitor<Optional<T>, R>) visitor).visit(this);
      } else {
        return visitor.visit(binding);
      }
    }

    @Override
    public Key<Optional<T>> getKey() {
      return optionalKey;
    }

    @Override
    public Set<Key<?>> getAlternateKeys() {
      Key<?> key = bindingSelection.getDirectKey();
      TypeLiteral<?> typeLiteral = key.getTypeLiteral();
      return ImmutableSet.of(
          (Key<?>) key.ofType(optionalOfProvider(typeLiteral)),
          (Key<?>) key.ofType(optionalOfJavaxProvider(typeLiteral)));
    }

    @Override
    public Binding<?> getActualBinding() {
      return bindingSelection.getActualBinding();
    }

    @Override
    public Binding<?> getDefaultBinding() {
      return bindingSelection.getDefaultBinding();
    }

    @Override
    public boolean containsElement(Element element) {
      return bindingSelection.containsElement(element);
    }
  }

  /**
   * A helper object that implements the core logic for deciding what the implementation of the
   * binding will be.
   *
   *
This also implements the main OptionalBinderBinding logic.
   */
  private static final class BindingSelection<T> {
    private static final ImmutableSet<Dependency<?>> MODULE_DEPENDENCIES =
        ImmutableSet.<Dependency<?>>of(Dependency.get(Key.get(Injector.class)));

    /*@Nullable */ BindingImpl<T> actualBinding;
    /*@Nullable */ BindingImpl<T> defaultBinding;
    /*@Nullable */ BindingImpl<T> binding;
    private boolean initialized;
    private final Key<T> key;

    // Until the injector initializes us, we don't know what our dependencies are,
    // so initialize to the whole Injector (like Multibinder, and MapBinder indirectly).
    private ImmutableSet<Dependency<?>> dependencies = MODULE_DEPENDENCIES;
    private ImmutableSet<Dependency<?>> providerDependencies = MODULE_DEPENDENCIES;

    /** lazily allocated, by {@link #getBindingName}. */
    private String bindingName;

    /** lazily allocated, by {@link #getKeyForDefaultBinding}. */
    private Key<T> defaultBindingKey;

    /** lazily allocated, by {@link #getKeyForActualBinding}. */
    private Key<T> actualBindingKey;

    BindingSelection(Key<T> key) {
      this.key = key;
    }

    void checkNotInitialized() {
      checkConfiguration(!initialized, "already initialized");
    }

    void initialize(InjectorImpl injector) {
      // Every one of our providers will call this method, so only execute the logic once.
      if (initialized) {
        return;
      }

      actualBinding = injector.getExistingBinding(getKeyForActualBinding());
      defaultBinding = injector.getExistingBinding(getKeyForDefaultBinding());
      // We should never create Jit bindings, but we can use them if some other binding created it.
      BindingImpl<T> userBinding = injector.getExistingBinding(key);
      if (actualBinding != null) {
        // TODO(sameb): Consider exposing an option that will allow
        // ACTUAL to fallback to DEFAULT if ACTUAL's provider returns null.
        // Right now, an ACTUAL binding can convert from present -> absent
        // if it's bound to a provider that returns null.
        binding = actualBinding;
      } else if (defaultBinding != null) {
        binding = defaultBinding;
      } else if (userBinding != null) {
        // If neither the actual or default is set, then we fallback
        // to the value bound to the type itself and consider that the
        // "actual binding" for the SPI.
        binding = userBinding;
        actualBinding = userBinding;
      }
      if (binding != null) {
        dependencies = ImmutableSet.<Dependency<?>>of(Dependency.get(binding.getKey()));
        providerDependencies =
            ImmutableSet.<Dependency<?>>of(Dependency.get(providerOf(binding.getKey())));
      } else {
        dependencies = ImmutableSet.of();
        providerDependencies = ImmutableSet.of();
      }
      initialized = true;
    }

    Key<T> getKeyForDefaultBinding() {
      if (defaultBindingKey == null) {
        defaultBindingKey = Key.get(key.getTypeLiteral(), new DefaultImpl(getBindingName()));
      }
      return defaultBindingKey;
    }

    Key<T> getKeyForActualBinding() {
      if (actualBindingKey == null) {
        actualBindingKey = Key.get(key.getTypeLiteral(), new ActualImpl(getBindingName()));
      }
      return actualBindingKey;
    }

    Key<T> getDirectKey() {
      return key;
    }

    private String getBindingName() {
      // Lazily allocated, most instantiations will never need this because they are deduped during
      // module installation.
      if (bindingName == null) {
        bindingName = Annotations.nameOf(key);
      }
      return bindingName;
    }

    BindingImpl<T> getBinding() {
      return binding;
    }

    // Provide default implementations for most of the OptionalBinderBinding interface
    BindingImpl<T> getDefaultBinding() {
      return defaultBinding;
    }

    BindingImpl<T> getActualBinding() {
      return actualBinding;
    }

    ImmutableSet<Dependency<?>> providerDependencies() {
      return providerDependencies;
    }

    ImmutableSet<Dependency<?>> dependencies() {
      return dependencies;
    }

    /**
     * Returns the Dependency for the target binding, throws NoSuchElementException if no target
     * exists.
     *
     *
Calls to this method should typically be guarded by checking if {@link #getBinding()}
     * returns {@code null}.
     */
    Dependency<?> getDependency() {
      return Iterables.getOnlyElement(dependencies);
    }

    /** Implementation of {@link OptionalBinderBinding#containsElement}. */
    boolean containsElement(Element element) {
      // All of our bindings are ProviderInstanceBindings whose providers extend
      // RealOptionalBinderProviderWithDependencies and have 'this' as its binding selection.
      if (element instanceof ProviderInstanceBinding) {
        javax.inject.Provider<?> providerInstance =
            ((ProviderInstanceBinding<?>) element).getUserSuppliedProvider();
        if (providerInstance instanceof RealOptionalBinderProviderWithDependencies) {
          return ((RealOptionalBinderProviderWithDependencies<?, ?>) providerInstance)
              .bindingSelection.equals(this);
        }
      }
      if (element instanceof Binding) {
        Key<?> elementKey = ((Binding) element).getKey();
        // if it isn't one of the things we bound directly it might be an actual or default key
        return elementKey.equals(getKeyForActualBinding())
            || elementKey.equals(getKeyForDefaultBinding());
      }
      return false; // cannot match;
    }

    @Override
    public boolean equals(Object o) {
      return o instanceof BindingSelection && ((BindingSelection) o).key.equals(key);
    }

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

  @Override
  public boolean equals(Object o) {
    return o instanceof RealOptionalBinder
        && ((RealOptionalBinder<?>) o).bindingSelection.equals(bindingSelection);
  }

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

  /** A base class for ProviderWithDependencies that need equality based on a specific object. */
  private abstract static class RealOptionalBinderProviderWithDependencies<T, P>
      extends InternalProviderInstanceBindingImpl.Factory
 {
    protected final BindingSelection<T> bindingSelection;

    RealOptionalBinderProviderWithDependencies(BindingSelection<T> bindingSelection) {
      // We need delayed initialization so we can detect jit bindings created by other bindings
      // while not also creating jit bindings ourselves.  This ensures we only pick up user bindings
      // if the binding would have existed in the injector statically.
      super(InitializationTiming.DELAYED);
      this.bindingSelection = bindingSelection;
    }

    @Override
    final void initialize(InjectorImpl injector, Errors errors) throws ErrorsException {
      bindingSelection.initialize(injector);
      doInitialize();
    }

    /**
     * Initialize the factory. BindingSelection is guaranteed to be initialized at this point and
     * this will be called prior to any provisioning.
     */
    abstract void doInitialize();

    @Override
    public boolean equals(Object obj) {
      return obj != null
          && this.getClass() == obj.getClass()
          && bindingSelection.equals(
              ((RealOptionalBinderProviderWithDependencies<?, ?>) obj).bindingSelection);
    }

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

  static class DefaultImpl extends BaseAnnotation implements Default {
    public DefaultImpl(String value) {
      super(Default.class, value);
    }
  }

  static class ActualImpl extends BaseAnnotation implements Actual {
    public ActualImpl(String value) {
      super(Actual.class, value);
    }
  }

  abstract static class BaseAnnotation implements Serializable, Annotation {

    private final String value;
    private final Class<? extends Annotation> clazz;

    BaseAnnotation(Class<? extends Annotation> clazz, String value) {
      this.clazz = checkNotNull(clazz, "clazz");
      this.value = checkNotNull(value, "value");
    }

    public String value() {
      return this.value;
    }

    @Override
    public int hashCode() {
      // This is specified in java.lang.Annotation.
      return (127 * "value".hashCode()) ^ value.hashCode();
    }

    @Override
    public boolean equals(Object o) {
      // We check against each annotation type instead of BaseAnnotation
      // so that we can compare against generated annotation implementations.
      if (o instanceof Actual && clazz == Actual.class) {
        Actual other = (Actual) o;
        return value.equals(other.value());
      } else if (o instanceof Default && clazz == Default.class) {
        Default other = (Default) o;
        return value.equals(other.value());
      }
      return false;
    }

    @Override
    public String toString() {
      return "@" + clazz.getName() + (value.isEmpty() ? "" : "(value=" + value + ")");
    }

    @Override
    public Class<? extends Annotation> annotationType() {
      return clazz;
    }

    private static final long serialVersionUID = 0;
  }
}


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

package com.google.inject.internal;

import static com.google.common.collect.ImmutableList.toImmutableList;

import com.google.common.collect.ImmutableList;
import com.google.inject.spi.InjectionPoint;
import java.util.stream.Stream;

/**
 * Constructor injectors by type.
 *
 * @author jessewilson@google.com (Jesse Wilson)
 */
final class ConstructorInjectorStore {
  private final InjectorImpl injector;

  private final FailableCache<InjectionPoint, ConstructorInjector<?>> cache =
      new FailableCache<InjectionPoint, ConstructorInjector<?>>() {
        @Override
        protected ConstructorInjector<?> create(InjectionPoint constructorInjector, Errors errors)
            throws ErrorsException {
          return createConstructor(constructorInjector, errors);
        }
      };

  ConstructorInjectorStore(InjectorImpl injector) {
    this.injector = injector;
  }

  /** Returns a new complete constructor injector with injection listeners registered. */
  public ConstructorInjector<?> get(InjectionPoint constructorInjector, Errors errors)
      throws ErrorsException {
    return cache.get(constructorInjector, errors);
  }

  /**
   * Purges an injection point from the cache. Use this only if the cache is not actually valid and
   * needs to be purged. (See issue 319 and
   * ImplicitBindingTest#testCircularJitBindingsLeaveNoResidue and
   * #testInstancesRequestingProvidersForThemselvesWithChildInjectors for examples of when this is
   * necessary.)
   *
   * <p>Returns true if the injector for that point was stored in the cache, false otherwise.
   */
  boolean remove(InjectionPoint ip) {
    return cache.remove(ip);
  }

  private <T> ConstructorInjector<T> createConstructor(InjectionPoint injectionPoint, Errors errors)
      throws ErrorsException {
    int numErrorsBefore = errors.size();

    SingleParameterInjector<?>[] constructorParameterInjectors =
        injector.getParametersInjectors(injectionPoint.getDependencies(), errors);

    @SuppressWarnings("unchecked") // the injector type agrees with the injection point type
    MembersInjectorImpl<T> membersInjector =
        (MembersInjectorImpl<T>)
            injector.membersInjectorStore.get(injectionPoint.getDeclaringType(), errors);

    /*if[AOP]*/
    ImmutableList<MethodAspect> injectorAspects = injector.state.getMethodAspects();
    ImmutableList<MethodAspect> methodAspects =
        membersInjector.getAddedAspects().isEmpty()
            ? injectorAspects
            : Stream.concat(injectorAspects.stream(), membersInjector.getAddedAspects().stream())
                .collect(toImmutableList());
    ConstructionProxyFactory<T> factory = new ProxyFactory<>(injectionPoint, methodAspects);
    /*end[AOP]*/
    /*if[NO_AOP]
    ConstructionProxyFactory<T> factory = new DefaultConstructionProxyFactory<>(injectionPoint);
    end[NO_AOP]*/

    errors.throwIfNewErrors(numErrorsBefore);

    return new ConstructorInjector<T>(
        membersInjector.getInjectionPoints(),
        factory.create(),
        constructorParameterInjectors,
        membersInjector);
  }
}


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

package com.google.inject.struts2;

import com.google.inject.AbstractModule;
import com.google.inject.Binder;
import com.google.inject.Injector;
import com.google.inject.internal.Annotations;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.ObjectFactory;
import com.opensymphony.xwork2.config.ConfigurationException;
import com.opensymphony.xwork2.config.entities.InterceptorConfig;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.interceptor.Interceptor;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

/**
 * Cleanup up version from Bob's GuiceObjectFactory. Now works properly with GS2 and fixes several
 * bugs.
 *
 * @author dhanji@gmail.com
 * @author benmccann.com
 */
public class Struts2Factory extends ObjectFactory {

  private static final long serialVersionUID = 1L;
  private static final Logger logger = Logger.getLogger(Struts2Factory.class.getName());
  private static final String ERROR_NO_INJECTOR =
      "Cannot find a Guice injector.  Are you sure you registered a GuiceServletContextListener "
          + "that uses the Struts2GuicePluginModule in your application's web.xml?";

  private static @com.google.inject.Inject Injector injector;

  private final List<ProvidedInterceptor> interceptors = new ArrayList<>();
  private volatile Injector strutsInjector;

  @Override
  public boolean isNoArgConstructorRequired() {
    return false;
  }

  @Inject(value = "guice.module", required = false)
  void setModule(String moduleClassName) {
    throw new RuntimeException(
        "The struts2 plugin no longer supports"
            + " specifying a module via the 'guice.module' property in XML."
            + " Please install your module via a GuiceServletContextListener instead.");
  }

  Set<Class<?>> boundClasses = new HashSet<>();

  @Override
  public Class<?> getClassInstance(String name) throws ClassNotFoundException {
    Class<?> clazz = super.getClassInstance(name);

    synchronized (this) {
      if (strutsInjector == null) {
        // We can only bind each class once.
        if (!boundClasses.contains(clazz)) {
          try {
            // Calling these methods now helps us detect ClassNotFoundErrors
            // early.
            clazz.getDeclaredFields();
            clazz.getDeclaredMethods();

            boundClasses.add(clazz);
          } catch (Throwable t) {
            // Struts should still work even though some classes aren't in the
            // classpath. It appears we always get the exception here when
            // this is the case.
            return clazz;
          }
        }
      }
    }

    return clazz;
  }

  @Override
  @SuppressWarnings("unchecked")
  public Object buildBean(Class clazz, Map<String, Object> extraContext) {
    if (strutsInjector == null) {
      synchronized (this) {
        if (strutsInjector == null) {
          createInjector();
        }
      }
    }
    return strutsInjector.getInstance(clazz);
  }

  private void createInjector() {
    logger.info("Loading struts2 Guice support...");

    // Something is wrong, since this should be there if GuiceServletContextListener
    // was present.
    if (injector == null) {
      logger.severe(ERROR_NO_INJECTOR);
      throw new RuntimeException(ERROR_NO_INJECTOR);
    }

    this.strutsInjector =
        injector.createChildInjector(
            new AbstractModule() {
              @Override
              protected void configure() {

                // Tell the injector about all the action classes, etc., so it
                // can validate them at startup.
                for (Class<?> boundClass : boundClasses) {
                  // TODO: Set source from Struts XML.
                  bind(boundClass);
                }

                // Validate the interceptor class.
                for (ProvidedInterceptor interceptor : interceptors) {
                  interceptor.validate(binder());
                }
              }
            });

    // Inject interceptors.
    for (ProvidedInterceptor interceptor : interceptors) {
      interceptor.inject();
    }

    logger.info("Injector created successfully.");
  }

  @Override
  @SuppressWarnings("unchecked")
  public Interceptor buildInterceptor(InterceptorConfig interceptorConfig, Map interceptorRefParams)
      throws ConfigurationException {
    // Ensure the interceptor class is present.
    Class<? extends Interceptor> interceptorClass;
    try {
      interceptorClass =
          (Class<? extends Interceptor>) getClassInstance(interceptorConfig.getClassName());
    } catch (ClassNotFoundException e) {
      throw new RuntimeException(e);
    }

    ProvidedInterceptor providedInterceptor =
        new ProvidedInterceptor(interceptorConfig, interceptorRefParams, interceptorClass);
    interceptors.add(providedInterceptor);
    if (strutsInjector != null) {
      synchronized (this) {
        if (strutsInjector != null) {
          providedInterceptor.inject();
        }
      }
    }
    return providedInterceptor;
  }

  private Interceptor superBuildInterceptor(
      InterceptorConfig interceptorConfig, Map<String, String> interceptorRefParams)
      throws ConfigurationException {
    return super.buildInterceptor(interceptorConfig, interceptorRefParams);
  }

  private class ProvidedInterceptor implements Interceptor {

    private static final long serialVersionUID = 1L;

    private final InterceptorConfig config;
    private final Map<String, String> params;
    private final Class<? extends Interceptor> interceptorClass;
    private Interceptor delegate;

    ProvidedInterceptor(
        InterceptorConfig config,
        Map<String, String> params,
        Class<? extends Interceptor> interceptorClass) {
      this.config = config;
      this.params = params;
      this.interceptorClass = interceptorClass;
    }

    void validate(Binder binder) {
      // TODO: Set source from Struts XML.
      if (hasScope(interceptorClass)) {
        binder.addError(
            "Scoping interceptors is not currently supported."
                + " Please remove the scope annotation from "
                + interceptorClass.getName()
                + ".");
      }

      // Make sure it implements Interceptor.
      if (!Interceptor.class.isAssignableFrom(interceptorClass)) {
        binder.addError(
            interceptorClass.getName() + " must implement " + Interceptor.class.getName() + ".");
      }
    }

    void inject() {
      delegate = superBuildInterceptor(config, params);
    }

    @Override
    public void destroy() {
      if (null != delegate) {
        delegate.destroy();
      }
    }

    @Override
    public void init() {
      throw new AssertionError();
    }

    @Override
    public String intercept(ActionInvocation invocation) throws Exception {
      return delegate.intercept(invocation);
    }
  }

  /** Returns true if the given class has a scope annotation. */
  private static boolean hasScope(Class<? extends Interceptor> interceptorClass) {
    for (Annotation annotation : interceptorClass.getAnnotations()) {
      if (Annotations.isScopeAnnotation(annotation.annotationType())) {
        return true;
      }
    }
    return false;
  }
}