diff --git a/org.eclipse.ice.dev.annotations/.classpath b/org.eclipse.ice.dev.annotations/.classpath index 6e0021feb..90dbf04f5 100644 --- a/org.eclipse.ice.dev.annotations/.classpath +++ b/org.eclipse.ice.dev.annotations/.classpath @@ -37,17 +37,11 @@ - - - - - - diff --git a/org.eclipse.ice.dev.annotations/pom.xml b/org.eclipse.ice.dev.annotations/pom.xml index bf6305502..a0bb33669 100644 --- a/org.eclipse.ice.dev.annotations/pom.xml +++ b/org.eclipse.ice.dev.annotations/pom.xml @@ -18,8 +18,7 @@ maven-compiler-plugin 3.8.1 - 1.8 - 1.8 + 11 com.google.auto.service diff --git a/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/DataField.java b/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/DataField.java index 6d004adbd..745ee6d88 100644 --- a/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/DataField.java +++ b/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/DataField.java @@ -12,4 +12,5 @@ public @interface DataField { String fieldName(); Class fieldType() default String.class; + String docString() default ""; } diff --git a/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/IDataElement.java b/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/IDataElement.java index b7e0a98bd..3a9d4a269 100644 --- a/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/IDataElement.java +++ b/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/IDataElement.java @@ -1,9 +1,153 @@ package org.eclipse.ice.dev.annotations; +import java.util.UUID; + /** * Marker interface for DataElements. */ public interface IDataElement { + + /** + * Get the public identifier of the data element. This is a common id that may + * or may not be unique to this data element. + * + * @return the public id + */ + public long getId(); + + /** + * Set the public identifier of the data element. This is a common id that may + * or may not be unique to this data element. + * + * @param public_id the public id + * @throws Exception An exception is thrown if the value is null, which is + * unallowable. + */ + public void setId(final long public_id) throws Exception; + + /** + * This operation returns the simple name of the data + * + * @return the name of the data, i.e. - "CORD-19" or "Steve" + */ + public String getName(); + + /** + * Set the simple name of the element + * + * @param elemName a simple name + * @throws Exception An exception is thrown if the value is null, which is + * unallowable. + */ + public void setName(final String name) throws Exception; + + /** + * Get the simple description of the data, i.e. - "Machine readable data for + * COVID-19 research." + * + * @return the description of the data + */ + public String getDescription(); + + /** + * Set the description of the data + * + * @param desc the description + * @throws Exception An exception is thrown if the value is null, which is + * unallowable. + */ + public void setDescription(final String desc) throws Exception; + + /** + * Get the comment/tag that annotates this data. This value is different than + * the description in that it provides commentary or a secondary designation in + * the form of an annotation for the data. For example, where the description + * should generally be useful, this value could simply be "2020Data" or any + * other tag of convenience used during processing. + * + * @return the comment + */ + public String getComment(); + + /** + * Return the comment or tag that annotates the data + * + * @param comment the comment to set + * @throws Exception An exception is thrown if the value is null, which is + * unallowable. + */ + public void setComment(final String comment) throws Exception; + + /** + * Get the context of the data in its present state. For example, a single + * physical sample may be used across multiple experiments and the context in + * one case may be "x-ray scattering" whereas in another it could be "neutron + * scattering." Another example is when the same data is being used by two + * clients and this value changes from "ornl.gov" to "lbnl.gov" to indicate that + * a client should dynamically adapt without changing the data otherwise. + * + * @return the context + */ + public String getContext(); + + /** + * Return the context in which the data exists. + * + * @param context the context to set + * @throws Exception An exception is thrown if the value is null, which is + * unallowable. + */ + public void setContext(final String context) throws Exception; + + /** + * True if the element is required by the client, false otherwise. This is only + * for client tracking and may make no sense for different clients. + * + * @return true if required, false if not + */ + public boolean isRequired(); + + /** + * True if the element is required by the client, false otherwise. This is only + * for client side tracking and may make no sense for different clients. + * + * @param required true if required, false if not + */ + public void setRequired(final boolean required); + + /** + * True if the element is something that should be secret by the client, false + * otherwise. This is only for client tracking and may make no sense for + * different clients. + * + * @return true if secret, false if not + */ + public boolean isSecret(); + + /** + * True if the element is something that should be secret by the client, false + * otherwise. This is only for client tracking and may make no sense for + * different clients. + * + * @param secret true if the element should be treated as a secret, false + * otherwise + */ + public void setSecret(final boolean secret); + + /** + * This operation returns the UUID of the data element. The UUID is a private + * unique identifier assigned to all data elements. + * + * @return the UUID for this element + */ + public UUID getUUID(); + + /** + * Format the DataElement as an output friendly string. + * @return String representation of Data + */ + public String toString(); + /** * 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 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 86ed16e76..d2f07c292 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 @@ -6,7 +6,9 @@ import java.io.StringWriter; import java.io.Writer; import java.util.Arrays; +import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Properties; import java.util.Set; @@ -72,6 +74,21 @@ public static List getAnnotationValuesForMirror( .collect(Collectors.toList()); } + /** + * Get a list of annotation values from an annotation mirror. + * @param mirror the mirror from which to grab values. + * @return list of AnnotationValue + */ + public static Map getAnnotationValueMapForMirror( + final Elements elementUtils, final AnnotationMirror mirror + ) { + return (Map) elementUtils.getElementValuesWithDefaults(mirror).entrySet().stream() + .collect(Collectors.toMap( + entry -> entry.getKey().getSimpleName().toString(), + entry -> entry.getValue().getValue() + )); + } + /** * Return stack trace as string. * @param e subject exception @@ -100,14 +117,11 @@ private static void unwrap(final Optional e) throws T { protected Elements elementUtils; protected DataFieldsVisitor fieldsVisitor; - protected DataFieldVisitor fieldVisitor; - @Override public void init(final ProcessingEnvironment env) { messager = env.getMessager(); elementUtils = env.getElementUtils(); - fieldVisitor = new DataFieldVisitor(); - fieldsVisitor = new DataFieldsVisitor(elementUtils, fieldVisitor); + fieldsVisitor = new DataFieldsVisitor(elementUtils); // Set up Velocity using the Singleton approach; ClasspathResourceLoader allows // us to load templates from src/main/resources @@ -130,7 +144,7 @@ public boolean process(final Set annotations, final Round return false; } - final Fields fields = new Fields(); + List fields = new ArrayList(); fields.addAll(DefaultFields.get()); try { @@ -158,9 +172,9 @@ public boolean process(final Set annotations, final Round * @return discovered fields * @throws IOException */ - private Fields collectFromDataFieldJson(Element element) throws IOException { + private List collectFromDataFieldJson(Element element) throws IOException { final List mirrors = element.getAnnotationMirrors(); - Fields fields = new Fields(); + List fields = new ArrayList<>(); // Iterate through AnnotationValues of AnnotationMirrors for DataFieldJson for ( final AnnotationValue value : mirrors.stream() @@ -197,40 +211,38 @@ private Fields collectFromDataFieldJson(Element element) throws IOException { * @return discovered fields * @throws UnexpectedValueError */ - private Fields collectFromDataFields(Element element) throws UnexpectedValueError { + private List collectFromDataFields(Element element) throws UnexpectedValueError { final List mirrors = element.getAnnotationMirrors(); - Fields fields = new Fields(); + List fields = new ArrayList<>(); // Iterate over the AnnotationValues of AnnotationMirrors of type DataFields. // DataFields present when more than one DataField annotation is used. for ( final AnnotationValue value : mirrors.stream() - .filter( - mirror -> mirror.getAnnotationType().toString().equals( - DataFields.class.getCanonicalName() + .filter( + mirror -> mirror.getAnnotationType().toString().equals( + DataFields.class.getCanonicalName() ) ) - .map(mirror -> getAnnotationValuesForMirror(elementUtils, mirror)) - .flatMap(List::stream) // Flatten List to List - .collect(Collectors.toList()) - ) { + .map(mirror -> getAnnotationValuesForMirror(elementUtils, mirror)) + .flatMap(List::stream) // Flatten List to List + .collect(Collectors.toList()) + ) { // Traditional for-loop used to allow raising an exception with unwrap if the // field visitor returns an error result unwrap(value.accept(fieldsVisitor, fields)); } - // Iterate over the AnnotationValues of AnnotationMirrors of type DataField. - // Only present when only one DataField annotation is used. + // Iterate over any DataField Annotations. Only present when only one DataField + // annotation is used. for ( - final AnnotationValue value : mirrors.stream() - .filter( - mirror -> mirror.getAnnotationType().toString().equals( - DataField.class.getCanonicalName() + final AnnotationMirror dataFieldMirror : mirrors.stream() + .filter( + mirror -> mirror.getAnnotationType().toString().equals( + DataField.class.getCanonicalName() ) ) - .map(mirror -> getAnnotationValuesForMirror(elementUtils, mirror)) - .flatMap(List::stream) - .collect(Collectors.toList()) - ) { - unwrap(value.accept(fieldVisitor, fields)); + .collect(Collectors.toList()) + ) { + unwrap(fieldsVisitor.visitAnnotation(dataFieldMirror, fields)); } return fields; } @@ -242,7 +254,7 @@ private Fields collectFromDataFields(Element element) throws UnexpectedValueErro * @param fields the fields extracted from DataField annotations on interface * @throws IOException */ - private void writeClass(final String interfaceName, final Fields fields) throws IOException { + private void writeClass(final String interfaceName, final List fields) throws IOException { // Determine package, class name from annotated interface name String packageName = null; final int lastDot = interfaceName.lastIndexOf('.'); @@ -258,8 +270,7 @@ private void writeClass(final String interfaceName, final Fields fields) throws context.put(ContextProperty.PACKAGE.key(), packageName); context.put(ContextProperty.INTERFACE.key(), simpleName); context.put(ContextProperty.CLASS.key(), generatedSimpleClassName); - // Hand over the list directly so template can #foreach on fields - context.put(ContextProperty.FIELDS.key(), fields.getFields()); + context.put(ContextProperty.FIELDS.key(), fields); // Write to file final JavaFileObject generatedClassFile = processingEnv.getFiler().createSourceFile(generatedClassName); diff --git a/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/processors/DataFieldVisitor.java b/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/processors/DataFieldVisitor.java deleted file mode 100644 index 58047f593..000000000 --- a/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/processors/DataFieldVisitor.java +++ /dev/null @@ -1,87 +0,0 @@ -package org.eclipse.ice.dev.annotations.processors; - -import java.util.Optional; - -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.SimpleAnnotationValueVisitor8; - -import org.eclipse.ice.dev.annotations.processors.Fields; - -/** - * Visitor that accumulates DataField information from AnnotationValues. This - * Visitor is only intended for use on the AnnotationValues of DataField - * AnnotationMirrors. - * - * Returns an Optional to report errors to the caller - * while conforming to the AnnotationValueVisitor Interface which does not throw - * any exceptions. Return value should be checked. - * - * Additional Parameter Fields acts as the data accumulator, appending visited - * data to its list of fields. - */ -class DataFieldVisitor extends SimpleAnnotationValueVisitor8, Fields> { - - /** - * Check if the given type mirror represents a primitive type. - * @param t type to check - * @return true if primitive, false otherwise - */ - private static boolean isPrimitiveType(TypeMirror t) { - TypeKind kind = t.getKind(); - return - kind == TypeKind.BOOLEAN || - kind == TypeKind.BYTE || - kind == TypeKind.CHAR || - kind == TypeKind.DOUBLE || - kind == TypeKind.FLOAT || - kind == TypeKind.INT || - kind == TypeKind.LONG || - kind == TypeKind.SHORT; - } - - /** - * Return error as default action for unhandled annotation values. - */ - @Override - protected Optional defaultAction(final Object o, final Fields f) { - return Optional.of( - new UnexpectedValueError( - "An unexpected annotation value was encountered: " + o.getClass().getCanonicalName() - ) - ); - } - - /** - * Visit AnnotationValues of type String. For DataField annotations, this is - * expected to be the value of DataField.fieldName. - */ - @Override - public Optional visitString(final String s, final Fields f) { - if (!f.isBuilding()) { - f.begin(); - } - f.setName(s); - if (f.isComplete()) { - f.finish(); - } - return Optional.empty(); - } - - /** - * Visit AnnotationValues of type Type. For DataField annotations, this is - * expected to be the value of DataField.fieldType. - */ - @Override - public Optional visitType(final TypeMirror t, final Fields f) { - if (!f.isBuilding()) { - f.begin(); - } - f.setType(t.toString()); - f.setPrimitive(isPrimitiveType(t)); - if (f.isComplete()) { - f.finish(); - } - return Optional.empty(); - } -} \ No newline at end of file diff --git a/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/processors/DataFieldsVisitor.java b/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/processors/DataFieldsVisitor.java index 52eaa9928..22b450b6f 100644 --- a/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/processors/DataFieldsVisitor.java +++ b/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/processors/DataFieldsVisitor.java @@ -1,15 +1,17 @@ package org.eclipse.ice.dev.annotations.processors; import java.util.List; +import java.util.Map; import java.util.Optional; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; +import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; import javax.lang.model.util.SimpleAnnotationValueVisitor8; +import org.apache.commons.lang3.ClassUtils; import org.eclipse.ice.dev.annotations.DataField; -import org.eclipse.ice.dev.annotations.processors.Fields; /** * Visitor that accumulates DataField information from AnnotationValues. This @@ -27,21 +29,19 @@ * DataField Array then pass the AnnotationMirror of each DataField to * visitAnnotation by recursively visiting the values. */ -class DataFieldsVisitor extends SimpleAnnotationValueVisitor8, Fields> { +class DataFieldsVisitor extends SimpleAnnotationValueVisitor8, List> { protected Elements elementUtils; - protected DataFieldVisitor fieldVisitor; - public DataFieldsVisitor(Elements elementUtils, DataFieldVisitor fieldVisitor) { + public DataFieldsVisitor(Elements elementUtils) { super(); this.elementUtils = elementUtils; - this.fieldVisitor = fieldVisitor; } /** * Return error as default action for unhandled annotation values. */ @Override - protected Optional defaultAction(final Object o, final Fields f) { + protected Optional defaultAction(final Object o, final List f) { return Optional.of( new UnexpectedValueError( "An unexpected annotation value was encountered: " + o.getClass().getCanonicalName() @@ -50,11 +50,11 @@ protected Optional defaultAction(final Object o, final Fie } /** - * Visit AnnotationValues of type Annotation (as an AnnotationMirror), expected - * to visit DataField AnnotationMirrors. + * Visit AnnotationValues of type Annotation. AnnotationMirror for DataField is + * expected. */ @Override - public Optional visitAnnotation(final AnnotationMirror a, final Fields f) { + public Optional visitAnnotation(final AnnotationMirror a, final List f) { if (!a.getAnnotationType().toString().equals(DataField.class.getCanonicalName())) { return Optional.of( new UnexpectedValueError( @@ -63,12 +63,23 @@ public Optional visitAnnotation(final AnnotationMirror a, ); } - for (final AnnotationValue value : DataElementProcessor.getAnnotationValuesForMirror(elementUtils, a)) { - final Optional result = value.accept(fieldVisitor, f); - if (result.isPresent()) { - return result; + Field.FieldBuilder builder = Field.builder(); + Map valueMap = DataElementProcessor.getAnnotationValueMapForMirror(elementUtils, a); + if (valueMap.containsKey("fieldName")) { + builder.name((String) valueMap.get("fieldName")); + } + if (valueMap.containsKey("fieldType")) { + TypeMirror type = (TypeMirror) valueMap.get("fieldType"); + try { + builder.type(ClassUtils.getClass(type.toString())); + } catch (ClassNotFoundException e) { + builder.type(Field.raw(type.toString())); } } + if (valueMap.containsKey("docString")) { + builder.docString((String) valueMap.get("docString")); + } + f.add(builder.build()); return Optional.empty(); } @@ -76,7 +87,7 @@ public Optional visitAnnotation(final AnnotationMirror a, * Visit AnnotationValues of type Array, visiting the Array of DataField AnnotationMirrors. */ @Override - public Optional visitArray(final List vals, final Fields f) { + public Optional visitArray(final List vals, final List f) { for (final AnnotationValue val : vals) { final Optional result = val.accept(this, f); if (result.isPresent()) { diff --git a/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/processors/DefaultFields.java b/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/processors/DefaultFields.java index 58d716f96..e440fe8d6 100644 --- a/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/processors/DefaultFields.java +++ b/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/processors/DefaultFields.java @@ -18,8 +18,17 @@ public class DefaultFields { private static Field privateId = Field.builder() .name("privateId") .type(UUID.class) + .docString("The private UUID of this element. This field is left out of matches().") .defaultValue(Field.raw("UUID.randomUUID()")) .match(false) + .getter(false) + .setter(false) + .alias( + FieldAlias.builder() + .alias("UUID") + .getter(true) + .build() + ) .build(); /** @@ -28,6 +37,7 @@ public class DefaultFields { private static Field id = Field.builder() .name("id") .type(long.class) + .docString("A unique identifier for this element.") .defaultValue(0L) .build(); @@ -37,6 +47,7 @@ public class DefaultFields { private static Field name = Field.builder() .name("name") .type(String.class) + .docString("A simple name for the data.") .defaultValue("name") .build(); @@ -46,6 +57,7 @@ public class DefaultFields { private static Field description = Field.builder() .name("description") .type(String.class) + .docString("A simple description of the data") .defaultValue("description") .build(); @@ -55,6 +67,7 @@ public class DefaultFields { private static Field comment = Field.builder() .name("comment") .type(String.class) + .docString("A comment that annotates the data in a meaningful way.") .defaultValue("no comment") .build(); @@ -64,6 +77,7 @@ public class DefaultFields { private static Field context = Field.builder() .name("context") .type(String.class) + .docString("The context (a tag) in which the data should be considered.") .defaultValue("default") .build(); @@ -73,6 +87,7 @@ public class DefaultFields { private static Field required = Field.builder() .name("required") .type(boolean.class) + .docString("This value is true if the element should be regarded by the client as required.") .defaultValue(false) .build(); @@ -83,6 +98,7 @@ public class DefaultFields { private static Field secret = Field.builder() .name("secret") .type(boolean.class) + .docString("This value is true if the element should be regarded as a secret by the client, such as for passwords.") .defaultValue(false) .build(); @@ -92,6 +108,7 @@ public class DefaultFields { private static Field validator = Field.builder() .name("validator") .type(Field.raw("JavascriptValidator<$class>")) + .docString("The validator used to check the correctness of the data.") .nullable(true) .build(); diff --git a/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/processors/Field.java b/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/processors/Field.java index 34266c2c7..f8cdf2256 100644 --- a/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/processors/Field.java +++ b/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/processors/Field.java @@ -1,5 +1,7 @@ package org.eclipse.ice.dev.annotations.processors; +import java.util.List; + import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; @@ -7,6 +9,7 @@ import lombok.Builder; import lombok.Data; import lombok.NonNull; +import lombok.Singular; /** * Container for Field information, taken from DataField Annotations, in @@ -33,6 +36,11 @@ public class Field { */ String defaultValue; + /** + * Comment to add to the field declaration. + */ + String docString; + /** * Whether or not this field can be null. * @@ -53,6 +61,21 @@ public class Field { */ @Builder.Default boolean match = true; + /** + * Generate a getter for this field. + */ + @Builder.Default boolean getter = true; + + /** + * Generate a setter for this field. + */ + @Builder.Default boolean setter = true; + + /** + * A list of alternate names for this field. + */ + @Singular("alias") List aliases; + /** * Builder class for Field. This class must be a static inner class of Field in * order to take advantage of Lombok's @Builder annotation. The methods defined @@ -237,4 +260,4 @@ private static boolean stringRepresentsPrimitive(String type) { type.equals("boolean") || type.equals("char"); } -} \ No newline at end of file +} diff --git a/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/processors/FieldAlias.java b/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/processors/FieldAlias.java new file mode 100644 index 000000000..1d395af17 --- /dev/null +++ b/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/processors/FieldAlias.java @@ -0,0 +1,22 @@ +package org.eclipse.ice.dev.annotations.processors; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +/** + * An alternate name for a field. This may help identify another JSON attribute + * name for a field, provide alternate Getters and Setters, etc. + */ +@AllArgsConstructor +@Getter +@Builder +public class FieldAlias { + String alias; + boolean getter; + boolean setter; + + public String getGetterName() { + return alias.substring(0, 1).toUpperCase() + alias.substring(1); + } +} diff --git a/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/processors/Fields.java b/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/processors/Fields.java deleted file mode 100644 index a82a52117..000000000 --- a/org.eclipse.ice.dev.annotations/src/main/java/org/eclipse/ice/dev/annotations/processors/Fields.java +++ /dev/null @@ -1,77 +0,0 @@ -package org.eclipse.ice.dev.annotations.processors; - -import java.util.ArrayList; -import java.util.List; - -/** - * A simple container for fields discovered on a DataElement. - */ -class Fields { - protected List fields; - protected Field.FieldBuilder building; - - public Fields() { - this.fields = new ArrayList<>(); - this.building = null; - } - - public void begin() { - this.building = Field.builder(); - } - - public void finish() { - this.fields.add(this.building.build()); - this.building = null; - } - - public List getFields() { - return fields; - } - - public boolean isBuilding() { - return this.building != null; - } - - public boolean isComplete() { - Field partial = this.building.build(); - return (partial.getType() != null) && (partial.getName() != null); - } - - public void setType(final String type) { - this.building.type(Field.raw(type)); - } - - public void setName(final String name) { - this.building.name(name); - } - - public void setPrimitive(boolean primitive) { - this.building.primitive(primitive); - } - - @Override - public String toString() { - return fields.toString(); - } - - public void add(Field field) { - this.fields.add(field); - } - - public void addAll(Fields fields) { - this.fields.addAll(fields.getFields()); - } - - public void addAll(List fields) { - this.fields.addAll(fields); - } - - public void addAll(Field[] fields) { - if (fields == null) { - return; - } - for (int i = 0; i < fields.length; i++) { - this.fields.add(fields[i]); - } - } -} \ No newline at end of file diff --git a/org.eclipse.ice.dev.annotations/src/main/resources/templates/DataElement.vm b/org.eclipse.ice.dev.annotations/src/main/resources/templates/DataElement.vm index 174b57eb9..a9969c45e 100644 --- a/org.eclipse.ice.dev.annotations/src/main/resources/templates/DataElement.vm +++ b/org.eclipse.ice.dev.annotations/src/main/resources/templates/DataElement.vm @@ -1,3 +1,11 @@ +## Whitespace helpers +#set($blank = "") +#set($newline = " +") +#macro(noop)#end +## +## Begin Template +## #if($package) package $package; #end @@ -16,9 +24,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.AccessLevel; import lombok.Data; +import lombok.Getter; import lombok.NoArgsConstructor; import lombok.NonNull; +import lombok.Setter; /** * This is an implementation of $interface that satisfies the dependencies of @@ -34,7 +45,47 @@ public class ${class} implements ${interface}, Serializable, IDataElement { private static final Logger logger = LoggerFactory.getLogger(${class}.class); #foreach($field in $fields) -#if(!${field.Nullable} && !${field.Primitive}) @NonNull #else #{end}protected #evaluate(${field.Type}) ${field.Name}#if(${field.DefaultValue}) = #evaluate(${field.DefaultValue})#end; + + #if(${field.DocString}) + /** + * ${field.DocString} + */ + #end + #macro(fieldtype)#evaluate(${field.Type})#end + #macro(fielddecl)protected #evaluate(${field.Type}) ${field.Name}#if(${field.DefaultValue}) = #evaluate(${field.DefaultValue})#end;#end + #if(!${field.Nullable} && !${field.Primitive}) + @NonNull + #end + #if(!${field.Getter}) + @Getter(AccessLevel.NONE) + #end + #if(!${field.Setter}) + @Setter(AccessLevel.NONE) + #end + $blank#fielddecl() + #end + + #foreach($field in $fields) + #foreach($alias in ${field.aliases}) + #if(${alias.Getter}) + /** + * Get ${field.Name} by alias ${alias.Alias}. + * @return ${field.Name} + */ + public #fieldtype() get${alias.GetterName}() { + return ${field.Name}; + } + #end + #if(${alias.Setter}) + /** + * Set ${field.Name} by alias ${alias.Alias}. + * @return ${field.Name} + */ + public void set${alias.SetterName}(#if(!${field.Nullable} && !${field.Primitive})@NonNull #end#fieldtype() ${field.Name}) { + this.${$field.Name} = ${field.Name}; + } + #end + #end #end /** @@ -81,28 +132,50 @@ public class ${class} implements ${interface}, Serializable, IDataElement { * this element. */ public boolean matches(Object o) { - if (o == this) return true; - if (!(o instanceof $class)) return false; - $class other = ($class) o; - #foreach($field in $fields) - #if(${field.Match}) - #if(${field.Nullable}) - if (this.${field.Name} != null) { - if (!this.${field.Name}.equals(other.${field.Name})) return false; - } else { - if (this.${field.Name} != other.${field.Name}) return false; + boolean retval = false; + + // Outer check for null comparisons + if (o != null) { + // Next check for shallow comparison + if (this != o) { + if (o instanceof $class) { + $class other = ($class) o; + + // Separate boolean checks used to enable better catching + // by debuggers. + #foreach($field in $fields) + #if(${field.Match}) + + boolean ${field.Name}Match = + #if(${field.Nullable}) + this.${field.Name} == null ? + this.${field.Name} == other.${field.Name} : + this.${field.Name}.equals(other.${field.Name}); + #elseif(${field.Primitive}) + this.${field.Name} == other.${field.Name}; + #else + this.${field.Name}.equals(other.${field.Name}); + #end## if nullable + #else + // Not checking ${field.Name} + #end## if match + #end## foreach + + #set($count = 0) + retval = + $blank#foreach($field in $fields) + #if(${field.Match}) + #set($count = $count + 1) + #set($third = $count % 3) + #noop()${field.Name}Match#if($foreach.hasNext) &&#if($third == 0)$newline #else #end#else;$newline#end#end#end + } + } else { + // This should be true if they are the same because the deep comparison is + // performed otherwise. + retval = true; + } } - #elseif(${field.Primitive}) - if (this.${field.Name} != other.${field.Name}) return false; - #else - if (!this.${field.Name}.equals(other.${field.Name})) return false; - #end## if nullable - #else - // Not checking ${field.Name} - #end## if match - #end## foreach - - return true; + return retval; } /** diff --git a/org.eclipse.ice.renderer/src/main/java/org/eclipse/ice/renderer/Person.java b/org.eclipse.ice.renderer/src/main/java/org/eclipse/ice/renderer/Person.java index d96b20104..1e1d1f38d 100644 --- a/org.eclipse.ice.renderer/src/main/java/org/eclipse/ice/renderer/Person.java +++ b/org.eclipse.ice.renderer/src/main/java/org/eclipse/ice/renderer/Person.java @@ -4,9 +4,9 @@ import org.eclipse.ice.dev.annotations.DataField; @DataElement -@DataField(fieldName = "age", fieldType = int.class) -@DataField(fieldName = "firstName", fieldType = String.class) -@DataField(fieldName = "lastName", fieldType = String.class) +@DataField(fieldName = "age", fieldType = int.class, docString = "The age of the person.") +@DataField(fieldName = "firstName", fieldType = String.class, docString = "The first name of the person.") +@DataField(fieldName = "lastName", fieldType = String.class, docString = "The last name of the Person.") public interface Person { //void foo(); } diff --git a/org.eclipse.ice.renderer/src/test/java/org/eclipse/ice/tests/renderer/GeneratedDataElementTest.java b/org.eclipse.ice.renderer/src/test/java/org/eclipse/ice/tests/renderer/GeneratedDataElementTest.java index c9b8e6c06..cd60c3a5a 100644 --- a/org.eclipse.ice.renderer/src/test/java/org/eclipse/ice/tests/renderer/GeneratedDataElementTest.java +++ b/org.eclipse.ice.renderer/src/test/java/org/eclipse/ice/tests/renderer/GeneratedDataElementTest.java @@ -104,7 +104,7 @@ void testDefaultProps() { assertEquals(element.getValidator(), validator); // Make sure that the UUID is not null - assertNotNull(element.getPrivateId()); + assertNotNull(element.getUUID()); return; }