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}