diff --git a/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/processors/DataElementProcessor.java b/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/processors/DataElementProcessor.java index 238b29077..1011bb8ce 100644 --- a/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/processors/DataElementProcessor.java +++ b/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/processors/DataElementProcessor.java @@ -1,12 +1,16 @@ package org.eclipse.ice.dev.annotations.processors; import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; import java.io.Writer; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Properties; import java.util.Set; +import java.util.stream.Collectors; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Messager; @@ -29,34 +33,59 @@ import org.apache.velocity.VelocityContext; import org.apache.velocity.app.Velocity; +import org.eclipse.ice.dev.annotations.DataElement; import org.eclipse.ice.dev.annotations.DataField; import com.google.auto.service.AutoService; +/** + * Processor for DataElement Annotations. + * + * This will generate an implementation for an interface annotated with + * DataElement, populating the implementation with metadata and fields + * specified with the DataField annotation. + */ @SupportedAnnotationTypes("org.eclipse.ice.dev.annotations.DataElement") @SupportedSourceVersion(SourceVersion.RELEASE_8) @AutoService(Processor.class) public class DataElementProcessor extends AbstractProcessor { - private static final String template = "templates/DataElement.vm"; - protected Messager messager; + public class UnexpectedValueError extends Exception { + private static final long serialVersionUID = -8486833574190525020L; + + public UnexpectedValueError(String message) { + super(message); + } + } + /** + * A simple container for fields discovered on a DataElement. + */ private static class Fields { + /** + * Container for name and class name attributes of DataField in string + * representation. + */ public class Field { String name; String className; - public void setName(String name) { - this.name = name; - } - public void setClassName(String className) { - this.className = className; + + public String getClassName() { + return className; } + public String getName() { return name; } - public String getClassName() { - return className; + + public void setClassName(final String className) { + this.className = className; + } + + public void setName(final String name) { + this.name = name; } + @Override public String toString() { return "Field (name=" + name + ", className=" + className + ")"; @@ -67,150 +96,198 @@ public String toString() { protected Field building; public Fields() { - this.fields = new ArrayList(); + this.fields = new ArrayList<>(); this.building = null; } + public boolean isBuilding() { + return this.building != null; + } + public void begin() { this.building = new Field(); } - public void setName(String name) { - this.building.setName(name); + public void finish() { + this.fields.add(this.building); + this.building = null; } - public void setClassName(String className) { + public List getFields() { + return fields; + } + + public void setClassName(final String className) { this.building.setClassName(className); } - public void finish() { - this.fields.add(this.building); - this.building = null; + public void setName(final String name) { + this.building.setName(name); } @Override public String toString() { return fields.toString(); } - - public List getFields() { - return fields; - } } - private class FieldVisitor extends SimpleAnnotationValueVisitor8 { - - protected final Elements elementUtils; - - FieldVisitor(Elements elementUtils) { - this.elementUtils = elementUtils; - } + /** + * Visitor that accumulates DataField information from AnnotationValues. + */ + private class FieldVisitor extends SimpleAnnotationValueVisitor8, Fields> { @Override - protected Void defaultAction(Object o, Fields f) { - return null; + protected Optional defaultAction(final Object o, final Fields f) { + return Optional.of(new UnexpectedValueError("An unexpected annotation value was encountered")); } @Override - public Void visitAnnotation(AnnotationMirror a, Fields f) { - if (a.getAnnotationType().toString().equals(DataField.class.getCanonicalName())) { - final Map elementValues = - elementUtils.getElementValuesWithDefaults(a); - f.begin(); - for (Map.Entry entry - : elementValues.entrySet()) { - entry.getValue().accept(this, f); + public Optional visitAnnotation(final AnnotationMirror a, final Fields f) { + if (!a.getAnnotationType().toString().equals(DataField.class.getCanonicalName())) { + return Optional.of(new UnexpectedValueError("Found AnnotationMirror not of type DataField")); + } + + f.begin(); + for (AnnotationValue value : getAnnotationValuesForMirror(a)) { + Optional result = value.accept(this, f); + if (result.isPresent()) { + return result; } - f.finish(); } - return null; + f.finish(); + return Optional.empty(); } @Override - public Void visitArray(List vals, Fields f) { - for (AnnotationValue val : vals) { - val.accept(this, f); + public Optional visitArray(final List vals, final Fields f) { + for (final AnnotationValue val : vals) { + Optional result = val.accept(this, f); + if (result.isPresent()) { + return result; + } } - return null; + return Optional.empty(); } @Override - public Void visitString(String s, Fields f) { + public Optional visitString(final String s, final Fields f) { + if (!f.isBuilding()) { + return Optional + .of(new UnexpectedValueError("Found String while still expecting DataField AnnotationMirror")); + } f.setName(s); - return null; + return Optional.empty(); } @Override - public Void visitType(TypeMirror t, Fields f) { + public Optional visitType(final TypeMirror t, final Fields f) { + if (!f.isBuilding()) { + return Optional + .of(new UnexpectedValueError("Found type while still expecting DataField Annotation Mirror")); + } f.setClassName(t.toString()); - return null; + return Optional.empty(); } } + /** + * Location of DataElement template for use with velocity. + */ + private static final String template = "templates/DataElement.vm"; + + protected Messager messager; + protected Elements elementUtils; + @Override - public void init(ProcessingEnvironment env) { + public void init(final ProcessingEnvironment env) { messager = env.getMessager(); + elementUtils = env.getElementUtils(); super.init(env); } - private void writeClass(String className, Fields fields) throws IOException { + @Override + public boolean process(final Set annotations, final RoundEnvironment roundEnv) { + final FieldVisitor fieldVisitor = new FieldVisitor(); + + for (final Element elem : roundEnv.getElementsAnnotatedWith(DataElement.class)) { + if (!elem.getKind().isInterface()) { + messager.printMessage(Diagnostic.Kind.ERROR, "DataElement annotation is only for interfaces"); + return false; + } + final Fields fields = new Fields(); + final List values = elem.getAnnotationMirrors().stream() + .map(mirror -> getAnnotationValuesForMirror(mirror)).flatMap(List::stream).collect(Collectors.toList()); + for (AnnotationValue value : values) { + Optional result = value.accept(fieldVisitor, fields); + if (result.isPresent()) { + messager.printMessage(Diagnostic.Kind.ERROR, stackTracetoString(result.get())); + return false; + } + } + try { + this.writeClass(((TypeElement) elem).getQualifiedName().toString(), fields); + } catch (final IOException e) { + messager.printMessage(Diagnostic.Kind.ERROR, stackTracetoString(e)); + return false; + } + } + return true; + } + + /** + * Return stack trace as string. + * @param e subject exception + * @return stack trace as string + */ + private String stackTracetoString(Throwable e) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + e.printStackTrace(pw); + return sw.toString(); + } + + /** + * Get a list of annotation values from an annotation mirror. + * @param mirror the mirror from which to grab values. + * @return list of AnnotationValue + */ + private List getAnnotationValuesForMirror(final AnnotationMirror mirror) { + final Map values = elementUtils + .getElementValuesWithDefaults(mirror); + return values.entrySet().stream().map(entry -> entry.getValue()).collect(Collectors.toList()); + } + + /** + * Write the implementation of DataElement annotated class to file. + * @param className the annotated class name + * @param fields the fields extracted from DataField annotations on class + * @throws IOException + */ + private void writeClass(final String className, final Fields fields) throws IOException { String packageName = null; - int lastDot = className.lastIndexOf('.'); + final int lastDot = className.lastIndexOf('.'); if (lastDot > 0) { packageName = className.substring(0, lastDot); } - String interfaceName = className.substring(lastDot + 1); - String generatedClassName = className + "Implementation"; - String generatedSimpleClassName = generatedClassName.substring(lastDot + 1); + final String interfaceName = className.substring(lastDot + 1); + final String generatedClassName = className + "Implementation"; + final String generatedSimpleClassName = generatedClassName.substring(lastDot + 1); - Properties p = new Properties(); + final Properties p = new Properties(); p.setProperty("resource.loader", "class"); p.setProperty( - "class.resource.loader.class", - "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader" + "class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader" ); Velocity.init(p); - VelocityContext context = new VelocityContext(); + final VelocityContext context = new VelocityContext(); context.put("package", packageName); context.put("interface", interfaceName); context.put("class", generatedSimpleClassName); context.put("fields", fields.getFields()); - JavaFileObject generatedClassFile = processingEnv.getFiler().createSourceFile(generatedClassName); + final JavaFileObject generatedClassFile = processingEnv.getFiler().createSourceFile(generatedClassName); try (Writer writer = generatedClassFile.openWriter()) { Velocity.mergeTemplate(template, "UTF-8", context, writer); } } - - @Override - public boolean process(Set annotations, RoundEnvironment roundEnv) { - final Elements elementUtils = this.processingEnv.getElementUtils(); - FieldVisitor fieldVisitor = new FieldVisitor(elementUtils); - - for (TypeElement annotation : annotations) { - for (Element elem : roundEnv.getElementsAnnotatedWith(annotation)) { - if (!elem.getKind().isInterface()) { - messager.printMessage(Diagnostic.Kind.ERROR, "DataElement annotation is only for interfaces"); - return false; - } - Fields fields = new Fields(); - List annotationMirrors = elem.getAnnotationMirrors(); - for (AnnotationMirror annotationMirror : annotationMirrors) { - final Map elementValues = - elementUtils.getElementValuesWithDefaults(annotationMirror); - for (Map.Entry entry - : elementValues.entrySet()) { - entry.getValue().accept(fieldVisitor, fields); - } - } - try { - this.writeClass(((TypeElement) elem).getQualifiedName().toString(), fields); - } catch (IOException e) { - messager.printMessage(Diagnostic.Kind.ERROR, e.getStackTrace().toString()); - return false; - } - } - } - return true; - } }