001package io.kanuka.generator; 002 003import io.kanuka.ContextModule; 004import io.kanuka.Factory; 005import io.kanuka.core.DependencyMeta; 006 007import javax.annotation.processing.AbstractProcessor; 008import javax.annotation.processing.ProcessingEnvironment; 009import javax.annotation.processing.RoundEnvironment; 010import javax.inject.Singleton; 011import javax.lang.model.SourceVersion; 012import javax.lang.model.element.Element; 013import javax.lang.model.element.ElementKind; 014import javax.lang.model.element.Name; 015import javax.lang.model.element.TypeElement; 016import javax.lang.model.util.Elements; 017import java.io.IOException; 018import java.util.ArrayList; 019import java.util.Iterator; 020import java.util.LinkedHashMap; 021import java.util.LinkedHashSet; 022import java.util.List; 023import java.util.Map; 024import java.util.Set; 025 026public class Processor extends AbstractProcessor { 027 028 private ProcessingContext processingContext; 029 030 private Elements elementUtils; 031 032 private Map<String, MetaData> metaData = new LinkedHashMap<>(); 033 034 private List<BeanReader> beanReaders = new ArrayList<>(); 035 036 public Processor() { 037 } 038 039 @Override 040 public synchronized void init(ProcessingEnvironment processingEnv) { 041 super.init(processingEnv); 042 this.processingContext = new ProcessingContext(processingEnv); 043 this.elementUtils = processingEnv.getElementUtils(); 044 } 045 046 @Override 047 public Set<String> getSupportedAnnotationTypes() { 048 049 Set<String> annotations = new LinkedHashSet<>(); 050 annotations.add(ContextModule.class.getCanonicalName()); 051 annotations.add(Factory.class.getCanonicalName()); 052 annotations.add(Singleton.class.getCanonicalName()); 053 return annotations; 054 } 055 056 @Override 057 public SourceVersion getSupportedSourceVersion() { 058 return SourceVersion.latest(); 059 } 060 061 @Override 062 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 063 064 Set<? extends Element> factoryBeans = roundEnv.getElementsAnnotatedWith(Factory.class); 065 Set<? extends Element> beans = roundEnv.getElementsAnnotatedWith(Singleton.class); 066 if (beans.isEmpty() && factoryBeans.isEmpty()) { 067 processingContext.logDebug("skip, no changed beans ..."); 068 return false; 069 } 070 071 readModule(roundEnv); 072 readChangedBeans(factoryBeans, true); 073 readChangedBeans(beans, false); 074 075 mergeMetaData(); 076 077 writeBeanHelpers(); 078 writeBeanFactory(); 079 080 return false; 081 } 082 083 084 private void writeBeanHelpers() { 085 for (BeanReader beanReader : beanReaders) { 086 try { 087 SimpleBeanWriter writer = new SimpleBeanWriter(beanReader, processingContext); 088 writer.write(); 089 } catch (IOException e) { 090 processingContext.logError(beanReader.getBeanType(), "Failed to write $di class"); 091 } 092 } 093 } 094 095 private void writeBeanFactory() { 096 097 MetaDataOrdering ordering = new MetaDataOrdering(metaData.values(), processingContext); 098 int remaining = ordering.processQueue(); 099 if (remaining > 0) { 100 processingContext.logWarn("there are " + remaining + " beans with unsatisfied dependencies (assuming external dependencies)"); 101 ordering.warnOnDependencies(); 102 } 103 104 try { 105 SimpleFactoryWriter factoryWriter = new SimpleFactoryWriter(ordering, processingContext); 106 factoryWriter.write(); 107 } catch (IOException e) { 108 processingContext.logError("Failed to write factory " + e.getMessage()); 109 } 110 } 111 112 /** 113 * Read the beans that have changed. 114 */ 115 private void readChangedBeans(Set<? extends Element> beans, boolean factory) { 116 for (Element element : beans) { 117 if (!(element instanceof TypeElement)) { 118 processingContext.logError("unexpected type [" + element + "]"); 119 } else { 120 readBeanMeta((TypeElement) element, factory); 121 } 122 } 123 } 124 125 /** 126 * Merge the changed bean meta data into the existing (factory) metaData. 127 */ 128 private void mergeMetaData() { 129 130 for (BeanReader beanReader : beanReaders) { 131 String simpleName = beanReader.getSimpleName(); 132 MetaData metaData = this.metaData.get(simpleName); 133 if (metaData == null) { 134 addMeta(beanReader); 135 136 } else { 137 updateMeta(metaData, beanReader); 138 } 139 } 140 } 141 142 /** 143 * Add a new previously unknown bean. 144 */ 145 private void addMeta(BeanReader beanReader) { 146 MetaData meta = beanReader.createMeta(); 147 metaData.put(meta.getType(), meta); 148 for (MetaData methodMeta : beanReader.createFactoryMethodMeta()) { 149 metaData.put(methodMeta.getType(), methodMeta); 150 } 151 } 152 153 /** 154 * Update the meta data on a previously known bean. 155 */ 156 private void updateMeta(MetaData metaData, BeanReader beanReader) { 157 metaData.update(beanReader); 158 } 159 160 /** 161 * Read the dependency injection meta data for the given bean. 162 */ 163 private void readBeanMeta(TypeElement typeElement, boolean factory) { 164 BeanReader beanReader = new BeanReader(typeElement, processingContext); 165 beanReader.read(factory); 166 beanReaders.add(beanReader); 167 } 168 169 /** 170 * Read the existing meta data from ContextModule (if found) and the factory bean (if exists). 171 */ 172 private void readModule(RoundEnvironment roundEnv) { 173 174 String factory = processingContext.loadMetaInfServices(); 175 if (factory != null) { 176 TypeElement factoryType = elementUtils.getTypeElement(factory); 177 if (factoryType != null) { 178 readFactory(factoryType); 179 } 180 } 181 182 Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(ContextModule.class); 183 if (!elementsAnnotatedWith.isEmpty()) { 184 Iterator<? extends Element> iterator = elementsAnnotatedWith.iterator(); 185 if (iterator.hasNext()) { 186 Element element = iterator.next(); 187 ContextModule annotation = element.getAnnotation(ContextModule.class); 188 if (annotation != null) { 189 processingContext.setContextDetails(annotation.name(), annotation.dependsOn(), element); 190 } 191 } 192 } 193 } 194 195 196 /** 197 * Read the existing factory bean. Each of the build methods is annotated with <code>@DependencyMeta</code> 198 * which holds the information we need (to regenerate the factory with any changes). 199 */ 200 private void readFactory(TypeElement factoryType) { 201 202 ContextModule module = factoryType.getAnnotation(ContextModule.class); 203 processingContext.setContextDetails(module.name(), module.dependsOn(), factoryType); 204 205 List<? extends Element> elements = factoryType.getEnclosedElements(); 206 if (elements != null) { 207 for (Element element : elements) { 208 ElementKind kind = element.getKind(); 209 if (ElementKind.METHOD == kind) { 210 211 Name simpleName = element.getSimpleName(); 212 if (simpleName.toString().startsWith("build")) { 213 // read a build method - DependencyMeta 214 DependencyMeta meta = element.getAnnotation(DependencyMeta.class); 215 if (meta == null) { 216 processingContext.logError("Missing @DependencyMeta on method " + simpleName.toString()); 217 } else { 218 metaData.put(meta.type(), new MetaData(meta)); 219 } 220 } 221 } 222 } 223 } 224 } 225}