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