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 117 MetaDataOrdering ordering = new MetaDataOrdering(metaData.values(), context); 118 int remaining = ordering.processQueue(); 119 if (remaining > 0) { 120 if (ordering.hasCircularDependencies()) { 121 ordering.errorOnCircularDependencies(); 122 } else { 123 context.logWarn("there are " + remaining + " beans with unsatisfied dependencies (assuming external dependencies)"); 124 ordering.warnOnDependencies(); 125 } 126 } 127 128 try { 129 SimpleFactoryWriter factoryWriter = new SimpleFactoryWriter(ordering, context); 130 factoryWriter.write(); 131 } catch (FilerException e) { 132 context.logWarn("FilerException trying to write factory " + e.getMessage()); 133 } catch (IOException e) { 134 context.logError("Failed to write factory " + e.getMessage()); 135 } 136 } 137 138 /** 139 * Read the beans that have changed. 140 */ 141 private void readChangedBeans(Set<? extends Element> beans, boolean factory) { 142 for (Element element : beans) { 143 if (!(element instanceof TypeElement)) { 144 context.logError("unexpected type [" + element + "]"); 145 } else { 146 if (readBeans.add(element.toString())) { 147 readBeanMeta((TypeElement) element, factory); 148 } else { 149 context.logDebug("skipping already processed bean " + element); 150 } 151 } 152 } 153 } 154 155 /** 156 * Merge the changed bean meta data into the existing (factory) metaData. 157 */ 158 private void mergeMetaData() { 159 for (BeanReader beanReader : beanReaders) { 160 if (beanReader.isRequestScoped()) { 161 context.logDebug("skipping request scoped processed bean " + beanReader); 162 } else { 163 String metaKey = beanReader.getMetaKey(); 164 MetaData metaData = this.metaData.get(metaKey); 165 if (metaData == null) { 166 addMeta(beanReader); 167 } else { 168 updateMeta(metaData, beanReader); 169 } 170 } 171 } 172 } 173 174 /** 175 * Add a new previously unknown bean. 176 */ 177 private void addMeta(BeanReader beanReader) { 178 MetaData meta = beanReader.createMeta(); 179 metaData.put(meta.getKey(), meta); 180 for (MetaData methodMeta : beanReader.createFactoryMethodMeta()) { 181 metaData.put(methodMeta.getKey(), methodMeta); 182 } 183 } 184 185 /** 186 * Update the meta data on a previously known bean. 187 */ 188 private void updateMeta(MetaData metaData, BeanReader beanReader) { 189 metaData.update(beanReader); 190 } 191 192 /** 193 * Read the dependency injection meta data for the given bean. 194 */ 195 private void readBeanMeta(TypeElement typeElement, boolean factory) { 196 197 if (typeElement.getKind() == ElementKind.ANNOTATION_TYPE) { 198 context.logDebug("skipping annotation type " + typeElement); 199 return; 200 } 201 BeanReader beanReader = new BeanReader(typeElement, context); 202 beanReader.read(factory); 203 beanReaders.add(beanReader); 204 } 205 206 /** 207 * Read the existing meta data from ContextModule (if found) and the factory bean (if exists). 208 */ 209 private void readModule(RoundEnvironment roundEnv) { 210 211 String factory = context.loadMetaInfServices(); 212 if (factory != null) { 213 TypeElement factoryType = elementUtils.getTypeElement(factory); 214 if (factoryType != null) { 215 readFactory(factoryType); 216 } 217 } 218 219 Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(ContextModule.class); 220 if (!elementsAnnotatedWith.isEmpty()) { 221 Iterator<? extends Element> iterator = elementsAnnotatedWith.iterator(); 222 if (iterator.hasNext()) { 223 Element element = iterator.next(); 224 ContextModule annotation = element.getAnnotation(ContextModule.class); 225 if (annotation != null) { 226 context.setContextDetails(annotation.name(), annotation.provides(), annotation.dependsOn(), element); 227 } 228 } 229 } 230 } 231 232 233 /** 234 * Read the existing factory bean. Each of the build methods is annotated with <code>@DependencyMeta</code> 235 * which holds the information we need (to regenerate the factory with any changes). 236 */ 237 private void readFactory(TypeElement factoryType) { 238 239 ContextModule module = factoryType.getAnnotation(ContextModule.class); 240 context.setContextDetails(module.name(), module.provides(), module.dependsOn(), factoryType); 241 242 List<? extends Element> elements = factoryType.getEnclosedElements(); 243 if (elements != null) { 244 for (Element element : elements) { 245 ElementKind kind = element.getKind(); 246 if (ElementKind.METHOD == kind) { 247 248 Name simpleName = element.getSimpleName(); 249 if (simpleName.toString().startsWith("build")) { 250 // read a build method - DependencyMeta 251 DependencyMeta meta = element.getAnnotation(DependencyMeta.class); 252 if (meta == null) { 253 context.logError("Missing @DependencyMeta on method " + simpleName.toString()); 254 } else { 255 final MetaData metaData = new MetaData(meta); 256 this.metaData.put(metaData.getKey(), metaData); 257 } 258 } 259 } 260 } 261 } 262 } 263}