Unverified Commit 7e29d719 authored by Jay Jay Billings's avatar Jay Jay Billings Committed by GitHub
Browse files

Merge pull request #423 from dbluhm/spec-classes

Revamp DataElement Annotations: Redefine entry point
parents 298e97c4 9fd4bb47
package org.eclipse.ice.dev.annotations;
public @interface BuilderProperty {
}
......@@ -5,8 +5,49 @@ import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.RetentionPolicy;
/**
* Mark a class as a DataElement Specification.
*
* Classes marked as {@code @DataElement} are expected to contain Fields marked
* as {@code @DataField}. These specifications will then be expanded to an
* interface and implementation of a DataElement derived class. For example:
*
* <pre>
* {@literal @DataElement}(name = "Person")
* public class PersonSpec {
* {@literal @DataField} private int age;
* {@literal @DataField} private String firstName;
* {@literal @DataField} private String lastName;
* }
* </pre>
*
* Will generate an interface like the following:
*
* <pre>
* public interface Person extends IDataElement{@code<Person>} {
* public int getAge();
* public void setAge(int age);
* public String getFirstName();
* public void setFirstName(String firstName);
* public String getLastName();
* public void setLastName(String lastName);
* }
* </pre>
*
* And an associated implementing class (in this case, the class would be called
* {@code PersonImplementation}) that fulfills that interface.
*
*
* @see org.eclipse.ice.dev.annotations.IDataElement
* @see org.eclipse.ice.dev.annotations.DataField
* @author Daniel Bluhm
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface DataElement {
/**
* Name of the DataElement to generate
* @return name annotation value
*/
String name();
}
package org.eclipse.ice.dev.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
/**
* Mark a Field as a DataField.
*
* @see org.eclipse.ice.dev.annotations.DataElement
* @see org.eclipse.ice.dev.annotations.DataField.Default
* @author Daniel Bluhm
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
@Repeatable(DataFields.class)
public @interface DataField {
String fieldName();
Class<?> fieldType() default String.class;
String docString() default "";
}
/**
* Flag whether this field should have an associated getter
* generated.
* @return getter annotation value
* @see org.eclipse.ice.dev.annotations.processors.Field#getter
*/
boolean getter() default true;
/**
* Flag whether this field should have an associated setter
* generated.
* @return setter annotation value
* @see org.eclipse.ice.dev.annotations.processors.Field#setter
*/
boolean setter() default true;
/**
* Flag whether this field should be included in
* {@link org.eclipse.ice.dev.annotations.IDataElement#matches(Object)}.
* @return match annotation value
* @see org.eclipse.ice.dev.annotations.processors.Field#match
*/
boolean match() default true;
/**
* Flag whether this field is considered unique in a collection.
* This causes persistence retrieval methods to return a single value
* rather than an iterable of values.
* @return unique annotation value
* @see org.eclipse.ice.dev.annotations.processors.Field#unique
*/
boolean unique() default false;
/**
* Flag whether this field should be searchable in a collection.
* This causes persistence retrieval methods to be generated for this
* field.
* @return search annotation value
* @see org.eclipse.ice.dev.annotations.processors.Field#search
*/
boolean search() default true;
/**
* Flag whether this field can have a value of null. This causes
* this field to be annotated with {@code @NonNull} from Lombok. Defaults
* to false.
* @return nullable annotation value
* @see org.eclipse.ice.dev.annotations.processors.Field#nullable
*/
boolean nullable() default false;
/**
* Set a default value for this DataField.
*
* Unfortunately, the default values of class fields cannot be retrieved during
* annotation processing unless they are compiled constants and the field is set
* as final.
*
* To accommodate setting a default value, this annotation takes a String which
* is placed verbatim in the generated implementation class as the default value
* for that field. As a result of this, default values of type String must
* include quotation marks in the String itself along with double escaping
* escaped values. To assist in correctly formatting strings, {@code isString}
* is provided as a flag for {@code @DataField.Default} that will cause the
* string to be passed through
* {@link javax.lang.model.util.Elements#getConstantExpression(Object)} and properly
* escape the output string.
*
* This annotation has no effect if the {@code @DataField} annotation is not
* present.
*
*
* @see org.eclipse.ice.dev.annotations.DataElement
* @see org.eclipse.ice.dev.annotations.DataField
* @author Daniel Bluhm
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Default {
/**
* The default value of this field represented as a String.
* @return defaultValue annotation value
*/
String value();
/**
* Flag whether the value is itself intended to be a String and
* should be escaped.
* @return isString annotation value
*/
boolean isString() default false;
}
}
\ No newline at end of file
package org.eclipse.ice.dev.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface DataFields {
DataField[] value();
}
......@@ -6,7 +6,7 @@ import java.util.UUID;
/**
* Marker interface for DataElements.
*/
public interface IDataElement {
public interface IDataElement<T> {
/**
* Get the public identifier of the data element. This is a common id that may
......@@ -36,7 +36,7 @@ public interface IDataElement {
/**
* Set the simple name of the element
*
* @param elemName a simple name
* @param name a simple name
* @throws Exception An exception is thrown if the value is null, which is
* unallowable.
*/
......@@ -149,6 +149,14 @@ public interface IDataElement {
*/
public String toString();
/**
* This operation clones the object. Note that it differs from the base class
* implementation in that it will return null if it cannot create the clone to
* promote fast failure. See {@link java.lang.Object#clone()};
* @return the cloned object
*/
public Object clone();
/**
* This function checks deep equality of DataElements to see if all members are
* equal ("match") with the exception of fields with match set to false (such
......@@ -173,15 +181,34 @@ public interface IDataElement {
* object.
*
* @param jsonDataElement the contents of this data element as JSON
* @return the deserialized DataElement
*/
public <T extends IDataElement> T fromJSON(final String jsonDataElement);
public T fromJSON(final String jsonDataElement);
/**
* Load from a String-Object Map, skipping the String parsing step. Structures
* such as <code>org.bson.Document</code> implement Map<String, Object> and
* such as {@link org.bson.Document} implement {@code Map<String, Object>} and
* therefore do not need to be processed from raw String form.
*
* @param jsonDataElement the contents of this data element as a Map<String, Object>
* @param <S> Object extending {@code Map<String, Object>}
* @param jsonDataElement the contents of this data element as a
* {@code Map<String, Object>}
* @return the deserialized DataElement
*/
public <S extends Map<String, Object>> T fromJSON(final S jsonDataElement);
/**
* This operation returns the validator that has been configured for this
* element.
*
* @return the validator or null if it has not been set
*/
public JavascriptValidator<T> getValidator();
/**
* This operation sets the validator associated with this element.
*
* @param validator the validator or null to reset the reference
*/
public <T extends Map<String, Object>, S extends IDataElement> S fromJSON(final T jsonDataElement);
public void setValidator(JavascriptValidator<T> validator);
}
......@@ -5,80 +5,86 @@ import java.util.UUID;
public interface IPersistenceHandler<T> {
/**
* Save the DataElement.
* @param <T> Object extending IDataElement
* @param element
* @throws Exception
* @param element Object extending {@code IDataElement}
* @throws Exception thrown on failure to save
*/
public void save(T element) throws Exception;
/**
* Clear all DataElements from the collection.
* @return
* @throws Exception
* @return number of deleted elements
* @throws Exception thrown on failure to clear the collection
*/
public long clear() throws Exception;
/**
* Find and retrieve all DataElements from the collection.
* @param <T>
* @return an iterable of the retrieved elements.
* @throws Exception
* @throws Exception thrown on failure to retrieve all elements
*/
public Iterable<T> findAll() throws Exception;
/**
* Find DataElement by UUID.
* @param UUID
* @param uuid to find
* @return found element or null
* @throws Exception thrown on failure to retrieve element
*/
public T findByUUID(UUID uuid) throws Exception;
/**
* Find DataElement by id.
* @param id
* @param id of elements to find
* @return Iterator of results
* @throws Exception thrown on failure to retrieve elements
*/
public Iterable<T> findById(long id) throws Exception;
/**
* Find DataElement by name.
* @param name
* @param name of elements to find
* @return Iterator of results
* @throws Exception thrown on failure to retrieve elements
*/
public Iterable<T> findByName(String name) throws Exception;
/**
* Find DataElement by description.
* @param description
* @param description of elements to find
* @return Iterator of results
* @throws Exception thrown on failure to retrieve elements
*/
public Iterable<T> findByDescription(String description) throws Exception;
/**
* Find DataElement by comment.
* @param comment
* @param comment of elements to find
* @return Iterator of results
* @throws Exception thrown on failure to retrieve elements
*/
public Iterable<T> findByComment(String comment) throws Exception;
/**
* Find DataElement by context.
* @param context
* @param context of elements to find
* @return Iterator of results
* @throws Exception thrown on failure to retrieve elements
*/
public Iterable<T> findByContext(String context) throws Exception;
/**
* Find DataElement by required.
* @param required
* @param required value to match
* @return Iterator of results
* @throws Exception thrown on failure to retrieve elements
*/
public Iterable<T> findByRequired(boolean required) throws Exception;
/**
* Find DataElement by secret.
* @param secret
* @param secret value to match
* @return Iterator of results
* @throws Exception thrown on failure to retrieve elements
*/
public Iterable<T> findBySecret(boolean secret) throws Exception;
}
\ No newline at end of file
}
......@@ -86,6 +86,7 @@ public class JavascriptValidator<T> implements Serializable {
/**
* Copy constructor
* @param otherValidator to copy
*/
public JavascriptValidator(JavascriptValidator<T> otherValidator) {
if (otherValidator != null) {
......@@ -136,6 +137,7 @@ public class JavascriptValidator<T> implements Serializable {
if (this == otherObject) {
retValue = true;
} else if (otherObject instanceof JavascriptValidator<?>) {
@SuppressWarnings("unchecked")
JavascriptValidator<T> otherValidator = (JavascriptValidator<T>) otherObject;
retValue = this.function.equals(otherValidator.function);
}
......
package org.eclipse.ice.dev.annotations.processors;
import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.lang.model.element.Element;
import javax.lang.model.util.Elements;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
/**
* Helper for accessing and working with Annotated Classes.
* @author Daniel Bluhm
*/
public abstract class AnnotatedElement {
/**
* List of all annotation mirrors on this element.
*/
private List<? extends AnnotationMirror> mirrors;
/**
* Elements used to retrieve defaults for annotation values.
*/
protected Elements elementUtils;
/**
* The element representing an interface annotated with
* {@code @DataElement}.
*/
protected Element element;
/**
* Construct an AnnotatedElement from an Element.
* @param element The annotated element
* @param elementUtils Elements helper from processing environment
*/
public AnnotatedElement(Element element, Elements elementUtils) {
this.element = element;
this.elementUtils = elementUtils;
}
/**
* 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
*/
public boolean hasAnnotation(Class<? extends Annotation> cls) {
return this.element.getAnnotation(cls) != null;
}
/**
* 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
*/
public <T extends Annotation> Optional<T> getAnnotation(Class<T> cls) {
T value = this.element.getAnnotation(cls);
if (value == null) {
return Optional.empty();
}
return Optional.of(value);
}
/**
* Get a map of annotation value names to the value identified by that name.
*
* 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.
* @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());
}
/**
* Get a list of annotation values from an annotation mirror of a given type.
*
* 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.
* @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());
}
/**
* Find and return annotation of a given on this element.
* @param annotationClass Class of annotation mirror to retrieve
* @return {@link Optional} of annotation mirror
*/
private Optional<AnnotationMirror> getAnnotationMirror(Class<?> annotationClass) {
if (this.mirrors == null) {
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());
}
}
......@@ -8,10 +8,8 @@ import java.io.Writer;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
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;
......@@ -21,7 +19,6 @@ import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
......@@ -32,9 +29,6 @@ import javax.tools.StandardLocation;
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 org.eclipse.ice.dev.annotations.DataFieldJson;
import org.eclipse.ice.dev.annotations.DataFields;
import org.eclipse.ice.dev.annotations.Persisted;
import com.fasterxml.jackson.databind.ObjectMapper;
......@@ -47,7 +41,12 @@ import com.google.auto.service.AutoService;
* DataElement, populating the implementation with metadata and fields specified
* with the DataField annotation.
*/
@SupportedAnnotationTypes("org.eclipse.ice.dev.annotations.DataElement")
@SupportedAnnotationTypes({
"org.eclipse.ice.dev.annotations.DataElement",
"org.eclipse.ice.dev.annotations.DataField",
"org.eclipse.ice.dev.annotations.DataField.Default",
"org.eclipse.ice.dev.annotations.Persisted"
})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@AutoService(Processor.class)
public class DataElementProcessor extends AbstractProcessor {
......@@ -61,13 +60,21 @@ public class DataElementProcessor extends AbstractProcessor {
private static final String DATAELEMENT_TEMPLATE = "templates/DataElement.vm";
/**
* Location of DataElement template for use with velocity.
* Location of PersistenceHandler template for use with velocity.
*
* Use of Velocity ClasspathResourceLoader means files are discovered relative
* to the src/main/resources folder.
*/
private static final String PERSISTENCE_HANDLER_TEMPLATE = "templates/PersistenceHandler.vm";
/**
* Location of Interface template for use with velocity.
*
* Use of Velocity ClasspathResourceLoader means files are discovered relative
* to the src/main/resources folder.
*/
private static final String INTERFACE_TEMPLATE = "templates/ElementInterface.vm";