Commit 7a1392a0 authored by Daniel Bluhm's avatar Daniel Bluhm

Add Types

Handles imports and collision detection for a collection of fields
Signed-off-by: Daniel Bluhm's avatarDaniel Bluhm <bluhmdj@ornl.gov>
parent 3461a844
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;
public class Types {
/**
* Import matcher regex.
*
* Consider the following string:
*
* <pre>
* {@code java.util.Map$Entry<java.lang.String, java.lang.Object>}
* </pre>
*
* {@code $Entry} will match separately with this regex, allowing us to test
* for strings beginning with {@code $} and dropping it from the imports. We
* do this because inner classes are accessed through their parent class so
* it is the parent class that must be imported.
*/
private static final Pattern IMPORT_RE = Pattern.compile("(\\$?[a-zA-Z0-9.]+)");
/**
* Type shortener regex.
*/
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.
*/
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.
*/
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;
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
Set<String> collisions = new HashSet<>();
for (String type : allTypes) {
String shortened = getShortenedType(type);
if (collisions.contains(shortened)) {
fullToShort.put(type, type);
} else if (shortToFull.containsKey(shortened)) {
fullToShort.put(type, type);
shortToFull.put(type, type);
String previous = shortToFull.remove(shortened);
shortToFull.put(previous, previous);
fullToShort.put(previous, previous);
collisions.add(shortened);
} else {
fullToShort.put(type, shortened);
shortToFull.put(shortened, type);
}
}
}
/**
* Return the short name of this field's type.
* @return the short name of this field's 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("$", ".");
}
/**
* Return set of strings representing the required imports of this field.
* @return set of strings to import
*/
public Set<String> getImports() {
return shortToFull.entrySet().stream()
.filter(entry -> !entry.getKey().equals(entry.getValue()))
.map(entry -> entry.getValue())
.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
package org.eclipse.ice.tests.dev.annotations.processors;
import static org.junit.jupiter.api.Assertions.*;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import org.eclipse.ice.dev.annotations.processors.Field;
import org.eclipse.ice.dev.annotations.processors.Types;
import org.junit.jupiter.api.Test;
class TypesTest {
@Test
void testGetShortenedType() {
assertEquals("boolean", Types.getShortenedType("boolean"));
assertEquals("String", Types.getShortenedType("java.lang.String"));
assertEquals("UUID", Types.getShortenedType("java.util.UUID"));
assertEquals("Entry", Types.getShortenedType("java.util.Map.Entry"));
assertEquals(
"Map.Entry<String, Object>",
Types.getShortenedType("java.util.Map$Entry<String, Object>")
);
assertEquals(
"List<String>",
Types.getShortenedType("java.util.List<java.lang.String>")
);
}
@Test
void testGetImports() {
Types types = new Types(List.of(
Field.builder()
.name("test")
.type(UUID.class)
.build()
));
assertTrue(types.getImports().contains("java.util.UUID"));
types = new Types(List.of(
Field.builder()
.name("test")
.type(String.class)
.build()
));
Set<String> imports = types.getImports();
assertFalse(imports.contains("java.lang.String"));
assertTrue(imports.isEmpty());
types = new Types(List.of(
Field.builder()
.name("test")
.type("java.util.Map.Entry")
.build()
));
assertTrue(types.getImports().contains("java.util.Map.Entry"));
types = new Types(List.of(
Field.builder()
.name("test")
.type("java.util.Map.Entry<java.lang.String, java.lang.Object>")
.build()
));
imports = types.getImports();
assertTrue(imports.contains("java.util.Map.Entry"));
assertFalse(imports.contains("java.lang.String"));
assertFalse(imports.contains("java.lang.Object"));
types = new Types(List.of(
Field.builder()
.name("test")
.type("java.util.List<java.lang.String>")
.build()
));
imports = types.getImports();
assertTrue(imports.contains("java.util.List"));
assertFalse(imports.contains("java.lang.String"));
types = new Types(List.of(
Field.builder()
.name("test")
.type("java.util.Map<java.lang.String, java.util.List<java.util.Map.Entry<java.lang.String, java.lang.String>>>")
.build()
));
imports = types.getImports();
assertTrue(imports.contains("java.util.Map"));
assertTrue(imports.contains("java.util.List"));
assertFalse(imports.contains("java.lang.String"));
}
@Test
void testResolve() {
Types types = new Types(List.of(
Field.builder()
.name("test")
.type("java.lang.String")
.build(),
Field.builder()
.name("test")
.type("int")
.build(),
Field.builder()
.name("test")
.type("java.util.UUID")
.build(),
Field.builder()
.name("test")
.type("java.util.List<java.lang.String>")
.build()
));
assertEquals("String", types.resolve("java.lang.String"));
assertEquals("int", types.resolve("int"));
assertEquals("UUID", types.resolve("java.util.UUID"));
assertEquals("List<String>", types.resolve("java.util.List<java.lang.String>"));
}
@Test
void testCollisionsHandledCorrectly() {
Types types = new Types(List.of(
Field.builder()
.name("test")
.type("java.lang.String")
.build(),
Field.builder()
.name("test")
.type("com.example.String")
.build()
));
Set<String> imports = types.getImports();
assertFalse(imports.contains("com.example.String"));
assertEquals("java.lang.String", types.resolve("java.lang.String"));
assertEquals("com.example.String", types.resolve("com.example.String"));
}
}
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment