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);
|
||||
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<FieldElement> fields;
|
||||
final int numFields;
|
||||
|
||||
ClassName serializerClassName;
|
||||
ClassName parserClassName;
|
||||
|
||||
SchemaElement(
|
||||
Schema annotation,
|
||||
TypeElement element,
|
||||
TableElement tableElement,
|
||||
SerializerElement serializerElement,
|
||||
ParserElement parserElement,
|
||||
FieldElement primaryKeyFieldElement,
|
||||
Collection<FieldElement> 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<FieldElement> 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();
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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