All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.predic8.membrane.annot.SpringConfigurationXSDGeneratingAnnotationProcessor Maven / Gradle / Ivy

There is a newer version: 5.7.4
Show newest version
/* Copyright 2013 predic8 GmbH, www.predic8.com

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License. */
package com.predic8.membrane.annot;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.*;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
import javax.tools.Diagnostic.Kind;

import com.predic8.membrane.annot.generator.*;
import com.predic8.membrane.annot.generator.kubernetes.KubernetesBootstrapper;
import com.predic8.membrane.annot.model.AttributeInfo;
import com.predic8.membrane.annot.model.ChildElementDeclarationInfo;
import com.predic8.membrane.annot.model.ChildElementInfo;
import com.predic8.membrane.annot.model.ElementInfo;
import com.predic8.membrane.annot.model.MainInfo;
import com.predic8.membrane.annot.model.Model;
import com.predic8.membrane.annot.model.OtherAttributesInfo;
import com.predic8.membrane.annot.model.TextContentInfo;

import static javax.tools.StandardLocation.CLASS_OUTPUT;

/**
 * The annotation processor for the annotations defining Membrane's configuration language ({@link MCMain} and others).
 *
 * 
    *
  • validates the correct usage of the annotations (not everything is checked, though)
  • *
  • generates the XML schema file for the declared namespace
  • *
  • generates parser classes for Spring-based deployments
  • *
  • generates parser classes for Blueprint-based deployments (if * org.apache.aries.blueprint:blueprint-parser and org.apache.aries.blueprint:org.apache.aries.blueprint.api * are present on the classpath)
  • *
  • generates the documentation of the language as an XML file * (if the MEMBRANE_GENERATE_DOC_DIR environment variable is set), based on the annotations and javadoc.
  • *
*/ @SupportedAnnotationTypes(value = { "com.predic8.membrane.annot.*" }) public class SpringConfigurationXSDGeneratingAnnotationProcessor extends AbstractProcessor { @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } private static final String CACHE_FILE_FORMAT_VERSION = "1"; private void log(String message) { //processingEnv.getMessager().printMessage(Kind.NOTE, message); // for Eclipse //System.out.println(message); // for Maven command line } @SuppressWarnings("unchecked") private void read() { if (cache != null) return; cache = new HashMap<>(); try { FileObject o = processingEnv.getFiler().getResource(CLASS_OUTPUT, "", "META-INF/membrane.cache"); try (BufferedReader r = new BufferedReader(o.openReader(false))) { if (!CACHE_FILE_FORMAT_VERSION.equals(r.readLine())) return; HashSet currentSet = null; Class annotationClass = null; while (true) { String line = r.readLine(); if (line == null) break; if (line.startsWith(" ")) { line = line.substring(1); TypeElement element = null; try { element = processingEnv.getElementUtils().getTypeElement(line); } catch (RuntimeException e) { // do nothing (Eclipse) } if (element != null) { if (element.getAnnotation(annotationClass) != null) currentSet.add(element); } } else { try { annotationClass = (Class) getClass().getClassLoader().loadClass(line); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } currentSet = new HashSet<>(); cache.put(annotationClass, currentSet); } } } } catch (FileNotFoundException e) { // do nothing (Maven) } catch (IOException e) { // do nothing (Eclipse) } for (Set e : cache.values()) { String status = "read " + e.size(); log(status); } } private void write() { try { FileObject o = processingEnv.getFiler().createResource(CLASS_OUTPUT, "", "META-INF/membrane.cache"); try (BufferedWriter bw = new BufferedWriter(o.openWriter())) { bw.write("1\n"); for (Map.Entry, HashSet> e : cache.entrySet()) { bw.write(e.getKey().getName()); bw.write("\n"); for (Element f : e.getValue()) { bw.write(" "); bw.write(((TypeElement) f).getQualifiedName().toString()); bw.write("\n"); } } } } catch (IOException e) { throw new RuntimeException(e); } } private HashMap, HashSet> cache; private Set getCachedElementsAnnotatedWith(RoundEnvironment roundEnv, Class annotation) { //FileObject o = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "META-INF", "membrane.cache"); if (cache == null) read(); HashSet result = cache.get(annotation); if (result == null) { // update cache cache.put(annotation, result = new HashSet<>(roundEnv.getElementsAnnotatedWith(annotation))); } else { for (Element e : roundEnv.getElementsAnnotatedWith(annotation)) { result.remove(e); result.add(e); } } return result; } boolean done; @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { // An instance is create per compiler call and not kept for the next incremental compilation. // // Use "roundEnv.getRootElements()" to get root elements (=classes) changed since last round. // Use "processingEnv.getElementUtils().getTypeElement(className)" to get the TypeElement of any class (changed or not). // "annotations.size()" equals the number of our annotations found in the "roundEnv.getRootElement()" classes. // // * rounds are repeated until nothing is filed (=created) any more // * resources (and java files?) may only be filed in one round // * in the last round, "roundEnv.processingOver()" is true try { String status = "process() a=" + annotations.size() + " r=" + roundEnv.getRootElements().size() + " h=" + hashCode() + (roundEnv.processingOver() ? " processing-over" : " "); log(status); read(); if (roundEnv.processingOver()) write(); if (annotations.size() > 0) { // a class with one of our annotation needs to be compiled status = "working with " + getCachedElementsAnnotatedWith(roundEnv, MCMain.class).size() + " and " + getCachedElementsAnnotatedWith(roundEnv, MCElement.class).size(); log(status); Model m = new Model(); Set mcmains = getCachedElementsAnnotatedWith(roundEnv, MCMain.class); if (mcmains.isEmpty()) { processingEnv.getMessager().printMessage(Kind.WARNING, "@MCMain was nowhere found."); return true; } for (Element element : mcmains) { MainInfo main = new MainInfo(); main.setElement((TypeElement)element); main.setAnnotation(element.getAnnotation(MCMain.class)); m.getMains().add(main); } for (Element e : getCachedElementsAnnotatedWith(roundEnv, MCElement.class)) { ElementInfo ii = new ElementInfo(); ii.setElement((TypeElement)e); ii.setAnnotation(e.getAnnotation(MCElement.class)); MainInfo main = ii.getMain(m); main.getIis().add(ii); main.getElements().put(ii.getElement(), ii); if (main.getGlobals().containsKey(ii.getAnnotation().name())) throw new ProcessingException("Duplicate global @MCElement name.", main.getGlobals().get(ii.getAnnotation().name()).getElement(), ii.getElement()); if (main.getIds().containsKey(ii.getId())) throw new ProcessingException("Duplicate element id \"" + ii.getId() + "\". Please assign one using @MCElement(id=\"...\").", e, main.getIds().get(ii.getId()).getElement()); main.getIds().put(ii.getId(), ii); scan(m, main, ii); if (ii.getTci() != null && !ii.getAnnotation().mixed()) throw new ProcessingException("@MCTextContent requires @MCElement(..., mixed=true) on the class.", ii.getElement()); if (ii.getTci() == null && ii.getAnnotation().mixed()) throw new ProcessingException("@MCElement(..., mixed=true) requires @MCTextContent on a property.", ii.getElement()); } for (MainInfo main : m.getMains()) { for (Map.Entry f : main.getChildElementDeclarations().entrySet()) { ChildElementDeclarationInfo cedi = f.getValue(); ElementInfo ei = main.getElements().get(f.getKey()); if (ei != null) cedi.getElementInfo().add(ei); else { for (Map.Entry e : main.getElements().entrySet()) if (processingEnv.getTypeUtils().isAssignable(e.getKey().asType(), f.getKey().asType())) cedi.getElementInfo().add(e.getValue()); } for (ElementInfo ei2 : cedi.getElementInfo()) ei2.addUsedBy(f.getValue()); if (cedi.getElementInfo().isEmpty() && cedi.isRaiseErrorWhenNoSpecimen()) { processingEnv.getMessager().printMessage(Kind.ERROR, "@MCChildElement references " + f.getKey().getQualifiedName() + ", but there is no @MCElement among it and its subclasses.", f.getKey()); return true; } } } if (mcmains.isEmpty()) { processingEnv.getMessager().printMessage(Kind.ERROR, "@MCMain but no @MCElement found.", mcmains.iterator().next()); return true; } process(m); } return true; } catch (IOException e) { throw new RuntimeException(e); } catch (ProcessingException e1) { for (int i = 0; i < e1.getElements().length; i++) processingEnv.getMessager().printMessage(Kind.ERROR, i == 0 ? e1.getMessage() : "also here", e1.getElements()[i]); return true; } } private static final String REQUIRED = "com.predic8.membrane.annot.Required"; private void scan(Model m, MainInfo main, ElementInfo ii) { scan(m, main, ii, ii.getElement()); } private void scan(Model m, MainInfo main, ElementInfo ii, TypeElement te) { TypeMirror superclass = te.getSuperclass(); if (superclass instanceof DeclaredType) scan(m, main, ii, (TypeElement) ((DeclaredType)superclass).asElement()); for (Element e2 : te.getEnclosedElements()) { MCAttribute a = e2.getAnnotation(MCAttribute.class); if (a != null) { AttributeInfo ai = new AttributeInfo(); ai.setAnnotation(a); ai.setE((ExecutableElement) e2); ai.setRequired(isRequired(e2)); ii.getAis().add(ai); ii.setHasIdField(ii.isHasIdField() || ai.getXMLName().equals("id")); } MCOtherAttributes d = e2.getAnnotation(MCOtherAttributes.class); if (d != null) { OtherAttributesInfo oai = new OtherAttributesInfo(); oai.setOtherAttributesSetter((ExecutableElement) e2); ii.setOai(oai); } MCChildElement b = e2.getAnnotation(MCChildElement.class); if (b != null) { ChildElementInfo cei = new ChildElementInfo(); cei.setEi(ii); cei.setAnnotation(b); cei.setE((ExecutableElement) e2); List parameters = cei.getE().getParameters(); if (parameters.size() == 0) throw new ProcessingException("Setter must have exactly one parameter.", e2); TypeMirror setterArgType = parameters.get(0).asType(); if (!(setterArgType instanceof DeclaredType)) throw new ProcessingException("Setter argument must be of an @MCElement-annotated type.", parameters.get(0)); cei.setTypeDeclaration((TypeElement) ((DeclaredType) setterArgType).asElement()); cei.setPropertyName(AnnotUtils.dejavaify(e2.getSimpleName().toString().substring(3))); cei.setRequired(isRequired(e2)); ii.getCeis().add(cei); // unwrap "java.util.List" and "java.util.Collection" if (cei.getTypeDeclaration().getQualifiedName().toString().startsWith("java.util.List") || cei.getTypeDeclaration().getQualifiedName().toString().startsWith("java.util.Collection")) { cei.setTypeDeclaration((TypeElement) ((DeclaredType) ((DeclaredType) setterArgType).getTypeArguments().get(0)).asElement()); cei.setList(true); } ChildElementDeclarationInfo cedi; if (!main.getChildElementDeclarations().containsKey(cei.getTypeDeclaration())) { cedi = new ChildElementDeclarationInfo(); cedi.setTarget(cei.getTypeDeclaration()); cedi.setRaiseErrorWhenNoSpecimen(!cei.getAnnotation().allowForeign()); main.getChildElementDeclarations().put(cei.getTypeDeclaration(), cedi); } else { cedi = main.getChildElementDeclarations().get(cei.getTypeDeclaration()); cedi.setRaiseErrorWhenNoSpecimen(cedi.isRaiseErrorWhenNoSpecimen() || !cei.getAnnotation().allowForeign()); } cedi.addUsedBy(cei); } MCTextContent c = e2.getAnnotation(MCTextContent.class); if (c != null) { TextContentInfo tci = new TextContentInfo(); tci.setPropertyName(AnnotUtils.dejavaify(e2.getSimpleName().toString().substring(3))); ii.setTci(tci); } } HashSet childOrders = new HashSet<>(); for (ChildElementInfo cei : ii.getCeis()) { if (!childOrders.add(cei.getAnnotation().order())) throw new ProcessingException("@MCChildElement(order=...) must be unique.", cei.getE()); } Collections.sort(ii.getCeis()); } private boolean isRequired(Element e2) { for (AnnotationMirror am : e2.getAnnotationMirrors()) if (((TypeElement)am.getAnnotationType().asElement()).getQualifiedName().toString().equals(REQUIRED)) return true; return false; } public void process(Model m) throws IOException { new Schemas(processingEnv).writeXSD(m); new KubernetesBootstrapper(processingEnv).boot(m); new Parsers(processingEnv).writeParsers(m); new Parsers(processingEnv).writeParserDefinitior(m); new HelpReference(processingEnv).writeHelp(m); new NamespaceInfo(processingEnv).writeInfo(m); if (processingEnv.getElementUtils().getTypeElement("org.apache.aries.blueprint.ParserContext") != null) { new BlueprintParsers(processingEnv).writeParserDefinitior(m); new BlueprintParsers(processingEnv).writeParsers(m); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy