<commit_before>package org.immutables.service.concurrent;

import com.google.common.annotations.Beta;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.reflect.Invokable;
import com.google.common.reflect.Parameter;
import com.google.common.reflect.TypeParameter;
import com.google.common.reflect.TypeToken;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.FutureFallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.inject.Binder;
import com.google.inject.Exposed;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.PrivateBinder;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.ScopedBindingBuilder;
import com.google.inject.internal.Annotations;
import com.google.inject.internal.Errors;
import com.google.inject.internal.util.StackTraceElements;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.Message;
import com.google.inject.spi.ProviderWithDependencies;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
import javax.annotation.Nullable;
import javax.inject.Inject;
import org.immutables.common.concurrent.FluentFutures;

/**
 * Create special mix-in module created from defining class with special asynchronous transformation
 * methods annotated with {@literal @}{@link EventuallyProvides}.
 * <p>
 * Basic example
 * 
 * <pre>
 * public class Providers {
 *   {@literal @}EventuallyProvides
 *   C combine(A a, B b) {
 *     return new C(a.value(), b.getProperty());
 *   }
 * 
 *   {@literal @}Exposed
 *   {@literal @}EventuallyProvides
 *   Z transformed(C c) {
 *     return c.transformed();
 *   }
 * }
 * </pre>
 * <p>
 * Having dependency on ListenableFuture&lt;A&gt; and ListenableFuture&lt;B&gt;, this module exposed
 * combined and transformed ListenableFuture&lt;Z&gt; available to injector.
 * <p>
 * <em>While super-classes could be used and will be scanned for such methods, method overriding is
 * not handled properly so avoid overriding provider methods. Use delegation to regular methods if some
 * functionality should be implemented or overridden.
 * </em>
 * <p>
 * To customize dispatching injector could provided with binding to {@literal @}{@link Async}
 * {@link Executor}
 * @see EventuallyProvides
 * @param <T> defining class type
 */
@Beta
public final class EventualProvidersModule<T> implements Module {
  private static TypeToken<ListenableFuture<?>> LISTENABLE_FUTURE = new TypeToken<ListenableFuture<?>>() {};
  private static Executor DEFAULT_EXECUTOR = MoreExecutors.sameThreadExecutor();

  private final Class<T> providersClass;
  private final TypeToken<T> type;

  private final ImmutableList<EventualProvider<?>> providers;
  @Nullable
  private final Class<? extends Annotation> scopeAnnotation;
  private final Errors errors;
  private final Object source;

  private EventualProvidersModule(Class<T> providersClass) {
    this.providersClass = providersClass;
    this.source = StackTraceElements.forType(providersClass);
    this.type = TypeToken.of(providersClass);
    this.errors = new Errors(source);
    this.scopeAnnotation = Annotations.findScopeAnnotation(errors, providersClass);
    this.providers = introspectProviders();
  }

  public static <T> EventualProvidersModule<T> from(Class<T> asyncProviderClass) {
    return new EventualProvidersModule<>(asyncProviderClass);
  }

  @Override
  public void configure(Binder binder) {
    binder = binder.withSource(source);

    if (errors.hasErrors()) {
      for (Message message : errors.getMessages()) {
        binder.addError(message);
      }
    } else {
      bindWithPrivateBinder(binder.newPrivateBinder());
    }
  }

  private void bindWithPrivateBinder(PrivateBinder privateBinder) {
    ScopedBindingBuilder scoper = privateBinder.bind(providersClass);

    if (scopeAnnotation != null) {
      scoper.in(scopeAnnotation);
    }

    for (EventualProvider<?> p : providers) {
      p.bindFutureProvider(privateBinder);
    }
  }

  private ImmutableList<EventualProvider<?>> introspectProviders() {
    ImmutableList.Builder<EventualProvider<?>> builder = ImmutableList.builder();

    // FIXME handle method overriding?
    for (Class<?> t : type.getTypes().classes().rawTypes()) {
      if (t != Object.class) {
        for (Method m : t.getDeclaredMethods()) {
          if (m.isAnnotationPresent(EventuallyProvides.class)) {
            builder.add(
                providerFor(
                    type.method(m),
                    StackTraceElements.forMember(m)));
          }
        }
      }
    }

    return builder.build();
  }

  private EventualProvider<?> providerFor(Invokable<T, ?> method, Object source) {
    Errors methodErrors = errors.withSource(source);

    Annotation[] annotations = method.getAnnotations();

    verifyMethodAccessibility(methodErrors, method, source);

    boolean exposedBinding = exposedBinding(method);

    @Nullable
    Annotation bindingAnnotation =
        Annotations.findBindingAnnotation(methodErrors, method, annotations);

    verifyAbsenseOfScopeAnnotation(methodErrors, annotations, source);

    List<Dependency<ListenableFuture<?>>> dependencies =
        Lists.newArrayListWithCapacity(method.getParameters().size());

    for (Parameter parameter : method.getParameters()) {
      dependencies.add(extractDependency(methodErrors, parameter));
    }

    Key<ListenableFuture<?>> bindingKey = futureKey(method.getReturnType(), bindingAnnotation);

    return new EventualProvider<>(
        method,
        exposedBinding,
        dependencies,
        bindingKey,
        scopeAnnotation,
        source);
  }

  private boolean exposedBinding(Invokable<T, ?> method) {
    return method.isPublic() || method.isAnnotationPresent(Exposed.class);
  }

  private void verifyAbsenseOfScopeAnnotation(Errors methodErrors, Annotation[] annotations, Object source) {
    @Nullable
    Class<? extends Annotation> methodScopeAnnotation =
        Annotations.findScopeAnnotation(methodErrors, annotations);
    if (methodScopeAnnotation != null) {
      methodErrors.addMessage(
          "Misplaced scope annotation @%s on method @%s %s.%n    Scope annotation will be inherited from enclosing class %s",
          methodScopeAnnotation.getSimpleName(),
          EventuallyProvides.class.getSimpleName(),
          source,
          providersClass.getSimpleName());
    }
  }<commit_msg>decided to make @Exposed alone, no `public` doing the same<commit_after>package org.immutables.service.concurrent;

import com.google.common.annotations.Beta;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.reflect.Invokable;
import com.google.common.reflect.Parameter;
import com.google.common.reflect.TypeParameter;
import com.google.common.reflect.TypeToken;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.FutureFallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.inject.Binder;
import com.google.inject.Exposed;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.PrivateBinder;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.ScopedBindingBuilder;
import com.google.inject.internal.Annotations;
import com.google.inject.internal.Errors;
import com.google.inject.internal.util.StackTraceElements;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.Message;
import com.google.inject.spi.ProviderWithDependencies;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
import javax.annotation.Nullable;
import javax.inject.Inject;
import org.immutables.common.concurrent.FluentFutures;

/**
 * Create special mix-in module created from defining class with special asynchronous transformation
 * methods annotated with {@literal @}{@link EventuallyProvides}.
 * <p>
 * Basic example
 * 
 * <pre>
 * public class Providers {
 *   {@literal @}EventuallyProvides
 *   C combine(A a, B b) {
 *     return new C(a.value(), b.getProperty());
 *   }
 * 
 *   {@literal @}Exposed
 *   {@literal @}EventuallyProvides
 *   Z transformed(C c) {
 *     return c.transformed();
 *   }
 * }
 * </pre>
 * <p>
 * Having dependency on ListenableFuture&lt;A&gt; and ListenableFuture&lt;B&gt;, this module exposed
 * combined and transformed ListenableFuture&lt;Z&gt; available to injector.
 * <p>
 * <em>While super-classes could be used and will be scanned for such methods, method overriding is
 * not handled properly so avoid overriding provider methods. Use delegation to regular methods if some
 * functionality should be implemented or overridden.
 * </em>
 * <p>
 * To customize dispatching injector could provided with binding to {@literal @}{@link Async}
 * {@link Executor}
 * @see EventuallyProvides
 * @param <T> defining class type
 */
@Beta
public final class EventualProvidersModule<T> implements Module {
  private static TypeToken<ListenableFuture<?>> LISTENABLE_FUTURE = new TypeToken<ListenableFuture<?>>() {};
  private static Executor DEFAULT_EXECUTOR = MoreExecutors.sameThreadExecutor();

  private final Class<T> providersClass;
  private final TypeToken<T> type;

  private final ImmutableList<EventualProvider<?>> providers;
  @Nullable
  private final Class<? extends Annotation> scopeAnnotation;
  private final Errors errors;
  private final Object source;

  private EventualProvidersModule(Class<T> providersClass) {
    this.providersClass = providersClass;
    this.source = StackTraceElements.forType(providersClass);
    this.type = TypeToken.of(providersClass);
    this.errors = new Errors(source);
    this.scopeAnnotation = Annotations.findScopeAnnotation(errors, providersClass);
    this.providers = introspectProviders();
  }

  public static <T> EventualProvidersModule<T> from(Class<T> asyncProviderClass) {
    return new EventualProvidersModule<>(asyncProviderClass);
  }

  @Override
  public void configure(Binder binder) {
    binder = binder.withSource(source);

    if (errors.hasErrors()) {
      for (Message message : errors.getMessages()) {
        binder.addError(message);
      }
    } else {
      bindWithPrivateBinder(binder.newPrivateBinder());
    }
  }

  private void bindWithPrivateBinder(PrivateBinder privateBinder) {
    ScopedBindingBuilder scoper = privateBinder.bind(providersClass);

    if (scopeAnnotation != null) {
      scoper.in(scopeAnnotation);
    }

    for (EventualProvider<?> p : providers) {
      p.bindFutureProvider(privateBinder);
    }
  }

  private ImmutableList<EventualProvider<?>> introspectProviders() {
    ImmutableList.Builder<EventualProvider<?>> builder = ImmutableList.builder();

    // FIXME handle method overriding?
    for (Class<?> t : type.getTypes().classes().rawTypes()) {
      if (t != Object.class) {
        for (Method m : t.getDeclaredMethods()) {
          if (m.isAnnotationPresent(EventuallyProvides.class)) {
            builder.add(
                providerFor(
                    type.method(m),
                    StackTraceElements.forMember(m)));
          }
        }
      }
    }

    return builder.build();
  }

  private EventualProvider<?> providerFor(Invokable<T, ?> method, Object source) {
    Errors methodErrors = errors.withSource(source);

    Annotation[] annotations = method.getAnnotations();

    verifyMethodAccessibility(methodErrors, method, source);

    @Nullable
    Annotation bindingAnnotation =
        Annotations.findBindingAnnotation(methodErrors, method, annotations);

    verifyAbsenseOfScopeAnnotation(methodErrors, annotations, source);

    List<Dependency<ListenableFuture<?>>> dependencies =
        Lists.newArrayListWithCapacity(method.getParameters().size());

    for (Parameter parameter : method.getParameters()) {
      dependencies.add(extractDependency(methodErrors, parameter));
    }

    Key<ListenableFuture<?>> bindingKey = futureKey(method.getReturnType(), bindingAnnotation);
    boolean exposedBinding = method.isAnnotationPresent(Exposed.class);

    return new EventualProvider<>(
        method,
        exposedBinding,
        dependencies,
        bindingKey,
        scopeAnnotation,
        source);
  }

  private void verifyAbsenseOfScopeAnnotation(Errors methodErrors, Annotation[] annotations, Object source) {
    @Nullable
    Class<? extends Annotation> methodScopeAnnotation =
        Annotations.findScopeAnnotation(methodErrors, annotations);
    if (methodScopeAnnotation != null) {
      methodErrors.addMessage(
          "Misplaced scope annotation @%s on method @%s %s.%n    Scope annotation will be inherited from enclosing class %s",
          methodScopeAnnotation.getSimpleName(),
          EventuallyProvides.class.getSimpleName(),
          source,
          providersClass.getSimpleName());
    }
  }
{'fraction_non_alphanumeric': 0.10578441222907994, 'fraction_numerical': 0.0, 'mean_word_length': 4.246611445783133, 'pattern_counts': {'":': 0, '<': 82, '<?xml version=': 0, '>': 82, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4335048', 'n_tokens_mistral': 3890, 'n_tokens_neox': 3779, 'n_words': 868}