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