001package io.avaje.inject.spi;
002
003import io.avaje.inject.BeanEntry;
004import jakarta.inject.Named;
005import org.mockito.Mockito;
006
007import java.lang.reflect.Type;
008import java.util.function.Consumer;
009
010/**
011 * Holds beans supplied to the dependency injection.
012 * <p>
013 * These can be externally supplied dependencies or test doubles for testing purposes.
014 */
015public class SuppliedBean {
016
017  private static final Class<?>[] NO_INTERFACES = new Class[0];
018
019  private final String name;
020  private final Type type;
021  private final int priority;
022  protected Object source;
023
024  /**
025   * Create with a class type and bean instance.
026   */
027  @SuppressWarnings({"rawtypes", "unchecked"})
028  public static SuppliedBean of(Class<?> type, Object source) {
029    return new SuppliedBean.ForClass(null, type, source, null);
030  }
031
032  /**
033   * Create for a class type with a consumer that runs once when the bean is obtained.
034   */
035  public static <B> SuppliedBean of(String name, Class<B> type, Consumer<B> consumer) {
036    return new SuppliedBean.ForClass<>(name, type, null, consumer);
037  }
038
039  /**
040   * Create for a class type with name.
041   */
042  public static <B> SuppliedBean of(String name, Class<B> type, B source) {
043    return new SuppliedBean.ForClass<>(name, type, source, null);
044  }
045
046  /**
047   * Create a supplied bean for a generic type.
048   */
049  public static SuppliedBean ofType(String name, Type type, Object source) {
050    return new SuppliedBean(BeanEntry.SUPPLIED, name, type, source);
051  }
052
053  /**
054   * Create a supplied bean with SECONDARY priority as a default fallback dependency that is
055   * only used when no other matching one is provided.
056   */
057  public static SuppliedBean secondary(String name, Type type, Object source) {
058    return new SuppliedBean(BeanEntry.SECONDARY, name, type, source);
059  }
060
061  private SuppliedBean(int priority, String name, Type type, Object source) {
062    this.priority = priority;
063    this.name = name;
064    this.type = type;
065    this.source = source;
066  }
067
068  /**
069   * Return the bean instance or provider to use for injection.
070   */
071  public Object source() {
072    return source;
073  }
074
075  /**
076   * Return the associated priority.
077   */
078  public final int priority() {
079    return priority;
080  }
081
082  /**
083   * Return the dependency injection target type.
084   */
085  public final Type type() {
086    return type;
087  }
088
089  /**
090   * Return the qualifier name of the supplied bean.
091   */
092  public final String name() {
093    if (name != null) {
094      return name;
095    }
096    if (type instanceof Class<?>) {
097      Named annotation = ((Class<?>) type).getAnnotation(Named.class);
098      return (annotation == null) ? null : annotation.value();
099    }
100    return null;
101  }
102
103  /**
104   * Return the interfaces to additionally register along with the type.
105   */
106  public final Class<?>[] interfaces() {
107    if (type instanceof Class<?>) {
108      return ((Class<?>) type).getInterfaces();
109    }
110    return NO_INTERFACES;
111  }
112
113  /**
114   * Class based supplied bean.
115   */
116  private static final class ForClass<B> extends SuppliedBean {
117
118    private final Consumer<B> consumer;
119    private final Class<B> classType;
120
121    ForClass(String name, Class<B> type, Object source, Consumer<B> consumer) {
122      super(BeanEntry.SUPPLIED, name, type, source);
123      this.classType = type;
124      this.consumer = consumer;
125    }
126
127    @Override
128    public Object source() {
129      if (source == null) {
130        var mock = Mockito.mock(classType);
131        if (consumer != null) {
132          consumer.accept(mock);
133        }
134        source = mock;
135      }
136      return source;
137    }
138  }
139}