001package io.avaje.inject.generator; 002 003import io.avaje.inject.ContextModule; 004import io.avaje.inject.Factory; 005import io.avaje.inject.spi.DependencyMeta; 006 007import javax.annotation.processing.AbstractProcessor; 008import javax.annotation.processing.FilerException; 009import javax.annotation.processing.ProcessingEnvironment; 010import javax.annotation.processing.RoundEnvironment; 011import jakarta.inject.Singleton; 012import javax.lang.model.SourceVersion; 013import javax.lang.model.element.Element; 014import javax.lang.model.element.ElementKind; 015import javax.lang.model.element.Name; 016import javax.lang.model.element.TypeElement; 017import javax.lang.model.util.Elements; 018import java.io.IOException; 019import java.util.ArrayList; 020import java.util.Collections; 021import java.util.HashSet; 022import java.util.Iterator; 023import java.util.LinkedHashMap; 024import java.util.LinkedHashSet; 025import java.util.List; 026import java.util.Map; 027import java.util.Set; 028 029public class Processor extends AbstractProcessor { 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(ContextModule.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 082 readModule(roundEnv); 083 readChangedBeans(factoryBeans, true); 084 readChangedBeans(beans, false); 085 readChangedBeans(controllers, false); 086 087 mergeMetaData(); 088 089 writeBeanHelpers(); 090 if (roundEnv.processingOver()) { 091 writeBeanFactory(); 092 } 093 094 return false; 095 } 096 097 private void writeBeanHelpers() { 098 for (BeanReader beanReader : beanReaders) { 099 try { 100 if (!beanReader.isWrittenToFile()) { 101 SimpleBeanWriter writer = new SimpleBeanWriter(beanReader, context); 102 writer.write(); 103 beanReader.setWrittenToFile(); 104 } 105 } catch (FilerException e) { 106 context.logWarn("FilerException to write $di class " + beanReader.getBeanType() + " " + e.getMessage()); 107 108 } catch (IOException e) { 109 e.printStackTrace(); 110 context.logError(beanReader.getBeanType(), "Failed to write $di class"); 111 } 112 } 113 } 114 115 private void writeBeanFactory() { 116 MetaDataOrdering ordering = new MetaDataOrdering(metaData.values(), context); 117 int remaining = ordering.processQueue(); 118 if (remaining > 0) { 119 ordering.logWarnings(); 120 } 121 122 try { 123 SimpleFactoryWriter factoryWriter = new SimpleFactoryWriter(ordering, context); 124 factoryWriter.write(); 125 } catch (FilerException e) { 126 context.logWarn("FilerException trying to write factory " + e.getMessage()); 127 } catch (IOException e) { 128 context.logError("Failed to write factory " + e.getMessage()); 129 } 130 } 131 132 /** 133 * Read the beans that have changed. 134 */ 135 private void readChangedBeans(Set<? extends Element> beans, boolean factory) { 136 for (Element element : beans) { 137 if (!(element instanceof TypeElement)) { 138 context.logError("unexpected type [" + element + "]"); 139 } else { 140 if (readBeans.add(element.toString())) { 141 readBeanMeta((TypeElement) element, factory); 142 } else { 143 context.logDebug("skipping already processed bean " + element); 144 } 145 } 146 } 147 } 148 149 /** 150 * Merge the changed bean meta data into the existing (factory) metaData. 151 */ 152 private void mergeMetaData() { 153 for (BeanReader beanReader : beanReaders) { 154 if (beanReader.isRequestScoped()) { 155 context.logDebug("skipping request scoped processed bean " + beanReader); 156 } else { 157 String metaKey = beanReader.getMetaKey(); 158 MetaData metaData = this.metaData.get(metaKey); 159 if (metaData == null) { 160 addMeta(beanReader); 161 } else { 162 updateMeta(metaData, beanReader); 163 } 164 } 165 } 166 } 167 168 /** 169 * Add a new previously unknown bean. 170 */ 171 private void addMeta(BeanReader beanReader) { 172 MetaData meta = beanReader.createMeta(); 173 metaData.put(meta.getKey(), meta); 174 for (MetaData methodMeta : beanReader.createFactoryMethodMeta()) { 175 metaData.put(methodMeta.getKey(), methodMeta); 176 } 177 } 178 179 /** 180 * Update the meta data on a previously known bean. 181 */ 182 private void updateMeta(MetaData metaData, BeanReader beanReader) { 183 metaData.update(beanReader); 184 } 185 186 /** 187 * Read the dependency injection meta data for the given bean. 188 */ 189 private void readBeanMeta(TypeElement typeElement, boolean factory) { 190 191 if (typeElement.getKind() == ElementKind.ANNOTATION_TYPE) { 192 context.logDebug("skipping annotation type " + typeElement); 193 return; 194 } 195 BeanReader beanReader = new BeanReader(typeElement, context); 196 beanReader.read(factory); 197 beanReaders.add(beanReader); 198 } 199 200 /** 201 * Read the existing meta data from ContextModule (if found) and the factory bean (if exists). 202 */ 203 private void readModule(RoundEnvironment roundEnv) { 204 205 String factory = context.loadMetaInfServices(); 206 if (factory != null) { 207 TypeElement factoryType = elementUtils.getTypeElement(factory); 208 if (factoryType != null) { 209 readFactory(factoryType); 210 } 211 } 212 213 Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(ContextModule.class); 214 if (!elementsAnnotatedWith.isEmpty()) { 215 Iterator<? extends Element> iterator = elementsAnnotatedWith.iterator(); 216 if (iterator.hasNext()) { 217 Element element = iterator.next(); 218 ContextModule annotation = element.getAnnotation(ContextModule.class); 219 if (annotation != null) { 220 context.setContextDetails(annotation.name(), annotation.provides(), annotation.dependsOn(), element); 221 } 222 } 223 } 224 } 225 226 227 /** 228 * Read the existing factory bean. Each of the build methods is annotated with <code>@DependencyMeta</code> 229 * which holds the information we need (to regenerate the factory with any changes). 230 */ 231 private void readFactory(TypeElement factoryType) { 232 233 ContextModule module = factoryType.getAnnotation(ContextModule.class); 234 context.setContextDetails(module.name(), module.provides(), module.dependsOn(), factoryType); 235 236 List<? extends Element> elements = factoryType.getEnclosedElements(); 237 if (elements != null) { 238 for (Element element : elements) { 239 ElementKind kind = element.getKind(); 240 if (ElementKind.METHOD == kind) { 241 242 Name simpleName = element.getSimpleName(); 243 if (simpleName.toString().startsWith("build")) { 244 // read a build method - DependencyMeta 245 DependencyMeta meta = element.getAnnotation(DependencyMeta.class); 246 if (meta == null) { 247 context.logError("Missing @DependencyMeta on method " + simpleName); 248 } else { 249 final MetaData metaData = new MetaData(meta); 250 this.metaData.put(metaData.getKey(), metaData); 251 } 252 } 253 } 254 } 255 } 256 } 257}