From c82e615772046685634797c3ea29c861662e212d Mon Sep 17 00:00:00 2001 From: Collin Smith Date: Wed, 16 Dec 2020 11:36:11 -0800 Subject: [PATCH] Created Parser interface and generation --- .../table/annotation/ParserCodeGenerator.java | 132 ++++++++++++++++++ .../table/annotation/ParserElement.java | 79 +++++++++++ .../table/annotation/SchemaElement.java | 19 +++ .../table/annotation/SchemaProcessor.java | 14 ++ .../com/riiablo/table/annotation/Parser.java | 23 +++ .../main/java/com/riiablo/table/Parser.java | 14 ++ .../java/com/riiablo/table/TsvParser.java | 16 +++ .../table/schema/MonStatsParserImpl.java | 22 +++ 8 files changed, 319 insertions(+) create mode 100644 table/annotation-processor/src/main/java/com/riiablo/table/annotation/ParserCodeGenerator.java create mode 100644 table/annotation-processor/src/main/java/com/riiablo/table/annotation/ParserElement.java create mode 100644 table/annotations/src/main/java/com/riiablo/table/annotation/Parser.java create mode 100644 table/core/src/main/java/com/riiablo/table/Parser.java create mode 100644 table/core/src/main/java/com/riiablo/table/TsvParser.java create mode 100644 table/integration/src/main/java/com/riiablo/table/schema/MonStatsParserImpl.java diff --git a/table/annotation-processor/src/main/java/com/riiablo/table/annotation/ParserCodeGenerator.java b/table/annotation-processor/src/main/java/com/riiablo/table/annotation/ParserCodeGenerator.java new file mode 100644 index 00000000..b2573801 --- /dev/null +++ b/table/annotation-processor/src/main/java/com/riiablo/table/annotation/ParserCodeGenerator.java @@ -0,0 +1,132 @@ +package com.riiablo.table.annotation; + +import com.squareup.javapoet.ArrayTypeName; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.Name; + +import static com.riiablo.table.annotation.Constants.STRING; + +class ParserCodeGenerator extends CodeGenerator { + ParserCodeGenerator(Context context, String parserPackage) { + super(context, parserPackage); + } + + @Override + ClassName formatName(String packageName, SchemaElement schemaElement) { + return schemaElement.parserClassName + = ClassName.get( + packageName, + schemaElement.element.getSimpleName() + Parser.class.getSimpleName()); + } + + @Override + TypeSpec.Builder newTypeSpec(SchemaElement schemaElement) { + ArrayTypeName fieldIdsTypeName = ArrayTypeName.of(int.class); + FieldSpec fieldIds = FieldSpec + .builder(fieldIdsTypeName, "fieldIds", Modifier.FINAL) + .initializer("new $T[$L]", fieldIdsTypeName.componentType, schemaElement.numFields) + .build(); + return super.newTypeSpec(schemaElement) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addSuperinterface(schemaElement.parserElement.declaredType) + .addField(fieldIds) + .addMethod(hasNext(schemaElement)) + .addMethod(parseFields(schemaElement, fieldIds)) + .addMethod(parseRecord(schemaElement, fieldIds)) + ; + } + + MethodSpec hasNext(SchemaElement schemaElement) { + ParserElement parserElement = schemaElement.parserElement; + MethodSpec.Builder method = MethodSpec + .overriding( + parserElement.getMethod("hasNext"), + parserElement.declaredType, + context.typeUtils); + final ParameterSpec parser = method.parameters.get(0); + method.addStatement("return $N.$N() != $L", parser, "cacheLine", -1); + return method.build(); + } + + MethodSpec parseFields(SchemaElement schemaElement, FieldSpec fieldIds) { + ParserElement parserElement = schemaElement.parserElement; + MethodSpec.Builder method = MethodSpec + .overriding( + parserElement.getMethod("parseFields"), + parserElement.declaredType, + context.typeUtils); + + int i = 0; + final ParameterSpec parser = method.parameters.get(0); + for (FieldElement field : schemaElement.fields) { + for (String fieldName : field.fieldNames) { + method.addStatement("$N[$L] = $N.$N($S)", fieldIds, i++, parser, "fieldId", fieldName); + } + } + + return method.build(); + } + + MethodSpec parseRecord(SchemaElement schemaElement, FieldSpec fieldIds) { + ParserElement parserElement = schemaElement.parserElement; + MethodSpec.Builder method = MethodSpec + .overriding( + parserElement.getMethod("parseRecord"), + parserElement.declaredType, + context.typeUtils); + + int i = 0; + final ParameterSpec record = method.parameters.get(0); + final ParameterSpec parser = method.parameters.get(1); + for (FieldElement field : schemaElement.fields) { + final TypeName fieldTypeName = TypeName.get(field.element()); + final CodeBlock fqFieldName = qualify(record, field.name()); + if (field.isArray()) { + final TypeName componentTypeName = TypeName.get(field.componentType()); + for (int j = 0, s = field.fieldNames.length; j < s; j++, i++) { + method.addStatement(parseX(parser, componentTypeName, + CodeBlock.of("$L[$L]", fqFieldName, j), + CodeBlock.of("$N[$L]", fieldIds, i))); + } + } else { + method.addStatement(parseX(parser, fieldTypeName, fqFieldName, + CodeBlock.of("$N[$L]", fieldIds, i++))); + } + } + + return method.build(); + } + + static CodeBlock qualify(Object object, Name field) { + return CodeBlock.of("$N.$N", object, field); + } + + static CodeBlock parseX(Object parser, TypeName type, Object var, Object fieldId) { + return CodeBlock.of("$L = $N.$N$L($L)", var, parser, "parse", getIoMethod(type), fieldId); + } + + static String getIoMethod(TypeName type) { + if (type == TypeName.BYTE) { + return "Byte"; + } else if (type == TypeName.SHORT) { + return "Short"; + } else if (type == TypeName.INT) { + return "Int"; + } else if (type == TypeName.LONG) { + return "Long"; + } else if (type == TypeName.BOOLEAN) { + return "Boolean"; + } else if (STRING.equals(type)) { + return "String"; + } else { + throw new UnsupportedOperationException(type + " is not supported!"); + } + } +} diff --git a/table/annotation-processor/src/main/java/com/riiablo/table/annotation/ParserElement.java b/table/annotation-processor/src/main/java/com/riiablo/table/annotation/ParserElement.java new file mode 100644 index 00000000..bbb76a50 --- /dev/null +++ b/table/annotation-processor/src/main/java/com/riiablo/table/annotation/ParserElement.java @@ -0,0 +1,79 @@ +package com.riiablo.table.annotation; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.MirroredTypeException; +import org.apache.commons.lang3.builder.ToStringBuilder; + +final class ParserElement { + static ParserElement get(Context context, Element element) { + Parser annotation = element.getAnnotation(Parser.class); + final TypeElement parserElement, parserImplElement; + final DeclaredType declaredType; + if (annotation == null) { + // Only need parserElement if generating Parser impl + parserElement = context.elementUtils.getTypeElement(com.riiablo.table.Parser.class.getCanonicalName()); + declaredType = context.typeUtils.getDeclaredType(parserElement, element.asType()); + parserImplElement = null; + } else { + // Only need parserImplElement if @Parser present + parserImplElement = getParserImpl(context, annotation); + parserElement = null; + declaredType = null; + } + return new ParserElement(annotation, declaredType, parserElement, parserImplElement); + } + + static TypeElement getParserImpl(Context context, Parser annotation) { + if (annotation == null) return null; + try { + Class parserImpl = annotation.value(); + return context.elementUtils.getTypeElement(parserImpl.getCanonicalName()); + } catch (MirroredTypeException t) { + DeclaredType parserImplMirror = (DeclaredType) t.getTypeMirror(); + return (TypeElement) parserImplMirror.asElement(); + } + } + + final Parser annotation; + final DeclaredType declaredType; + final TypeElement parserElement; + final TypeElement parserImplElement; + + ParserElement( + Parser annotation, + DeclaredType declaredType, + TypeElement parserElement, + TypeElement parserImplElement) { + this.annotation = annotation; + this.declaredType = declaredType; + this.parserElement = parserElement; + this.parserImplElement = parserImplElement; + } + + ExecutableElement getMethod(CharSequence methodName) { + for (Element e : parserElement.getEnclosedElements()) { + if (e.getKind() == ElementKind.METHOD) { + ExecutableElement methodElement = (ExecutableElement) e; + if (methodElement.getSimpleName().contentEquals(methodName)) { + return methodElement; + } + } + } + + throw new AssertionError(parserElement + " does not contain " + methodName); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("annotation", annotation) + .append("declaredType", declaredType) + .append("parserElement", parserElement) + .append("parserImplElement", parserImplElement) + .toString(); + } +} diff --git a/table/annotation-processor/src/main/java/com/riiablo/table/annotation/SchemaElement.java b/table/annotation-processor/src/main/java/com/riiablo/table/annotation/SchemaElement.java index 47e73f85..31c3169d 100644 --- a/table/annotation-processor/src/main/java/com/riiablo/table/annotation/SchemaElement.java +++ b/table/annotation-processor/src/main/java/com/riiablo/table/annotation/SchemaElement.java @@ -69,12 +69,14 @@ final class SchemaElement { TableElement tableElement = TableElement.get(context, typeElement); SerializerElement serializerElement = SerializerElement.get(context, typeElement); + ParserElement parserElement = ParserElement.get(context, typeElement); return new SchemaElement( annotation, typeElement, tableElement, serializerElement, + parserElement, primaryKeyFieldElement, fields); } @@ -124,27 +126,42 @@ final class SchemaElement { final TypeElement element; final TableElement tableElement; final SerializerElement serializerElement; + final ParserElement parserElement; final FieldElement primaryKeyFieldElement; final Collection fields; + final int numFields; ClassName serializerClassName; + ClassName parserClassName; SchemaElement( Schema annotation, TypeElement element, TableElement tableElement, SerializerElement serializerElement, + ParserElement parserElement, FieldElement primaryKeyFieldElement, Collection fields) { this.annotation = annotation; this.element = element; this.tableElement = tableElement; this.serializerElement = serializerElement; + this.parserElement = parserElement; this.primaryKeyFieldElement = primaryKeyFieldElement; this.fields = fields; + this.numFields = countNumFields(fields); if (serializerElement.serializerImplElement != null) { serializerClassName = ClassName.get(serializerElement.serializerImplElement); } + if (parserElement.parserImplElement != null) { + parserClassName = ClassName.get(parserElement.parserImplElement); + } + } + + final int countNumFields(Collection fields) { + int numFields = 0; + for (FieldElement field : fields) numFields += field.fieldNames.length; + return numFields; } @Override @@ -154,6 +171,8 @@ final class SchemaElement { .append("tableElement", tableElement) .append("serializerElement", serializerElement) .append("serializerClassName", serializerClassName) + .append("ParserElement", parserElement) + .append("serializerClassName", serializerClassName) .append("primaryKeyFieldElement", primaryKeyFieldElement) .toString(); } diff --git a/table/annotation-processor/src/main/java/com/riiablo/table/annotation/SchemaProcessor.java b/table/annotation-processor/src/main/java/com/riiablo/table/annotation/SchemaProcessor.java index 7280fcbf..0e4a8a5c 100644 --- a/table/annotation-processor/src/main/java/com/riiablo/table/annotation/SchemaProcessor.java +++ b/table/annotation-processor/src/main/java/com/riiablo/table/annotation/SchemaProcessor.java @@ -49,6 +49,8 @@ public class SchemaProcessor extends AbstractProcessor { context, "com.riiablo.excel.table"); SerializerCodeGenerator serializerCodeGenerator = new SerializerCodeGenerator( context, "com.riiablo.excel.serializer"); + ParserCodeGenerator parserCodeGenerator = new ParserCodeGenerator( + context, "com.riiablo.excel.parser"); for (Element element : roundEnv.getElementsAnnotatedWith(Schema.class)) { if (element.getKind() != ElementKind.CLASS) { context.error(element, "{} can only be applied to classes", Schema.class); @@ -66,8 +68,20 @@ public class SchemaProcessor extends AbstractProcessor { t.printStackTrace(System.err); } } + + if (schemaElement.parserElement.declaredType != null) { + try { + parserCodeGenerator.generate(schemaElement) + .writeTo(processingEnv.getFiler()); + // .writeTo(System.out); + } catch (Throwable t) { + context.error(ExceptionUtils.getRootCauseMessage(t)); + t.printStackTrace(System.err); + } + } // Depends on serializerElement to generate Serializer impl + // Depends on parserElement to generate Parser impl if (schemaElement.tableElement.declaredType != null) { try { tableCodeGenerator.generate(schemaElement) diff --git a/table/annotations/src/main/java/com/riiablo/table/annotation/Parser.java b/table/annotations/src/main/java/com/riiablo/table/annotation/Parser.java new file mode 100644 index 00000000..a24d8dc7 --- /dev/null +++ b/table/annotations/src/main/java/com/riiablo/table/annotation/Parser.java @@ -0,0 +1,23 @@ +package com.riiablo.table.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that the specified {@link Schema schema} should use the given + * {@link #value() parser} in lieu of generating one. The parser implementation + * should have the schema set as its generic parameter. + */ +@Documented +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.TYPE) +public @interface Parser { + /** + * A parser implementation that should be used by this + * {@link Schema schema} in lieu of generating one. + */ + Class> value(); +} diff --git a/table/core/src/main/java/com/riiablo/table/Parser.java b/table/core/src/main/java/com/riiablo/table/Parser.java new file mode 100644 index 00000000..f6e53027 --- /dev/null +++ b/table/core/src/main/java/com/riiablo/table/Parser.java @@ -0,0 +1,14 @@ +package com.riiablo.table; + +import java.io.IOException; + +/** + * Defines behaviors necessary to parse a record to and from a tsv format. + * + * @param record type + */ +public interface Parser { + void parseFields(final TsvParser parser); + boolean hasNext(final TsvParser parser) throws IOException; + void parseRecord(final R record, final TsvParser parser); +} diff --git a/table/core/src/main/java/com/riiablo/table/TsvParser.java b/table/core/src/main/java/com/riiablo/table/TsvParser.java new file mode 100644 index 00000000..20491223 --- /dev/null +++ b/table/core/src/main/java/com/riiablo/table/TsvParser.java @@ -0,0 +1,16 @@ +package com.riiablo.table; + +import java.io.IOException; + +public interface TsvParser { + int cacheLine() throws IOException; + int fieldId(String fieldName); + byte parseByte(int fieldId); + short parseShort(int fieldId); + int parseInt(int fieldId); + long parseLong(int fieldId); + boolean parseBoolean(int fieldId); + float parseFloat(int fieldId); + double parseDouble(int fieldId); + String parseString(int fieldId); +} diff --git a/table/integration/src/main/java/com/riiablo/table/schema/MonStatsParserImpl.java b/table/integration/src/main/java/com/riiablo/table/schema/MonStatsParserImpl.java new file mode 100644 index 00000000..2f29e612 --- /dev/null +++ b/table/integration/src/main/java/com/riiablo/table/schema/MonStatsParserImpl.java @@ -0,0 +1,22 @@ +package com.riiablo.table.schema; + +import java.io.IOException; +import javax.annotation.Generated; + +import com.riiablo.table.Parser; +import com.riiablo.table.TsvParser; + +public abstract class MonStatsParserImpl implements Parser { + @Override + public boolean hasNext(TsvParser parser) throws IOException { + return parser.cacheLine() != -1; + } + + // TODO: performance improvement of sorting calls by fieldId + // create Function[numFields]: (record) -> record. = parser.parse(fieldId) + @Generated(value = "") + public void parseRecord(final MonStats record, final TsvParser parser) { + record.A1MaxD[0] = parser.parseInt(0); + record.A1MaxD[1] = parser.parseInt(5); + } +}