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