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