Created Parser interface and generation

This commit is contained in:
Collin Smith
2020-12-16 11:36:11 -08:00
parent 1308eb410c
commit c82e615772
8 changed files with 319 additions and 0 deletions

View File

@ -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!");
}
}
}

View File

@ -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();
}
}

View File

@ -69,12 +69,14 @@ final class SchemaElement {
TableElement tableElement = TableElement.get(context, typeElement); TableElement tableElement = TableElement.get(context, typeElement);
SerializerElement serializerElement = SerializerElement.get(context, typeElement); SerializerElement serializerElement = SerializerElement.get(context, typeElement);
ParserElement parserElement = ParserElement.get(context, typeElement);
return new SchemaElement( return new SchemaElement(
annotation, annotation,
typeElement, typeElement,
tableElement, tableElement,
serializerElement, serializerElement,
parserElement,
primaryKeyFieldElement, primaryKeyFieldElement,
fields); fields);
} }
@ -124,27 +126,42 @@ final class SchemaElement {
final TypeElement element; final TypeElement element;
final TableElement tableElement; final TableElement tableElement;
final SerializerElement serializerElement; final SerializerElement serializerElement;
final ParserElement parserElement;
final FieldElement primaryKeyFieldElement; final FieldElement primaryKeyFieldElement;
final Collection<FieldElement> fields; final Collection<FieldElement> fields;
final int numFields;
ClassName serializerClassName; ClassName serializerClassName;
ClassName parserClassName;
SchemaElement( SchemaElement(
Schema annotation, Schema annotation,
TypeElement element, TypeElement element,
TableElement tableElement, TableElement tableElement,
SerializerElement serializerElement, SerializerElement serializerElement,
ParserElement parserElement,
FieldElement primaryKeyFieldElement, FieldElement primaryKeyFieldElement,
Collection<FieldElement> fields) { Collection<FieldElement> fields) {
this.annotation = annotation; this.annotation = annotation;
this.element = element; this.element = element;
this.tableElement = tableElement; this.tableElement = tableElement;
this.serializerElement = serializerElement; this.serializerElement = serializerElement;
this.parserElement = parserElement;
this.primaryKeyFieldElement = primaryKeyFieldElement; this.primaryKeyFieldElement = primaryKeyFieldElement;
this.fields = fields; this.fields = fields;
this.numFields = countNumFields(fields);
if (serializerElement.serializerImplElement != null) { if (serializerElement.serializerImplElement != null) {
serializerClassName = ClassName.get(serializerElement.serializerImplElement); serializerClassName = ClassName.get(serializerElement.serializerImplElement);
} }
if (parserElement.parserImplElement != null) {
parserClassName = ClassName.get(parserElement.parserImplElement);
}
}
final int countNumFields(Collection<FieldElement> fields) {
int numFields = 0;
for (FieldElement field : fields) numFields += field.fieldNames.length;
return numFields;
} }
@Override @Override
@ -154,6 +171,8 @@ final class SchemaElement {
.append("tableElement", tableElement) .append("tableElement", tableElement)
.append("serializerElement", serializerElement) .append("serializerElement", serializerElement)
.append("serializerClassName", serializerClassName) .append("serializerClassName", serializerClassName)
.append("ParserElement", parserElement)
.append("serializerClassName", serializerClassName)
.append("primaryKeyFieldElement", primaryKeyFieldElement) .append("primaryKeyFieldElement", primaryKeyFieldElement)
.toString(); .toString();
} }

View File

@ -49,6 +49,8 @@ public class SchemaProcessor extends AbstractProcessor {
context, "com.riiablo.excel.table"); context, "com.riiablo.excel.table");
SerializerCodeGenerator serializerCodeGenerator = new SerializerCodeGenerator( SerializerCodeGenerator serializerCodeGenerator = new SerializerCodeGenerator(
context, "com.riiablo.excel.serializer"); context, "com.riiablo.excel.serializer");
ParserCodeGenerator parserCodeGenerator = new ParserCodeGenerator(
context, "com.riiablo.excel.parser");
for (Element element : roundEnv.getElementsAnnotatedWith(Schema.class)) { for (Element element : roundEnv.getElementsAnnotatedWith(Schema.class)) {
if (element.getKind() != ElementKind.CLASS) { if (element.getKind() != ElementKind.CLASS) {
context.error(element, "{} can only be applied to classes", Schema.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); 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 serializerElement to generate Serializer impl
// Depends on parserElement to generate Parser impl
if (schemaElement.tableElement.declaredType != null) { if (schemaElement.tableElement.declaredType != null) {
try { try {
tableCodeGenerator.generate(schemaElement) tableCodeGenerator.generate(schemaElement)

View File

@ -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<? extends com.riiablo.table.Parser<?>> value();
}

View File

@ -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 <R> record type
*/
public interface Parser<R> {
void parseFields(final TsvParser parser);
boolean hasNext(final TsvParser parser) throws IOException;
void parseRecord(final R record, final TsvParser parser);
}

View File

@ -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);
}

View File

@ -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<MonStats> {
@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.<field> = parser.parse<type>(fieldId)
@Generated(value = "")
public void parseRecord(final MonStats record, final TsvParser parser) {
record.A1MaxD[0] = parser.parseInt(0);
record.A1MaxD[1] = parser.parseInt(5);
}
}