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 MetaData metaData = this.metaData.get(beanReader.getMetaKey()); 158 if (metaData == null) { 159 addMeta(beanReader); 160 } else { 161 updateMeta(metaData, beanReader); 162 } 163 } 164 } 165 } 166 167 /** 168 * Add a new previously unknown bean. 169 */ 170 private void addMeta(BeanReader beanReader) { 171 MetaData meta = beanReader.createMeta(); 172 metaData.put(meta.getKey(), meta); 173 for (MetaData methodMeta : beanReader.createFactoryMethodMeta()) { 174 metaData.put(methodMeta.getKey(), methodMeta); 175 } 176 } 177 178 /** 179 * Update the meta data on a previously known bean. 180 */ 181 private void updateMeta(MetaData metaData, BeanReader beanReader) { 182 metaData.update(beanReader); 183 } 184 185 /** 186 * Read the dependency injection meta data for the given bean. 187 */ 188 private void readBeanMeta(TypeElement typeElement, boolean factory) { 189 if (typeElement.getKind() == ElementKind.ANNOTATION_TYPE) { 190 context.logDebug("skipping annotation type " + typeElement); 191 return; 192 } 193 beanReaders.add(new BeanReader(typeElement, context, factory).read()); 194 } 195 196 /** 197 * Read the existing meta data from InjectModule (if found) and the factory bean (if exists). 198 */ 199 private void readModule(RoundEnvironment roundEnv) { 200 String factory = context.loadMetaInfServices(); 201 if (factory != null) { 202 TypeElement factoryType = elementUtils.getTypeElement(factory); 203 if (factoryType != null) { 204 readFactory(factoryType); 205 } 206 } 207 208 Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(InjectModule.class); 209 if (!elementsAnnotatedWith.isEmpty()) { 210 Iterator<? extends Element> iterator = elementsAnnotatedWith.iterator(); 211 if (iterator.hasNext()) { 212 Element element = iterator.next(); 213 InjectModule annotation = element.getAnnotation(InjectModule.class); 214 if (annotation != null) { 215 context.setContextDetails(annotation.name(), annotation.provides(), annotation.dependsOn(), element); 216 } 217 } 218 } 219 } 220 221 222 /** 223 * Read the existing factory bean. Each of the build methods is annotated with <code>@DependencyMeta</code> 224 * which holds the information we need (to regenerate the factory with any changes). 225 */ 226 private void readFactory(TypeElement factoryType) { 227 InjectModule module = factoryType.getAnnotation(InjectModule.class); 228 context.setContextDetails(module.name(), module.provides(), module.dependsOn(), factoryType); 229 230 List<? extends Element> elements = factoryType.getEnclosedElements(); 231 if (elements != null) { 232 for (Element element : elements) { 233 if (ElementKind.METHOD == element.getKind()) { 234 readBuildMethodDependencyMeta(element); 235 } 236 } 237 } 238 } 239 240 private void readBuildMethodDependencyMeta(Element element) { 241 Name simpleName = element.getSimpleName(); 242 if (simpleName.toString().startsWith("build_")) { 243 // read a build method - DependencyMeta 244 DependencyMeta meta = element.getAnnotation(DependencyMeta.class); 245 if (meta == null) { 246 context.logError("Missing @DependencyMeta on method " + simpleName); 247 } else { 248 final MetaData metaData = new MetaData(meta); 249 this.metaData.put(metaData.getKey(), metaData); 250 } 251 } 252 } 253}