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