Commit 33bfd9b1 authored by Daniel Bluhm's avatar Daniel Bluhm

Restructure WriterGenerator creation

Signed-off-by: Daniel Bluhm's avatarDaniel Bluhm <bluhmdj@ornl.gov>
parent a3444899
......@@ -18,7 +18,7 @@ import javax.lang.model.element.AnnotationValue;
*
* @author Daniel Bluhm
*/
public abstract class AnnotatedElement {
public class AnnotatedElement {
/**
* List of all annotation mirrors on this element.
*/
......
package org.eclipse.ice.dev.annotations.processors;
import java.util.Optional;
import javax.lang.model.element.Element;
import org.slf4j.Logger;
/**
* Interface for classes acting as extractors of annotation info.
* @author Daniel Bluhm
......@@ -17,6 +21,33 @@ public interface AnnotationExtractor<T> {
* implementation of the extractor.
* @param element from which information will be extracted.
* @return extracted information
* @throws InvalidElementException if element is not annotated as expected
* for this annotation extractor.
*/
public T extract(Element element) throws InvalidElementException;
/**
* Get handle to logger.
* @return logger.
*/
public Logger log();
/**
* Extract information from element and annotations found on or within
* element if possible, return empty otherwise.
* @param element from which information will be extracted.
* @return extracted information wrapped in optional or empty.
*/
public T extract(Element element);
public default Optional<T> extractIfApplies(Element element) {
Optional<T> value = Optional.empty();
try {
value = Optional.of(extract(element));
} catch (InvalidElementException e) {
log().debug(
"Failed to extract metadata from annotation, returning empty:",
e
);
}
return value;
}
}
\ No newline at end of file
......@@ -14,13 +14,20 @@ import java.io.IOException;
import java.io.Writer;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.processing.Filer;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import org.eclipse.ice.dev.annotations.DataElement;
import org.eclipse.ice.dev.annotations.DataField;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import lombok.Builder;
......@@ -30,7 +37,9 @@ import lombok.Builder;
*
* @author Michael Walsh
*/
public class DataElementAnnotationExtractor {
public class DataElementAnnotationExtractor
implements AnnotationExtractor<DataElementMetadata>
{
/**
* Annotations to not be transfered from member variables of Spec classes to
......@@ -46,20 +55,19 @@ public class DataElementAnnotationExtractor {
);
/**
* used for extracting and preparing data for writer generation
* Logger.
*/
private ICEAnnotationExtractionService annotationExtractionService;
private static final Logger logger = LoggerFactory.getLogger(DataElementAnnotationExtractor.class);
/**
* Filer used for generating files.
* Element utilities from annotation processing environment.
*/
private Filer filer;
private Elements elementUtils;
/**
* used to generate writers based on the output of the annotation extraction
* service
* Extractor for data fields found on data element.
*/
private WriterGenerator<AnnotationExtractionResponse> writerGenerator;
private DataFieldExtractor dataFieldExtractor;
/**
* Constructor that lets you initialize the {@link DataElementAnnotationExtractor} with different
......@@ -68,58 +76,87 @@ public class DataElementAnnotationExtractor {
* @param writerGenerator
*/
@Builder
DataElementAnnotationExtractor(
ICEAnnotationExtractionService annotationExtractionService,
Filer filer,
WriterGenerator<AnnotationExtractionResponse> writerGenerator
public DataElementAnnotationExtractor(
Elements elementUtils,
DataFieldExtractor dataFieldExtractor
) {
this.annotationExtractionService = annotationExtractionService;
this.filer = filer;
this.writerGenerator = writerGenerator;
this.annotationExtractionService.setNonTransferableAnnotations(nonTransferableAnnotations);
this.annotationExtractionService.setFieldFilter(DataElementAnnotationExtractor::isDataField);
this.elementUtils = elementUtils;
this.dataFieldExtractor = dataFieldExtractor;
}
@Override
public Logger log() {
return logger;
}
@Override
public DataElementMetadata extract(Element element) throws InvalidElementException {
if (element.getKind() != ElementKind.CLASS) {
throw new InvalidElementException(
"Element must be class, found " + element.toString()
);
}
AnnotatedElement helper = new AnnotatedElement(element, elementUtils);
if (!helper.hasAnnotation(DataElement.class)) {
throw new InvalidElementException(
"Element is not annotated with DataElement"
);
}
Fields fields = new Fields();
fields.collect(DefaultFields.get());
fields.collect(extractFields(element));
return DataElementMetadata.builder()
.name(extractName(helper))
.packageName(extractPackageName(element))
.fields(fields)
.build();
}
/**
* 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)}
* Extract name from DataElement annotation.
* @param element from which name will be extracted.
* @return String or null if DataElement annotation is missing.
*/
public List<GeneratedFileWriter> generateWriters(
AnnotationExtractionRequest request
) throws IOException {
AnnotationExtractionResponse response = annotationExtractionService.extract(request);
return writerGenerator.generate(response);
private String extractName(AnnotatedElement element) {
return element.getAnnotation(DataElement.class)
.map(DataElement::name)
.orElse(null);
}
/**
* For a given request it will generate then execute writers
*
* @param request
* @throws IOException
* Determine package name from element.
* @param element from which package name will be determined.
* @return String or null if element is not in a package.
*/
public void generateAndWrite(AnnotationExtractionRequest request) throws IOException {
generateWriters(request)
.forEach(writer -> {
try (Writer file = writer.openWriter(filer)) {
writer.write(file);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
});
private String extractPackageName(Element element) {
String elementFQN = ((TypeElement) element).getQualifiedName().toString();
String packageName = null;
final int lastDot = elementFQN.lastIndexOf('.');
if (lastDot > 0) {
packageName = elementFQN.substring(0, lastDot);
}
return packageName;
}
/**
* Determine if the passed field is a DataField.
*
* @param element to check
* @return whether element is a DataField
* Use DataFieldExtractor to extract DataField annotated elements contained
* in this DataElement.
* @param element containing DataFields.
* @return extracted Fields.
*/
public static boolean isDataField(Element element) {
return element.getAnnotation(DataField.class) != null;
private List<Field> extractFields(Element element) {
return element.getEnclosedElements().stream()
.filter(child -> child.getAnnotation(DataField.class) != null)
.map(dataField -> {
try {
return dataFieldExtractor.extract(dataField);
} catch (InvalidElementException e) {
logger.warn("Invalid element encountered while extracting DataField", e);
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
}
package org.eclipse.ice.dev.annotations.processors;
import lombok.Data;
import lombok.experimental.SuperBuilder;
/**
* POJO representing metadata extracted from DataElement and associated
* annotations.
*
* @author Daniel Bluhm
*/
@Data
@SuperBuilder
public class DataElementMetadata {
/**
* Base name of classes to be generated.
*/
protected String name;
/**
* Package of classes to be generated.
*/
protected String packageName;
/**
* Collected fields of the DataElement.
*/
protected Fields fields;
/**
* Fully qualified name (package + name) of the DataElement.
* @return fully qualified name.
*/
public String getFullyQualifiedName() {
return String.format("%s.%s", this.packageName, this.name);
}
}
......@@ -14,6 +14,8 @@ 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.Optional;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
......@@ -96,20 +98,12 @@ public class DataElementProcessor extends AbstractProcessor {
@Override
public synchronized void init(final ProcessingEnvironment env) {
messager = env.getMessager();
elementUtils = env.getElementUtils();
mapper = new ObjectMapper();
ICEAnnotationExtractionService extractionService =
new ICEAnnotationExtractionService(
elementUtils, mapper, env,
new DefaultNameGenerator()
);
this.messager = env.getMessager();
this.elementUtils = env.getElementUtils();
this.mapper = new ObjectMapper();
this.extractor = DataElementAnnotationExtractor.builder()
.annotationExtractionService(extractionService)
.filer(env.getFiler())
.writerGenerator(new DataElementWriterGenerator())
.elementUtils(elementUtils)
.dataFieldExtractor(new DataFieldExtractor(elementUtils))
.build();
super.init(env);
}
......@@ -119,37 +113,24 @@ public class DataElementProcessor extends AbstractProcessor {
// Iterate over all elements with DataElement Annotation
for (final Element elem : roundEnv.getElementsAnnotatedWith(DataElement.class)) {
try {
if (!valid(elem))
throw new InvalidDataElementSpec("DataElementSpec must be class, found " + elem.toString());
AnnotationExtractionRequest request = AnnotationExtractionRequest.builder().element(elem)
.className(extractName(elem)).build();
extractor.generateAndWrite(request);
} catch (final IOException | InvalidDataElementSpec e) {
DataElementMetadata data = this.extractor.extract(elem);
Optional<PersistenceMetadata> persistence = Optional.empty();
Set<WriterGenerator> generators = WriterGeneratorFactory.create(
data,
persistence
);
for (WriterGenerator generator : generators) {
for (GeneratedFileWriter fileWriter : generator.generate()) {
try (Writer writer = fileWriter.openWriter(processingEnv.getFiler())) {
fileWriter.write(writer);
}
}
}
} catch (final IOException | InvalidElementException e) {
messager.printMessage(Diagnostic.Kind.ERROR, stackTraceToString(e));
return false;
}
}
return false;
}
/**
* Return the element name as extracted from the DataElement annotation.
*
* @return the extracted name
*/
private String extractName(Element element) {
return specExtractionHelper.getAnnotation(element, DataElement.class).map(DataElement::name).orElse(null);
}
/**
* Determine if a given annotated element is a valid class for transformation
*
* @param element
* @return
*/
private boolean valid(Element element) {
return element.getKind() == ElementKind.CLASS;
}
}
......@@ -7,36 +7,64 @@
*
* Contributors:
* Michael Walsh - Initial implementation
* Daniel Bluhm - Modifications
*******************************************************************************/
package org.eclipse.ice.dev.annotations.processors;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import lombok.AllArgsConstructor;
/**
* Uses metadata extracted from spec classes annotated with @DataElement to
* generate the interface, implementation, and persistence handler.
*
*/
public class DataElementWriterGenerator
implements WriterGenerator<AnnotationExtractionResponse>
{
@AllArgsConstructor
public class DataElementWriterGenerator implements WriterGenerator {
/**
* Logger.
*/
private static final Logger logger = LoggerFactory.getLogger(DataElementWriterGenerator.class);
/**
* Data from which FileWriters are generated.
*/
private DataElementMetadata data;
@Override
public List<GeneratedFileWriter> generate(AnnotationExtractionResponse response) {
public List<GeneratedFileWriter> generate() {
List<GeneratedFileWriter> writers = new ArrayList<>();
writers.add(InterfaceWriter.fromContext(response.getClassMetadata()));
writers.add(ImplementationWriter.fromContext(response.getClassMetadata()));
writers.add(TypeScriptWriter.fromContext(response.getClassMetadata()));
// TODO This check should be more graceful or happen elsewhere
if (response.getClassMetadata().get(PersistenceHandlerTemplateProperty.COLLECTION) != null) {
writers.add(PersistenceHandlerWriter.fromContext(response.getClassMetadata()));
Fields nonDefaults = data.getFields().getNonDefaultFields();
writers.add(InterfaceWriter.builder()
.packageName(data.getPackageName())
.interfaceName(data.getName())
.fields(nonDefaults)
.types(nonDefaults.getTypes())
.build());
writers.add(ImplementationWriter.builder()
.packageName(data.getPackageName())
.interfaceName(data.getName())
.className(data.getName() + "Implementation")
.fields(data.getFields())
.types(data.getFields().getTypes())
.build());
try {
writers.add(TypeScriptWriter.builder()
.name(data.getName())
.fields(nonDefaults)
.types(nonDefaults.getTypes())
.build());
} catch (UnsupportedOperationException e) {
logger.warn("Failed to create typescript writer for element:", e);
}
return writers
.stream()
.filter(Objects::nonNull)
.collect(Collectors.toList());
return writers;
}
}
package org.eclipse.ice.dev.annotations.processors;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import org.eclipse.ice.dev.annotations.DataField;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DataFieldExtractor implements AnnotationExtractor<Field> {
/**
* Logger.
*/
private static final Logger logger = LoggerFactory.getLogger(DataFieldExtractor.class);
/**
* Element helper from annotation processing environment.
*/
private Elements elementUtils;
public DataFieldExtractor(Elements elementUtils) {
this.elementUtils = elementUtils;
}
/**
* Set of Annotation names that extractAnnotations should filter out.
*/
private static final Set<String> ANNOTATION_CLASS_NAMES = Set.of(
DataField.class,
DataField.Default.class
).stream()
.map(Class::getCanonicalName)
.collect(Collectors.toSet());
@Override
public Logger log() {
return logger;
}
@Override
public Field extract(Element element) throws InvalidElementException {
DataField fieldInfo = element.getAnnotation(DataField.class);
return Field.builder()
.name(extractFieldName(element))
.type(extractFieldType(element))
.defaultValue(extractDefaultValue(element))
.docString(extractDocComment(element))
.annotations(extractAnnotations(element))
.modifiersToString(extractModifiers(element))
.getter(fieldInfo.getter())
.setter(fieldInfo.setter())
.match(fieldInfo.match())
.unique(fieldInfo.unique())
.searchable(fieldInfo.searchable())
.nullable(fieldInfo.nullable())
.build();
}
/**
* Return the set of access modifiers on this Field.
* @param element from which modifiers are extracted.
* @return extract field modifiers
* @see Modifier
*/
private Set<Modifier> extractModifiers(Element element) {
return element.getModifiers();
}
/**
* Return the set of annotations on this DataField, excepting the DataField
* Annotation itself.
* @param element from which annotations are extracted.
* @return extracted annotations, excluding DataField related annotations
*/
private List<String> extractAnnotations(Element element) {
return element.getAnnotationMirrors().stream()
.filter(mirror -> !ANNOTATION_CLASS_NAMES.contains(
mirror.getAnnotationType().toString()
))
.map(AnnotationMirror::toString)
.collect(Collectors.toList());