001package io.avaje.inject.generator; 002 003import io.avaje.inject.InjectModule; 004import io.avaje.inject.Factory; 005import io.avaje.inject.Request; 006import io.avaje.inject.spi.DependencyMeta; 007 008import javax.annotation.processing.AbstractProcessor; 009import javax.annotation.processing.FilerException; 010import javax.annotation.processing.ProcessingEnvironment; 011import javax.annotation.processing.RoundEnvironment; 012import jakarta.inject.Singleton; 013import javax.lang.model.SourceVersion; 014import javax.lang.model.element.*; 015import javax.lang.model.util.Elements; 016import java.io.IOException; 017import java.util.ArrayList; 018import java.util.Collections; 019import java.util.HashSet; 020import java.util.Iterator; 021import java.util.LinkedHashMap; 022import java.util.LinkedHashSet; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026 027public class Processor extends AbstractProcessor { 028 029 private static final String INJECT_MODULE = "io.avaje.inject.InjectModule"; 030 031 private ProcessingContext context; 032 033 private Elements elementUtils; 034 035 /** 036 * Map to merge the existing meta data with partially compiled code. Keyed by type and qualifier/name. 037 */ 038 private final Map<String, MetaData> metaData = new LinkedHashMap<>(); 039 040 private final List<BeanReader> beanReaders = new ArrayList<>(); 041 042 private final Set<String> readBeans = new HashSet<>(); 043 044 public Processor() { 045 } 046 047 @Override 048 public SourceVersion getSupportedSourceVersion() { 049 return SourceVersion.latest(); 050 } 051 052 @Override 053 public synchronized void init(ProcessingEnvironment processingEnv) { 054 super.init(processingEnv); 055 this.context = new ProcessingContext(processingEnv); 056 this.elementUtils = processingEnv.getElementUtils(); 057 } 058 059 @Override 060 public Set<String> getSupportedAnnotationTypes() { 061 062 Set<String> annotations = new LinkedHashSet<>(); 063 annotations.add(InjectModule.class.getCanonicalName()); 064 annotations.add(Factory.class.getCanonicalName()); 065 annotations.add(Singleton.class.getCanonicalName()); 066 annotations.add(Constants.CONTROLLER); 067 return annotations; 068 } 069 070 @Override 071 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 072 073 Set<? extends Element> controllers = Collections.emptySet(); 074 TypeElement typeElement = elementUtils.getTypeElement(Constants.CONTROLLER); 075 if (typeElement != null) { 076 controllers = roundEnv.getElementsAnnotatedWith(typeElement); 077 } 078 079 Set<? extends Element> factoryBeans = roundEnv.getElementsAnnotatedWith(Factory.class); 080 Set<? extends Element> beans = roundEnv.getElementsAnnotatedWith(Singleton.class); 081 Set<? extends Element> requestBeans = roundEnv.getElementsAnnotatedWith(Request.class); 082 083 readModule(roundEnv); 084 readChangedBeans(factoryBeans, true); 085 readChangedBeans(beans, false); 086 readChangedBeans(controllers, false); 087 readChangedBeans(requestBeans, false); 088 089 mergeMetaData(); 090 091 writeBeanHelpers(); 092 if (roundEnv.processingOver()) { 093 writeBeanFactory(); 094 } 095 return false; 096 } 097 098 private void writeBeanHelpers() { 099 for (BeanReader beanReader : beanReaders) { 100 try { 101 if (!beanReader.isWrittenToFile()) { 102 SimpleBeanWriter writer = new SimpleBeanWriter(beanReader, context); 103 writer.write(); 104 beanReader.setWrittenToFile(); 105 } 106 } catch (FilerException e) { 107 context.logWarn("FilerException to write $DI class " + beanReader.getBeanType() + " " + e.getMessage()); 108 109 } catch (IOException e) { 110 e.printStackTrace(); 111 context.logError(beanReader.getBeanType(), "Failed to write $DI class"); 112 } 113 } 114 } 115 116 private void writeBeanFactory() { 117 MetaDataOrdering ordering = new MetaDataOrdering(metaData.values(), context); 118 int remaining = ordering.processQueue(); 119 if (remaining > 0) { 120 ordering.logWarnings(); 121 } 122 123 try { 124 SimpleFactoryWriter factoryWriter = new SimpleFactoryWriter(ordering, context); 125 factoryWriter.write(); 126 } catch (FilerException e) { 127 context.logWarn("FilerException trying to write factory " + e.getMessage()); 128 } catch (IOException e) { 129 context.logError("Failed to write factory " + e.getMessage()); 130 } 131 } 132 133 /** 134 * Read the beans that have changed. 135 */ 136 private void readChangedBeans(Set<? extends Element> beans, boolean factory) { 137 for (Element element : beans) { 138 if (!(element instanceof TypeElement)) { 139 context.logError("unexpected type [" + element + "]"); 140 } else { 141 if (readBeans.add(element.toString())) { 142 readBeanMeta((TypeElement) element, factory); 143 } else { 144 context.logDebug("skipping already processed bean " + element); 145 } 146 } 147 } 148 } 149 150 /** 151 * Merge the changed bean meta data into the existing (factory) metaData. 152 */ 153 private void mergeMetaData() { 154 for (BeanReader beanReader : beanReaders) { 155 if (!beanReader.isRequestScopedController()) { 156 MetaData metaData = this.metaData.get(beanReader.getMetaKey()); 157 if (metaData == null) { 158 addMeta(beanReader); 159 } else { 160 updateMeta(metaData, beanReader); 161 } 162 } 163 } 164 } 165 166 /** 167 * Add a new previously unknown bean. 168 */ 169 private void addMeta(BeanReader beanReader) { 170 MetaData meta = beanReader.createMeta(); 171 metaData.put(meta.getKey(), meta); 172 for (MetaData methodMeta : beanReader.createFactoryMethodMeta()) { 173 metaData.put(methodMeta.getKey(), methodMeta); 174 } 175 } 176 177 /** 178 * Update the meta data on a previously known bean. 179 */ 180 private void updateMeta(MetaData metaData, BeanReader beanReader) { 181 metaData.update(beanReader); 182 } 183 184 /** 185 * Read the dependency injection meta data for the given bean. 186 */ 187 private void readBeanMeta(TypeElement typeElement, boolean factory) { 188 if (typeElement.getKind() == ElementKind.ANNOTATION_TYPE) { 189 context.logDebug("skipping annotation type " + typeElement); 190 return; 191 } 192 beanReaders.add(new BeanReader(typeElement, context, factory).read()); 193 } 194 195 /** 196 * Read the existing meta data from InjectModule (if found) and the factory bean (if exists). 197 */ 198 private void readModule(RoundEnvironment roundEnv) { 199 String factory = context.loadMetaInfServices(); 200 if (factory != null) { 201 TypeElement factoryType = elementUtils.getTypeElement(factory); 202 if (factoryType != null) { 203 readFactory(factoryType); 204 } 205 } 206 207 Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(InjectModule.class); 208 if (!elementsAnnotatedWith.isEmpty()) { 209 Iterator<? extends Element> iterator = elementsAnnotatedWith.iterator(); 210 if (iterator.hasNext()) { 211 Element element = iterator.next(); 212 InjectModule annotation = element.getAnnotation(InjectModule.class); 213 if (annotation != null) { 214 context.setContextDetails(annotation.name(), annotation.provides(), annotation.dependsOn(), element); 215 context.setContextRequires(readRequires(element)); 216 } 217 } 218 } 219 } 220 221 /** 222 * Read the list of required class names. 223 */ 224 private List<String> readRequires(Element element) { 225 List<String> requiresList = new ArrayList<>(); 226 for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) { 227 if (INJECT_MODULE.equals(annotationMirror.getAnnotationType().toString())) { 228 for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : annotationMirror.getElementValues().entrySet()) { 229 if (entry.getKey().toString().startsWith("requires")) { 230 for (Object requiresType : (List<?>) entry.getValue().getValue()) { 231 String fullName = requiresType.toString(); 232 fullName = fullName.substring(0, fullName.length() - 6); 233 requiresList.add(fullName); 234 } 235 } 236 } 237 } 238 } 239 return requiresList; 240 } 241 242 243 /** 244 * Read the existing factory bean. Each of the build methods is annotated with <code>@DependencyMeta</code> 245 * which holds the information we need (to regenerate the factory with any changes). 246 */ 247 private void readFactory(TypeElement factoryType) { 248 InjectModule module = factoryType.getAnnotation(InjectModule.class); 249 context.setContextDetails(module.name(), module.provides(), module.dependsOn(), factoryType); 250 context.setContextRequires(readRequires(factoryType)); 251 252 List<? extends Element> elements = factoryType.getEnclosedElements(); 253 if (elements != null) { 254 for (Element element : elements) { 255 if (ElementKind.METHOD == element.getKind()) { 256 readBuildMethodDependencyMeta(element); 257 } 258 } 259 } 260 } 261 262 private void readBuildMethodDependencyMeta(Element element) { 263 Name simpleName = element.getSimpleName(); 264 if (simpleName.toString().startsWith("build_")) { 265 // read a build method - DependencyMeta 266 DependencyMeta meta = element.getAnnotation(DependencyMeta.class); 267 if (meta == null) { 268 context.logError("Missing @DependencyMeta on method " + simpleName); 269 } else { 270 final MetaData metaData = new MetaData(meta); 271 this.metaData.put(metaData.getKey(), metaData); 272 } 273 } 274 } 275}