Unverified Commit 968357d6 authored by Daniel Bluhm's avatar Daniel Bluhm Committed by GitHub

Merge pull request #454 from dbluhm/dataelement-imports

Automatically import types
parents b620bc12 31901981
......@@ -46,7 +46,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
* @author Jay Jay Billings
*
*/
public class JavascriptValidator<T> implements Serializable {
public class JavascriptValidator implements Serializable {
/**
* Logging tool
......@@ -88,7 +88,7 @@ public class JavascriptValidator<T> implements Serializable {
* Copy constructor
* @param otherValidator to copy
*/
public JavascriptValidator(JavascriptValidator<T> otherValidator) {
public JavascriptValidator(JavascriptValidator otherValidator) {
if (otherValidator != null) {
function = otherValidator.function;
}
......@@ -136,9 +136,9 @@ public class JavascriptValidator<T> implements Serializable {
// Check shallow identify and type first
if (this == otherObject) {
retValue = true;
} else if (otherObject instanceof JavascriptValidator<?>) {
} else if (otherObject instanceof JavascriptValidator) {
@SuppressWarnings("unchecked")
JavascriptValidator<T> otherValidator = (JavascriptValidator<T>) otherObject;
JavascriptValidator otherValidator = (JavascriptValidator) otherObject;
retValue = this.function.equals(otherValidator.function);
}
......@@ -153,7 +153,7 @@ public class JavascriptValidator<T> implements Serializable {
* @throws NoSuchMethodException This exception is thrown if the Javascript
* validation function cannot be found.
*/
public boolean validate(final T data) throws NoSuchMethodException {
public boolean validate(final IDataElement data) throws NoSuchMethodException {
boolean retValue = false;
Object result = null;
......@@ -196,7 +196,7 @@ public class JavascriptValidator<T> implements Serializable {
public Object clone() {
try {
// Call the copy constructor to create the clone.
return new JavascriptValidator<T>(this);
return new JavascriptValidator(this);
} catch (Exception e) {
logger.error("Unable to clone DataElement!", e);
return null;
......
......@@ -99,7 +99,7 @@ class GeneratedDataElementTest {
assertEquals(element.isSecret(), secret);
// Make sure that adding validators works superficially
JavascriptValidator<GeneratedDataElement> validator = new JavascriptValidator<GeneratedDataElement>();
JavascriptValidator validator = new JavascriptValidator();
element.setValidator(validator);
assertEquals(element.getValidator(), validator);
......@@ -167,7 +167,7 @@ class GeneratedDataElementTest {
GeneratedDataElement element = getStringElement("Major Lazer & La Roux");
element.setSecret(true);
element.setRequired(true);
element.setValidator(new JavascriptValidator<GeneratedDataElement>());
element.setValidator(new JavascriptValidator());
// Because of the private id changing and being unique, this cannot be checked
// against a reference but can only be checked by inversion.
......@@ -178,7 +178,7 @@ class GeneratedDataElementTest {
element.setTestField("Eastern Sun");
System.out.println(output);
GeneratedDataElement element2 = getStringElement("Emancipator");
element2.setValidator(new JavascriptValidator<GeneratedDataElement>());
element2.setValidator(new JavascriptValidator());
element2.fromJson(output);
element.fromJson(output);
assertEquals(element,element2);
......@@ -198,7 +198,7 @@ class GeneratedDataElementTest {
element.setTestPOJO(new TestPOJO());
element.setSecret(true);
element.setRequired(true);
element.setValidator(new JavascriptValidator<GeneratedDataElementPOJO>());
element.setValidator(new JavascriptValidator());
// Because of the private id changing and being unique, this cannot be checked
// against a reference but can only be checked by inversion.
......@@ -209,7 +209,7 @@ class GeneratedDataElementTest {
GeneratedDataElementPOJO element2 = new GeneratedDataElementPOJOImplementation();
TestPOJO pojo2 = new TestPOJO();
pojo2.setDoubleValue(1.072);
element2.setValidator(new JavascriptValidator<GeneratedDataElementPOJO>());
element2.setValidator(new JavascriptValidator());
element2.setTestPOJO(pojo2);
element2.fromJson(output);
......@@ -236,12 +236,12 @@ class GeneratedDataElementTest {
GeneratedDataElement element4 = getStringElement("Halsey");
// Need a validator for the tests that is shared on the equal elements.
JavascriptValidator<GeneratedDataElement> validator = new JavascriptValidator<GeneratedDataElement>();
JavascriptValidator validator = new JavascriptValidator();
element.setValidator(validator);
element2.setValidator(validator);
element4.setValidator(validator);
// Billie needs her own validator
element3.setValidator(new JavascriptValidator<GeneratedDataElement>());
element3.setValidator(new JavascriptValidator());
// Data elements must be checked both for matching - a deep inequality except
// the UUID - and for a fully complete match that contains the UUID. Start with
......
......@@ -160,6 +160,7 @@ public class DataElementProcessor extends AbstractProcessor {
.className(element.getImplName())
.interfaceName(element.getName())
.fields(fields)
.types(fields.getTypes())
.build()
.write(writer);
}
......@@ -189,6 +190,7 @@ public class DataElementProcessor extends AbstractProcessor {
.implementation(element.getImplName())
.collection(collectionName)
.fields(fields)
.types(fields.getTypes())
.build()
.write(writer);
}
......@@ -211,6 +213,9 @@ public class DataElementProcessor extends AbstractProcessor {
.packageName(element.getPackageName())
.interfaceName(element.getName())
.fields(fields)
// Only subset of fields used in interface. More specific scope
// needed to correctly determine imports.
.types(new Types(fields.getInterfaceFields()))
.build()
.write(writer);
}
......
......@@ -22,7 +22,7 @@ public class DefaultFields {
.varName("privateId")
.type(UUID.class)
.docString("The private UUID of this element. This field is left out of matches().")
.defaultValue(UUID.class.getCanonicalName() + ".randomUUID()")
.defaultValue("UUID.randomUUID()")
.match(false)
.getter(true)
.setter(false)
......@@ -113,7 +113,7 @@ public class DefaultFields {
*/
private static Field validator = Field.builder()
.name("validator")
.type(JavascriptValidator.class.getCanonicalName() + "<$interface>")
.type(JavascriptValidator.class)
.docString("The validator used to check the correctness of the data.")
.nullable(true)
.defaultField(true)
......
......@@ -143,19 +143,6 @@ public class Field {
return this.varName != null;
}
/**
* Get a class by name or return null if not found
* @param cls
* @return found class or null
*/
private static Class<?> getClassOrNull(String cls) {
try {
return ClassUtils.getClass(cls);
} catch (ClassNotFoundException e) {
return null;
}
}
/**
* Return this Fields name ready for use in a method name.
* @return capitalized name
......@@ -193,6 +180,19 @@ public class Field {
return this.modifiers.contains("final");
}
/**
* Get a class by name or return null if not found
* @param cls
* @return found class or null
*/
private static Class<?> getClassOrNull(String cls) {
try {
return ClassUtils.getClass(cls);
} catch (ClassNotFoundException e) {
return null;
}
}
/**
* Instruct Jackson how to deserialize fields.
*/
......@@ -216,7 +216,7 @@ public class Field {
*/
@JsonIgnore
public FieldBuilder type(Class<?> type) {
this.type = type.getName().toString();
this.type = type.getCanonicalName();
this.primitive = type.isPrimitive();
return this;
}
......
......@@ -4,6 +4,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
/**
* A collection of Field objects to be used especially in template rendering.
......@@ -86,6 +87,24 @@ public class Fields implements Iterable<Field> {
.iterator();
}
/**
* Return iterable of fields needed for interface.
* @return Iterable of fields needed for interface.
*/
public Iterable<Field> getInterfaceFields() {
return fields.stream()
.filter(field -> !field.isDefaultField())
.collect(Collectors.toList());
}
/**
* Return Types instance for this set of Fields.
* @return Types instance
*/
public Types getTypes() {
return new Types(this);
}
/**
* Returns an iterator over the mutable fields found in this collection. The
* mutable fields are the default set iterated over because they are the most
......
......@@ -12,6 +12,7 @@
package org.eclipse.ice.dev.annotations.processors;
import lombok.Builder;
import lombok.NonNull;
/**
* Writer for DataElement Implementation classes.
......@@ -42,6 +43,11 @@ public class ImplementationWriter extends VelocitySourceWriter {
*/
private static final String FIELDS = "fields";
/**
* Context key for types.
*/
private static final String TYPES = "types";
/**
* Context key for class.
*/
......@@ -49,7 +55,8 @@ public class ImplementationWriter extends VelocitySourceWriter {
@Builder
public ImplementationWriter(
String packageName, String interfaceName, String className, Fields fields
String packageName, String interfaceName, String className,
@NonNull Fields fields, @NonNull Types types
) {
super();
this.template = IMPL_TEMPLATE;
......@@ -57,5 +64,6 @@ public class ImplementationWriter extends VelocitySourceWriter {
this.context.put(INTERFACE, interfaceName);
this.context.put(CLASS, className);
this.context.put(FIELDS, fields);
this.context.put(TYPES, types);
}
}
......@@ -43,14 +43,21 @@ public class InterfaceWriter extends VelocitySourceWriter {
*/
private static final String FIELDS = "fields";
/**
* Context key for types.
*/
private static final String TYPES = "types";
@Builder
public InterfaceWriter(
String packageName, String interfaceName, @NonNull Fields fields
String packageName, String interfaceName, @NonNull Fields fields,
@NonNull Types types
) {
super();
this.template = TEMPLATE;
context.put(PACKAGE, packageName);
context.put(INTERFACE, interfaceName);
context.put(FIELDS, fields);
context.put(TYPES, types);
}
}
......@@ -63,11 +63,16 @@ public class PersistenceHandlerWriter extends VelocitySourceWriter {
*/
private static final String FIELDS = "fields";
/**
* Context key for types.
*/
private static final String TYPES = "types";
@Builder
public PersistenceHandlerWriter(
String packageName, String elementInterface, String interfaceName,
String className, String implementation, String collection,
@NonNull Fields fields
@NonNull Fields fields, @NonNull Types types
) {
super();
this.template = PERSISTENCE_HANDLER_TEMPLATE;
......@@ -78,5 +83,6 @@ public class PersistenceHandlerWriter extends VelocitySourceWriter {
this.context.put(COLLECTION, collection);
this.context.put(IMPLEMENTATION, implementation);
this.context.put(FIELDS, fields);
this.context.put(TYPES, types);
}
}
/*******************************************************************************
* 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:
* Daniel Bluhm - Initial implementation
*******************************************************************************/
package org.eclipse.ice.dev.annotations.processors;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* Class for holding and retrieving type information for a collection of Fields.
* Stores context needed to prevent name collision.
* @author Daniel Bluhm
*/
public class Types {
/**
* Type matching regex used in shortening types. Can also be used for getting
* the last element of a type hierarchy (through capturing group 1).
*/
private static final Pattern TYPE_SHORTENER = Pattern.compile("(?:\\w+\\.)*([\\w\\$]+)\\b");
/**
* Lookup table for fully qualified types to their shortened types.
*
* This is used to record collisions by mapping fully qualified types to
* themselves when another type would have conflicted with the shortened
* version.
*
* This map combined with the shortToFull map create a bidirectional mapping.
*/
private Map<String, String> fullToShort;
/**
* Lookup table for shortened types to their fully qualified types.
*
* This is used to determine what types must be imported.
*
* This map combined with the fullToShort map create a bidirectional mapping.
*/
private Map<String, String> shortToFull;
/**
* The set of all types present on the fields accounted for by this instance
* of Types.
*/
private Set<String> allTypes;
/**
* Instantiate Types.
*
* Constructs bidirectional mapping between full and short type names.
* @param fields for which this Types instance is accountable.
*/
public Types(Iterable<Field> fields) {
this.fullToShort = new HashMap<>();
this.shortToFull = new HashMap<>();
this.allTypes = new HashSet<>();
// Extract types
for (Field field : fields) {
Matcher matcher = TYPE_SHORTENER.matcher(field.getType());
while (matcher.find()) {
allTypes.add(matcher.group());
}
}
// Determine shortened names, populating the look up tables while
// detecting collisions.
// Types that would collide are left in fully qualified form and map to
// themselves in the bidirectional map.
Set<String> collisions = new HashSet<>();
for (String type : allTypes) {
String shortened = getShortenedType(type);
if (collisions.contains(shortened)) {
// Collision detected; first instance of collision already
// corrected, storing only this instance.
fullToShort.put(type, type);
shortToFull.put(type, type);
} else if (shortToFull.containsKey(shortened)) {
// Collision detected; correct first instance as well as storing
// this instance.
fullToShort.put(type, type);
shortToFull.put(type, type);
String previous = shortToFull.remove(shortened);
shortToFull.put(previous, previous);
fullToShort.put(previous, previous);
// Mark this collision as already having its first instance
// corrected.
collisions.add(shortened);
} else {
// No collision detected, save to bidirectional mapping
fullToShort.put(type, shortened);
shortToFull.put(shortened, type);
}
}
}
/**
* Return the shortened type for the given type.
* @param type to shorten
* @return shortened type
*/
public static String getShortenedType(String type) {
StringBuffer shortenedType = new StringBuffer();
Matcher matcher = TYPE_SHORTENER.matcher(type);
while (matcher.find()) {
matcher.appendReplacement(shortenedType, "$1");
}
matcher.appendTail(shortenedType);
return shortenedType.toString().replace("$", ".");
}
/**
* Get set of imports needed for the fields this types instance handles.
* @return set of imports
*/
public Set<String> getImports() {
return shortToFull.entrySet().stream()
// Filter out collisions (fully qualified maps to itself)
// Also filters out primitives (boolean shortened is still boolean)
.filter(entry -> !entry.getKey().equals(entry.getValue()))
.map(entry -> entry.getValue())
// No need to import java.lang package
.filter(type -> !type.startsWith("java.lang"))
.collect(Collectors.toSet());
}
/**
* Resolve the type and its type parameters, shortening all types that
* can be shortened.
* @param type to look up
* @return shortened type if no collisions, full type if it would collide
*/
public String resolve(String type) {
StringBuffer resolved = new StringBuffer();
Matcher matcher = TYPE_SHORTENER.matcher(type);
while (matcher.find()) {
matcher.appendReplacement(
resolved,
fullToShort.get(matcher.group())
);
}
matcher.appendTail(resolved);
return resolved.toString();
}
}
\ No newline at end of file
......@@ -60,4 +60,4 @@ class VelocityProperties extends Properties {
}
return instance;
}
}
\ No newline at end of file
}
#parse("templates/common.vm")
#if($package)
package $package;
#end
......@@ -23,7 +22,7 @@ import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.Setter;
import org.eclipse.ice.data.JavascriptValidator;
#imports
/**
* This is an implementation of $interface that satisfies the dependencies of
......
#parse("templates/common.vm")
#if($package)
package $package;
#end
import org.eclipse.ice.data.IDataElement;
#imports
/**
* This interface satisfies the dependencies of the @DataElement Annotation and
......
......@@ -10,6 +10,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
#imports
/**
* This is an implementation of $interface<$elementInterface>
* that satisfies the dependencies of the {@code @Persisted} Annotation and was
......
##
## Macros and values useful across all templates and other readability helpers.
##
## Help make this file more readable.
#macro(definitions)
#evaluate($bodyContent.toString().replaceAll("\n\p{Space}*\n", "").replaceAll("\p{Space}*##.*", ""))
#end
#@definitions
## Whitespace helpers ##
## Whitespace helpers ##
## Prepend a directive with this value if you don't want the directive to
## Gobble whitespace
#set($blank = "")
## Gobbles whitespace
#macro(noop)#end
## Literal newline
## Use as a block macro to set the tab index of all lines in body.
#macro(settab $num)
#set($tab = " ")
#set($newline = "
")
## Literal tab
#set($tab = " ")