/*
* Copyright 2015 Workday, Inc.
*
* This software is available under the MIT license.
* Please see the LICENSE.txt file in this project.
*/
package com.workday.autoparse.xml.codegen;
import com.squareup.javawriter.JavaWriter;
import com.workday.autoparse.xml.annotations.XmlPostParse;
import com.workday.autoparse.xml.parser.Attributes;
import com.workday.autoparse.xml.parser.GeneratedClassNames;
import com.workday.autoparse.xml.parser.ParseException;
import com.workday.autoparse.xml.parser.ParserUtils;
import com.workday.autoparse.xml.parser.UnexpectedChildException;
import com.workday.autoparse.xml.parser.UnexpectedElementHandler;
import com.workday.autoparse.xml.parser.UnknownElementException;
import com.workday.autoparse.xml.parser.XmlElementParser;
import com.workday.autoparse.xml.parser.XmlStreamReader;
import com.workday.autoparse.xml.utils.CollectionUtils;
import com.workday.autoparse.xml.utils.Preconditions;
import com.workday.meta.MetaTypes;
import java.io.IOException;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
/**
* Generates implementations of {@link XmlElementParser}.
*
* @author nathan.taylor
* @since 2013-9-17- 16:56
*/
class XmlElementParserGenerator {
private final ProcessingEnvironment processingEnv;
private final TypeElement classElement;
private final ParseAttributesMethodWriter parseAttributesMethodWriter;
private final ParseChildrenMethodWriter parseChildrenMethodWriter;
XmlElementParserGenerator(ProcessingEnvironment processingEnv, TypeElement classElement) {
this.processingEnv = processingEnv;
this.classElement = classElement;
MetaTypes metaTypes = new MetaTypes(processingEnv);
AttributesAndElements attributesAndElements =
new AttributesAndElements(processingEnv, classElement);
parseAttributesMethodWriter =
new ParseAttributesMethodWriter(attributesAndElements, processingEnv, metaTypes);
parseChildrenMethodWriter =
new ParseChildrenMethodWriter(attributesAndElements,
processingEnv,
metaTypes,
classElement);
}
public void generateParser()
throws IOException {
String parserName =
classElement.getQualifiedName().toString() + GeneratedClassNames.PARSER_SUFFIX;
JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(parserName);
JavaWriter writer = new JavaWriter(sourceFile.openWriter());
writer.emitPackage(processingEnv.getElementUtils()
.getPackageOf(classElement)
.getQualifiedName()
.toString());
writer.emitImports(getStandardImports());
writer.emitEmptyLine();
String xmlElementParserName =
JavaWriter.type(XmlElementParser.class, classElement.getSimpleName().toString());
writer.beginType(parserName,
"class",
EnumSet.of(Modifier.PUBLIC, Modifier.FINAL),
null,
xmlElementParserName);
writer.emitEmptyLine();
writer.emitField(parserName,
"INSTANCE",
EnumSet.of(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL),
String.format("new %s()", writer.compressType(parserName)));
writer.emitEmptyLine();
// Constructor
writer.beginMethod(null, parserName, EnumSet.of(Modifier.PRIVATE));
writer.endMethod();
writer.emitEmptyLine();
writeParseElementMethod(writer);
writer.emitEmptyLine();
parseAttributesMethodWriter.writeParseAttributesMethod(classElement, writer);
writer.emitEmptyLine();
parseChildrenMethodWriter.writeParseChildrenMethod(classElement, writer);
writer.emitEmptyLine();
writer.endType();
writer.close();
}
private Set<String> getStandardImports() {
Set<String> results = new HashSet<>();
results.add(List.class.getCanonicalName());
results.add(Attributes.class.getCanonicalName());
results.add(Preconditions.class.getCanonicalName());
results.add(CollectionUtils.class.getCanonicalName());
results.add(ParseException.class.getCanonicalName());
results.add(ParserUtils.class.getCanonicalName());
results.add(Set.class.getCanonicalName());
results.add(UnexpectedChildException.class.getCanonicalName());
results.add(UnexpectedElementHandler.class.getCanonicalName());
results.add(UnknownElementException.class.getCanonicalName());
results.add(XmlElementParser.class.getCanonicalName());
results.add(XmlStreamReader.class.getCanonicalName());
return results;
}
private void writeParseElementMethod(JavaWriter writer)
throws IOException {
List<String> parameters =
CollectionUtils.newArrayList(XmlStreamReader.class.getSimpleName(), "reader");
List<String> throwsTypes = CollectionUtils.newArrayList(
ParseException.class.getSimpleName(),
UnknownElementException.class.getSimpleName(),
UnexpectedChildException.class.getSimpleName());
writer.emitAnnotation(Override.class);
writer.beginMethod(classElement.getSimpleName().toString(),
"parseElement",
EnumSet.of(Modifier.PUBLIC),
parameters,
throwsTypes);
writer.emitStatement("%s object = new %s()",
classElement.getSimpleName(),
classElement.getSimpleName());
writer.emitStatement("parseAttributes(object, reader)");
writer.emitStatement("parseChildren(object, reader)");
writer.emitStatement(
"Preconditions.checkState(reader.isEndElement(), \"Expected to be at an end "
+ "element\")");
writer.emitStatement("reader.next()");
writer.beginControlFlow("if (reader.isCharacters())");
writer.emitStatement("reader.nextTag()");
writer.endControlFlow();
ExecutableElement postParseMethod = findPostParseMethod();
if (postParseMethod != null) {
writer.emitStatement("object.%s()", postParseMethod.getSimpleName());
}
writer.emitStatement("return object");
writer.endMethod();
}
private ExecutableElement findPostParseMethod() {
ExecutableElement result = null;
List<? extends Element> allMembers =
processingEnv.getElementUtils().getAllMembers(classElement);
for (Element e : allMembers) {
if (e.getAnnotation(XmlPostParse.class) != null) {
if (result != null) {
String errorMessage =
String.format("Found multiple methods annotated with @%s",
XmlPostParse.class.getSimpleName());
processingEnv.getMessager()
.printMessage(Diagnostic.Kind.ERROR, errorMessage, e);
}
result = (ExecutableElement) e;
if (result.getParameters().size() > 0) {
String errorMessage =
String.format("A method annotated with @%s must take no parameters.",
XmlPostParse.class.getSimpleName());
processingEnv.getMessager()
.printMessage(Diagnostic.Kind.ERROR, errorMessage, result);
}
if (result.getModifiers().contains(Modifier.PRIVATE)) {
String errorMessage =
String.format("A method annotated with @%s must be non-private.",
XmlPostParse.class.getSimpleName());
processingEnv.getMessager()
.printMessage(Diagnostic.Kind.ERROR, errorMessage, result);
}
}
}
return result;
}
}