/* * 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.workday.autoparse.xml.annotations.XmlAttribute; import com.workday.autoparse.xml.annotations.XmlChildElement; import com.workday.autoparse.xml.annotations.XmlElement; import com.workday.autoparse.xml.annotations.XmlParserPartition; import com.workday.autoparse.xml.annotations.XmlPostParse; import com.workday.autoparse.xml.annotations.XmlTextContent; import com.workday.autoparse.xml.annotations.XmlUnknownElement; import com.workday.autoparse.xml.utils.CollectionUtils; import com.workday.autoparse.xml.utils.StringUtils; import com.workday.meta.PackageTree; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.util.ElementFilter; import javax.tools.Diagnostic; /** * A {@link javax.annotation.processing.Processor} that generates code used to inflate objects * annotated with {@link XmlElement}. This processor will be invoked during compilation. Clients * will have no need to instantiate or make calls to this class. * * @author nathan.taylor * @since 2013-09-30 */ public class AutoparseProcessor extends AbstractProcessor { private Map<String, Collection<TypeElement>> parserMap = new HashMap<>(); @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { if (annotations == null || annotations.isEmpty()) { return false; } // Generate parsers for classes annotated with @XmlElement Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(XmlElement.class); for (Element element : elements) { if (element.getKind() == ElementKind.CLASS) { addClassToParseMap((TypeElement) element); generateClassParser((TypeElement) element); } } // Generate parsers for classes annotated with @XmlUnknownElement Set<? extends Element> elementsForUnknown = roundEnv.getElementsAnnotatedWith(XmlUnknownElement.class); for (Element element : elementsForUnknown) { if (element.getAnnotation(XmlElement.class) == null) { generateClassParser((TypeElement) element); } } // Generate ParserMaps Set<PackageElement> partitionPackageElements = ElementFilter.packagesIn( roundEnv.getElementsAnnotatedWith(XmlParserPartition.class)); PackageTree packageTree = new PackageTree(processingEnv.getElementUtils(), partitionPackageElements); generateParserMaps(packageTree); return true; } @Override public Set<String> getSupportedAnnotationTypes() { return CollectionUtils.newHashSet(XmlElement.class.getCanonicalName(), XmlUnknownElement.class.getCanonicalName(), XmlPostParse.class.getCanonicalName(), XmlChildElement.class.getCanonicalName(), XmlAttribute.class.getCanonicalName(), XmlTextContent.class.getCanonicalName(), XmlParserPartition.class.getCanonicalName()); } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } private void addClassToParseMap(TypeElement element) { XmlElement annotation = element.getAnnotation(XmlElement.class); for (String parseKey : annotation.value()) { if (StringUtils.isNotEmpty(parseKey)) { putInCollectionMap(parserMap, parseKey, element); } } } private <K, V> void putInCollectionMap(Map<K, Collection<V>> map, K key, V value) { Collection<V> collection = map.get(key); if (collection == null) { collection = new ArrayList<>(); map.put(key, collection); } collection.add(value); } private void generateClassParser(TypeElement element) { try { new XmlElementParserGenerator(processingEnv, element).generateParser(); } catch (IOException e) { processingEnv.getMessager() .printMessage(Diagnostic.Kind.ERROR, e.getMessage(), element); } } private void generateParserMaps(PackageTree packageTree) { Map<PackageElement, Map<String, TypeElement>> mapsByPackage = splitParserMap(packageTree); for (Map.Entry<PackageElement, Map<String, TypeElement>> entry : mapsByPackage.entrySet()) { ParserMapGenerator generator = new ParserMapGenerator(processingEnv, entry.getKey(), entry.getValue()); try { generator.generateParseMap(); } catch (IOException e) { processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage()); } } } private Map<PackageElement, Map<String, TypeElement>> splitParserMap(PackageTree packageTree) { Map<PackageElement, Map<String, TypeElement>> mapsByPackage = new HashMap<>(); for (Map.Entry<String, Collection<TypeElement>> entry : parserMap.entrySet()) { String parseKey = entry.getKey(); for (TypeElement element : entry.getValue()) { // A null matching package means that this element goes into the default partition, // which is keyed by null here. PackageElement matchingPackage = packageTree.getMatchingPackage(element); Map<String, TypeElement> map = mapsByPackage.get(matchingPackage); if (map == null) { map = new HashMap<>(); mapsByPackage.put(matchingPackage, map); } TypeElement previousValue = map.put(parseKey, element); if (previousValue != null) { String packageString = matchingPackage != null ? String.format(Locale.US, "partition under package '%s'", matchingPackage.getQualifiedName()) : "the default partition"; String errorMessage = String.format("%s and %s both tried to map to tag name \"%s\" in %s.", element.getQualifiedName(), previousValue.getQualifiedName(), parseKey, packageString); processingEnv.getMessager() .printMessage(Diagnostic.Kind.ERROR, errorMessage, element); } } } return mapsByPackage; } }