001package io.avaje.jsonb.generator;
002
003import io.avaje.jsonb.Json;
004
005import javax.annotation.processing.AbstractProcessor;
006import javax.annotation.processing.ProcessingEnvironment;
007import javax.annotation.processing.RoundEnvironment;
008import javax.lang.model.SourceVersion;
009import javax.lang.model.element.Element;
010import javax.lang.model.element.ElementKind;
011import javax.lang.model.element.TypeElement;
012import java.io.IOException;
013import java.util.*;
014
015public class Processor extends AbstractProcessor {
016
017  private final ComponentMetaData metaData = new ComponentMetaData();
018  private final ImportReader importReader = new ImportReader();
019  private final List<BeanReader> allReaders = new ArrayList<>();
020  private final Set<String> sourceTypes = new HashSet<>();
021
022  private ProcessingContext context;
023  private SimpleComponentWriter componentWriter;
024  private boolean readModuleInfo;
025
026  public Processor() {
027  }
028
029  @Override
030  public SourceVersion getSupportedSourceVersion() {
031    return SourceVersion.latest();
032  }
033
034  @Override
035  public synchronized void init(ProcessingEnvironment processingEnv) {
036    super.init(processingEnv);
037    this.context = new ProcessingContext(processingEnv);
038    this.componentWriter = new SimpleComponentWriter(context, metaData);
039  }
040
041  @Override
042  public Set<String> getSupportedAnnotationTypes() {
043    Set<String> annotations = new LinkedHashSet<>();
044    annotations.add(Json.class.getCanonicalName());
045    annotations.add(Json.Import.class.getCanonicalName());
046    return annotations;
047  }
048
049  /**
050   * Read the existing metadata from the generated component (if exists).
051   */
052  private void readModule() {
053    if (readModuleInfo) {
054      return;
055    }
056    readModuleInfo = true;
057    new ComponentReader(context, metaData).read();
058  }
059
060  @Override
061  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment round) {
062    readModule();
063    writeAdapters(round.getElementsAnnotatedWith(Json.class));
064    writeAdaptersForImported(round.getElementsAnnotatedWith(Json.Import.class));
065    initialiseComponent();
066    cascadeTypes();
067    writeComponent(round.processingOver());
068    return false;
069  }
070
071  private void cascadeTypes() {
072    while (!allReaders.isEmpty()) {
073      cascadeTypesInner();
074    }
075  }
076
077  private void cascadeTypesInner() {
078    ArrayList<BeanReader> copy = new ArrayList<>(allReaders);
079    allReaders.clear();
080
081    Set<String> extraTypes = new TreeSet<>();
082    for (BeanReader reader : copy) {
083      reader.cascadeTypes(extraTypes);
084    }
085    for (String type : extraTypes) {
086      if (!ignoreType(type)) {
087        TypeElement element = context.element(type);
088        if (cascadeElement(element)) {
089          writeAdapterForType(element);
090        }
091      }
092    }
093  }
094
095  private boolean cascadeElement(TypeElement element) {
096    return element.getKind() != ElementKind.ENUM
097      && !metaData.contains(adapterName(element));
098  }
099
100  private String adapterName(TypeElement element) {
101    return new AdapterName(element).fullName();
102  }
103
104  private boolean ignoreType(String type) {
105    return type.indexOf('.') == -1
106      || type.startsWith("java.")
107      || type.startsWith("javax.")
108      || sourceTypes.contains(type);
109  }
110
111  /**
112   * Elements that have a {@code @Json.Import} annotation.
113   */
114  private void writeAdaptersForImported(Set<? extends Element> importedElements) {
115    for (Element importedElement : importedElements) {
116      for (String importType : importReader.read(importedElement)) {
117        TypeElement element = context.element(importType);
118        if (element == null) {
119          context.logError("Unable to find imported element " + importType);
120        } else {
121          writeAdapterForType(element);
122        }
123      }
124    }
125  }
126
127  private void initialiseComponent() {
128    metaData.initialiseFullName();
129    try {
130      componentWriter.initialise();
131    } catch (IOException e) {
132      context.logError("Error creating writer for JsonbComponent", e);
133    }
134  }
135
136  private void writeComponent(boolean processingOver) {
137    if (processingOver) {
138      try {
139        componentWriter.write();
140        componentWriter.writeMetaInf();
141      } catch (IOException e) {
142        context.logError("Error writing component", e);
143      }
144    }
145  }
146
147  /**
148   * Read the beans that have changed.
149   */
150  private void writeAdapters(Set<? extends Element> beans) {
151    for (Element element : beans) {
152      if (!(element instanceof TypeElement)) {
153        context.logError("unexpected type [" + element + "]");
154      } else {
155        writeAdapterForType((TypeElement) element);
156      }
157    }
158  }
159
160  private void writeAdapterForType(TypeElement typeElement) {
161    BeanReader beanReader = new BeanReader(typeElement, context);
162    beanReader.read();
163    try {
164      SimpleAdapterWriter beanWriter = new SimpleAdapterWriter(beanReader, context);
165      metaData.add(beanWriter.fullName());
166      beanWriter.write();
167      allReaders.add(beanReader);
168      sourceTypes.add(typeElement.getSimpleName().toString());
169    } catch (IOException e) {
170      context.logError("Error writing JsonAdapter for %s %s", beanReader, e);
171    }
172  }
173
174}