mirror of
https://github.com/collinsmith/riiablo.git
synced 2025-07-04 15:27:30 +07:00
Created Parser interface and generation
This commit is contained in:
@ -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!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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();
|
||||||
|
}
|
14
table/core/src/main/java/com/riiablo/table/Parser.java
Normal file
14
table/core/src/main/java/com/riiablo/table/Parser.java
Normal 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);
|
||||||
|
}
|
16
table/core/src/main/java/com/riiablo/table/TsvParser.java
Normal file
16
table/core/src/main/java/com/riiablo/table/TsvParser.java
Normal 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);
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user