Commit 5ef31b33 authored by Daniel Bluhm's avatar Daniel Bluhm

Merge branch 'walshmm/feature/ice-annotation-extraction-service' into 'annotation-extraction'

Walshmm/feature/ice annotation extraction service

See merge request rse-ice/ice!2
parents 2f835787 006611ce
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<classpathentry kind="src" output="target/classes"
path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="optional" value="true" />
<attribute name="maven.pomderived" value="true" />
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<classpathentry excluding="**" kind="src"
output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
<attribute name="maven.pomderived" value="true" />
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<classpathentry kind="src" output="target/test-classes"
path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
<attribute name="optional" value="true" />
<attribute name="maven.pomderived" value="true" />
<attribute name="test" value="true" />
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
<classpathentry excluding="**" kind="src"
output="target/test-classes" path="src/test/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
<attribute name="maven.pomderived" value="true" />
<attribute name="test" value="true" />
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<classpathentry kind="con"
path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true"/>
<attribute name="maven.pomderived" value="true" />
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<classpathentry kind="con"
path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
<attribute name="maven.pomderived" value="true" />
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/5"/>
<classpathentry kind="src" path="target/generated-sources/annotations">
<classpathentry kind="con"
path="org.eclipse.jdt.junit.JUNIT_CONTAINER/5" />
<classpathentry kind="src"
path="target/generated-sources/annotations">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="ignore_optional_problems" value="true"/>
<attribute name="m2e-apt" value="true"/>
<attribute name="optional" value="true" />
<attribute name="maven.pomderived" value="true" />
<attribute name="ignore_optional_problems" value="true" />
<attribute name="m2e-apt" value="true" />
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="target/generated-test-sources/test-annotations">
<classpathentry kind="src" output="target/test-classes"
path="target/generated-test-sources/test-annotations">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="ignore_optional_problems" value="true"/>
<attribute name="m2e-apt" value="true"/>
<attribute name="test" value="true"/>
<attribute name="optional" value="true" />
<attribute name="maven.pomderived" value="true" />
<attribute name="ignore_optional_problems" value="true" />
<attribute name="m2e-apt" value="true" />
<attribute name="test" value="true" />
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
<classpathentry kind="output" path="target/classes" />
</classpath>
......@@ -44,6 +44,11 @@
<!-- Apache Velocity Template Engine -->
<dependencies>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
......
......@@ -12,6 +12,13 @@ import com.mongodb.client.model.Filters;
* @author Daniel Bluhm
*/
public class PersistenceFilters {
/*
* Explicit private constructor to hide implicit public constructor, otherwise this would be a code smell
*/
private PersistenceFilters() {
}
/**
* Create a filter for a UUID.
*
......
/*******************************************************************************
* Copyright (c) 2020- UT-Battelle, LLC.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Michael Walsh - Initial implementation
*******************************************************************************/
package org.eclipse.ice.dev.annotations.processors;
import java.io.IOException;
import javax.annotation.processing.ProcessingEnvironment;
import javax.tools.JavaFileObject;
/**
* Abstract class for service classes that generate
* {@link org.eclipse.ice.dev.annotations.processors.VelocitySourceWriter}s
*
* @author Michael Walsh
*/
public abstract class AbstractWriterGenerator implements WriterGenerator {
/**
* Used to create JavaFileObjects
*/
protected ProcessingEnvironment processingEnv;
/**
* Constructor
*
* @param processingEnv necessary to write source files with
*/
AbstractWriterGenerator(ProcessingEnvironment processingEnv) {
this.processingEnv = processingEnv;
}
/**
* Generates object used for writing templated class to
*
* @param name of file
* @return JavaFileObject used to write generated class file to
* @throws IOException file writing
*/
public JavaFileObject createFileObjectForName(String name) throws IOException {
return processingEnv.getFiler().createSourceFile(name);
}
}
......@@ -15,6 +15,7 @@ import javax.lang.model.element.AnnotationValue;
/**
* Helper for accessing and working with Annotated Classes.
*
* @author Daniel Bluhm
*/
public abstract class AnnotatedElement {
......@@ -29,14 +30,14 @@ public abstract class AnnotatedElement {
protected Elements elementUtils;
/**
* The element representing an interface annotated with
* {@code @DataElement}.
* The element representing an interface annotated with {@code @DataElement}.
*/
protected Element element;
/**
* Construct an AnnotatedElement from an Element.
* @param element The annotated element
*
* @param element The annotated element
* @param elementUtils Elements helper from processing environment
*/
public AnnotatedElement(Element element, Elements elementUtils) {
......@@ -46,6 +47,7 @@ public abstract class AnnotatedElement {
/**
* Determine if an annotation of a given type decorates this element.
*
* @param cls class of annotation to check
* @return whether annotation is present or not
*/
......@@ -55,6 +57,7 @@ public abstract class AnnotatedElement {
/**
* Get the AnnotationMirror of a given type if present on the element.
*
* @param <T> Type of annotation to retrieve
* @param cls class of annotation to retrieve
* @return AnnotationMirror or null if not found
......@@ -73,19 +76,17 @@ public abstract class AnnotatedElement {
* This is useful when dealing with a complicated Annotation potentially
* containing a value that is a Class object. Otherwise, it is recommended to
* directly retrieve the value from an Annotation instance.
* @param annotationClass the class of the annotation from which values
* will be retrieved.
*
* @param annotationClass the class of the annotation from which values will be
* retrieved.
* @return Map of String to unwrapped AnnotationValue (Object)
*/
public Map<String, Object> getAnnotationValueMap(Class<?> annotationClass) {
return this.getAnnotationMirror(annotationClass)
.map(mirror -> elementUtils.getElementValuesWithDefaults(mirror))
.map(map -> map.entrySet().stream()
.collect(Collectors.toMap(
entry -> entry.getKey().getSimpleName().toString(),
entry -> entry.getValue().getValue()
))
).orElse(Collections.emptyMap());
.map(mirror -> elementUtils.getElementValuesWithDefaults(mirror))
.map(map -> map.entrySet().stream().collect(Collectors.toMap(
entry -> entry.getKey().getSimpleName().toString(), entry -> entry.getValue().getValue())))
.orElse(Collections.emptyMap());
}
/**
......@@ -94,21 +95,21 @@ public abstract class AnnotatedElement {
* This is useful when dealing with a complicated Annotation potentially
* containing a value that is a Class object. Otherwise, it is recommended to
* directly retrieve the value from an Annotation instance.
*
* @param annotationClass the class of the annotation from which values will be
* retrieved.
* retrieved.
* @return list of AnnotationValue
*/
public List<AnnotationValue> getAnnotationValues(Class<?> annotationClass) {
return this.getAnnotationMirror(annotationClass)
.map(mirror -> elementUtils.getElementValuesWithDefaults(mirror))
.map(map -> map.entrySet().stream()
.map(entry -> (AnnotationValue) entry.getValue())
.collect(Collectors.toList())
).orElse(Collections.emptyList());
.map(mirror -> elementUtils.getElementValuesWithDefaults(mirror)).map(map -> map.entrySet().stream()
.map(entry -> (AnnotationValue) entry.getValue()).collect(Collectors.toList()))
.orElse(Collections.emptyList());
}
/**
* Find and return annotation of a given on this element.
*
* @param annotationClass Class of annotation mirror to retrieve
* @return {@link Optional} of annotation mirror
*/
......@@ -117,10 +118,7 @@ public abstract class AnnotatedElement {
this.mirrors = this.element.getAnnotationMirrors();
}
return this.mirrors.stream()
.filter(m -> m.getAnnotationType()
.toString().equals(annotationClass.getCanonicalName())
).findAny()
.map(m -> Optional.of((AnnotationMirror) m))
.orElse(Optional.empty());
.filter(m -> m.getAnnotationType().toString().equals(annotationClass.getCanonicalName())).findAny()
.map(m -> Optional.of((AnnotationMirror) m)).orElse(Optional.empty());
}
}
/*******************************************************************************
* Copyright (c) 2020- UT-Battelle, LLC.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Michael Walsh - Initial implementation
*******************************************************************************/
package org.eclipse.ice.dev.annotations.processors;
import javax.lang.model.element.Element;
import lombok.Builder;
import lombok.Data;
/**
* Input POJO for the flavors of the ICEAnnotationExtractionService
*
* @author Michael Walsh
*/
@Data
@Builder
public class AnnotationExtractionRequest {
/**
* Element to be extracted from
*/
private Element element;
/**
* Included list of static default fields in addition to fields specified in
* element
*/
@Builder.Default
private boolean includeDefaults = true;
/**
* Base name for the generated classes
*/
private String className;
}
/*******************************************************************************
* Copyright (c) 2020- UT-Battelle, LLC.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Michael Walsh - Initial implementation
*******************************************************************************/
package org.eclipse.ice.dev.annotations.processors;
import java.util.Map;
import lombok.Builder;
import lombok.Data;
/**
* Typical response from the flavors of ICEAnnotationExtractionService
*
* @author Michael Walsh
*/
@Data
@Builder
public class AnnotationExtractionResponse {
/**
* Pojo containing metadata about fields to be included in Velocity generated
* classes
*/
private Fields fields;
/**
* Map containing metadata surrounding the class types to be generated, e.g.
* package name
*/
private Map<TemplateProperty, Object> classMetadata;
}
/*******************************************************************************
* Copyright (c) 2020- UT-Battelle, LLC.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Michael Walsh - Initial implementation
*******************************************************************************/
package org.eclipse.ice.dev.annotations.processors;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.lang.model.element.Element;
import org.eclipse.ice.dev.annotations.DataField;
/**
* Flavor of ICEAnnotationExtractionService that specializes in extracting data
* from Spec classes with the class level annotation of {@link DataElement}
*
* @author Michael Walsh
*/
public class DataElementAnnotationExtractor {
/**
* Annotations to not be transfered from member variables of Spec classes to
* final generated classes
*/
private static final List<String> nonTransferableAnnotations = Stream.of(DataField.class, DataField.Default.class)
.map(Class::getCanonicalName)
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
/**
* used for extracting and preparing data for writer generation
*/
private ICEAnnotationExtractionService annotationExtractionService;
/**
* used to generate writers based on the output of the annotation extraction
* service
*/
private WriterGenerator writerGenerator;
/**
* Constructor that lets you initialize the {@link DataElementAnnotationExtractor} with different
* implementations of {@link ICEAnnotationExtractionService} and {@link WriterGenerator}
* @param annotationExtractionService
* @param writerGenerator
*/
DataElementAnnotationExtractor(ICEAnnotationExtractionService annotationExtractionService,
WriterGenerator writerGenerator) {
this.annotationExtractionService = annotationExtractionService;
this.writerGenerator = writerGenerator;
this.annotationExtractionService.setNonTransferableAnnotations(nonTransferableAnnotations);
this.annotationExtractionService.setFieldFilter(DataElementAnnotationExtractor::isDataField);
}
/**
* For a given request it will extract data from client classes
* and generate a list of {@link VelocitySourceWriter}
*
* @param request
* @return list of generated SourceWriters
* @throws IOException due to {@link ICEAnnotationExtractionService#extract(AnnotationExtractionRequest)}
*/
public List<VelocitySourceWriter> generateWriters(AnnotationExtractionRequest request) throws IOException {
AnnotationExtractionResponse response = annotationExtractionService.extract(request);
List<VelocitySourceWriter> writerList = writerGenerator.generateWriters(request.getElement(),response);
return writerList;
}
/**
* For a given request it will generate then execute writers
*
* @param request
* @throws IOException
*/
public void generateAndWrite(AnnotationExtractionRequest request) throws IOException {
this.generateWriters(request).forEach(writer -> {
try {
writer.write();
} catch (IOException e) {
e.printStackTrace();
}
});
}
/**
* Determine if the passed field is a DataField.
*
* @param element to check
* @return whether element is a DataField
*/
public static boolean isDataField(Element element) {
return element.getAnnotation(DataField.class) != null;
}
}
/*******************************************************************************
* Copyright (c) 2020- UT-Battelle, LLC.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Michael Walsh - Initial implementation
*******************************************************************************/
package org.eclipse.ice.dev.annotations.processors;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import javax.tools.JavaFileObject;
import lombok.Builder;
/**
* Implementation of the VelocitySourceWriter that handles generating the
* DataElementImplementation
*
* @author Michael Walsh
*/
public class DataElementImplementationWriter extends ImplementationWriter {
/**
* Location of DataElement template for use with velocity.
*
* Use of Velocity ClasspathResourceLoader means files are discovered relative
* to the src/main/resources folder.
*/
private static final String IMPL_TEMPLATE = "templates/DataElement.vm";
/**
* Constructor
*
* @param packageName
* @param interfaceName
* @param className
* @param fields
* @param generatedFile
*/
@Builder
public DataElementImplementationWriter(String packageName, String interfaceName, String className, Fields fields,
Types types, JavaFileObject generatedFile) {
super(packageName, interfaceName, className, fields, types, generatedFile);
this.template = IMPL_TEMPLATE;
}
/**
* Private argless constructor purely for use of the static method to have
* access to the inherited getInitializer() method
*/
private DataElementImplementationWriter() {
super();
}
/**
* Supplies a lambda that will provide a fully initialized
* DataElementImplementationWriter given a map and a JavaFileObject
*/
@Override
public BiFunction<JavaFileObject, Map, List<VelocitySourceWriter>> getInitializer() {
return (fileObject, context) ->
Arrays.asList(DataElementImplementationWriter.builder()
.packageName((String) context.get(MetaTemplateProperty.PACKAGE))