therian.buildweaver.StandardOperatorsProcessor.java Source code

Java tutorial

Introduction

Here is the source code for therian.buildweaver.StandardOperatorsProcessor.java

Source

/*
 *  Copyright the original author or authors.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package therian.buildweaver;

import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.util.Collections;
import java.util.EnumSet;
import java.util.LinkedHashSet;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.NestingKind;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
import javax.tools.FileObject;
import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.CharEncoding;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.text.StrSubstitutor;
import org.kohsuke.MetaInfServices;

/**
 * Creates package-access class therian.StandardOperators with all elements annotated by {@link StandardOperator}.
 */
@MetaInfServices(Processor.class)
@SupportedAnnotationTypes("therian.buildweaver.StandardOperator")
public class StandardOperatorsProcessor extends AbstractProcessor {
    public static final String TARGET_CLASSNAME = "therian.StandardOperators";
    public static final String TEMPLATE_RESOURCE = "/therian/StandardOperators";

    private final Set<Element> originatingElements = new LinkedHashSet<>();
    private final Set<String> operators = new LinkedHashSet<>();

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        try {
            final FileObject resource = processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT,
                    StringUtils.substringBeforeLast(TARGET_CLASSNAME, ".").replace('.', '/'),
                    StringUtils.substringAfterLast(TARGET_CLASSNAME, ".") + ".class");

            if (resource.getLastModified() > 0L) {
                processingEnv.getMessager().printMessage(Kind.NOTE,
                        String.format("%s already generated", TARGET_CLASSNAME));
                return false;
            }
        } catch (IOException e1) {
            // expected, swallow
        }
        try {
            ClassUtils.getClass(TARGET_CLASSNAME);
            processingEnv.getMessager().printMessage(Kind.ERROR,
                    String.format("%s exists on classpath", TARGET_CLASSNAME));
            return false;
        } catch (ClassNotFoundException e) {
            // expected, swallow
        }

        if (roundEnv.processingOver()) {
            write();
            return true;
        }
        for (TypeElement ann : annotations) {
            final Set<? extends Element> standardOperatorElements = roundEnv.getElementsAnnotatedWith(ann);
            originatingElements.addAll(standardOperatorElements);

            for (Element element : standardOperatorElements) {
                Validate.validState(isValidStandardOperator(element), "%s is not a valid @StandardOperator",
                        appendTo(new StringBuilder(), element).toString());

                if (element.getKind() == ElementKind.CLASS) {
                    operators.add(appendTo(new StringBuilder("new "), element).append("()").toString());
                }
                if (element.getKind() == ElementKind.METHOD) {
                    operators.add(appendTo(new StringBuilder(), element).append("()").toString());
                }
                if (element.getKind() == ElementKind.FIELD) {
                    operators.add(appendTo(new StringBuilder(), element).toString());
                }
            }
        }
        return true;
    }

    private void write() {
        InputStream templateStream = null;
        Writer targetWriter = null;
        try {
            templateStream = getClass().getResourceAsStream(TEMPLATE_RESOURCE);
            final String template = IOUtils.toString(templateStream, CharEncoding.UTF_8);
            final String output = StrSubstitutor.replace(template,
                    Collections.singletonMap("operators", StringUtils.join(operators, ",\n")));
            final JavaFileObject target = processingEnv.getFiler().createSourceFile(TARGET_CLASSNAME,
                    originatingElements.toArray(new Element[originatingElements.size()]));
            targetWriter = target.openWriter();
            IOUtils.write(output, targetWriter);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            IOUtils.closeQuietly(templateStream);
            IOUtils.closeQuietly(targetWriter);
        }

    }

    private static ExecutableElement findDefaultConstructor(TypeElement t) {
        for (Element element : t.getEnclosedElements()) {
            if (element.getKind() == ElementKind.CONSTRUCTOR) {
                ExecutableElement cs = (ExecutableElement) element;
                if (cs.getParameters().size() == 0 && cs.getModifiers().contains(Modifier.PUBLIC)) {
                    return cs;
                }
            }
        }
        return null;
    }

    /**
     * Must be a public static concrete class with a default constructor, public static zero-arg method, or public
     * static final field.
     *
     * @param e
     * @return boolean
     */
    private static boolean isValidStandardOperator(final Element e) {
        if (e.getKind() == ElementKind.FIELD) {
            return e.getModifiers().containsAll(EnumSet.of(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL));
        }
        if (e.getKind() == ElementKind.METHOD) {
            return e.getModifiers().containsAll(EnumSet.of(Modifier.PUBLIC, Modifier.STATIC))
                    && ((ExecutableElement) e).getParameters().isEmpty();
        }
        if (e.getKind() == ElementKind.CLASS) {
            if (e.getModifiers().contains(Modifier.ABSTRACT) || findDefaultConstructor((TypeElement) e) == null) {
                return false;
            }
            Element current = e;
            while (current.getKind() == ElementKind.CLASS) {
                final TypeElement t = (TypeElement) current;
                if (t.getNestingKind() == NestingKind.TOP_LEVEL) {
                    return true;
                }
                if (t.getNestingKind() == NestingKind.MEMBER && t.getModifiers().contains(Modifier.STATIC)) {
                    current = t.getEnclosingElement();
                    continue;
                }
                break;
            }
        }
        return false;
    }

    private static StringBuilder appendTo(StringBuilder buf, Element e) {
        final Element parent = e.getEnclosingElement();
        if (parent != null) {
            appendTo(buf, parent).append('.');
        }
        return buf.append(
                e.getKind() == ElementKind.PACKAGE ? ((PackageElement) e).getQualifiedName() : e.getSimpleName());
    }
}