All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.java.truecommons.annotations.processing.ServiceImplementationProcessor Maven / Gradle / Ivy

Go to download

Provides common annotations with processors. You should use the scope "provided" when adding this module as a dependency.

The newest version!
/*
 * Copyright (C) 2005-2015 Schlichtherle IT Services.
 * All rights reserved. Use is subject to license terms.
 */
package net.java.truecommons.annotations.processing;

import java.io.*;
import java.util.*;
import java.util.Map.Entry;
import javax.annotation.processing.*;
import javax.lang.model.*;
import javax.lang.model.element.*;
import static javax.lang.model.element.ElementKind.*;
import static javax.lang.model.element.Modifier.*;
import javax.lang.model.type.*;
import javax.lang.model.util.*;
import javax.tools.*;
import static javax.tools.Diagnostic.Kind.*;
import static javax.tools.StandardLocation.*;
import net.java.truecommons.annotations.ServiceImplementation;
import net.java.truecommons.annotations.ServiceSpecification;

/**
 * Processes the {@link ServiceImplementation} annotation.
 * If any of the processing options
 * {@code net.java.truecommons.annotations.verbose} or
 * {@code net.java.truecommons.annotations.processing.verbose}
 * is set to {@code true} (whereby case is ignored), then this processor emits
 * a note for every service implementation class it registers in a service
 * provider configuration file in {@code META-INF/services}.
 *
 * @since  TrueCommons 2.1
 * @author Christian Schlichtherle
 */
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes("*")
@SupportedOptions({
    "net.java.truecommons.annotations.verbose",
    "net.java.truecommons.annotations.processing.verbose"
})
public final class ServiceImplementationProcessor extends ServiceProcessor {

    private static final Comparator TYPE_ELEMENT_COMPARATOR =
            new Comparator() {
                @Override
                public int compare(TypeElement o1, TypeElement o2) {
                    return o1.getQualifiedName().toString().compareTo(
                            o2.getQualifiedName().toString());
                }
            };

    private boolean verbose;

    @Override
    public void init(final ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        final Set supported = getSupportedOptions();
        final Map present = processingEnv.getOptions();
        verbose = false;
        for (final String option : supported)
            verbose |= Boolean.parseBoolean(present.get(option));
    }

    @Override
    boolean isDebugEnabled() { return verbose; }

    @Override
    public boolean process(
            final Set annotations,
            final RoundEnvironment roundEnv) {
        final Registry registry = new Registry();
        for (final Element elem : roundEnv.getElementsAnnotatedWith(ServiceImplementation.class)) {
            final TypeElement impl = (TypeElement) elem;
            if (valid(impl, impl))
                if (!processAnnotations(impl, registry))
                    if (!processTypeHierarchy(impl, registry))
                        error("Cannot find any specification.", impl);
        }
        registry.persist();
        return false; // critical!
    }

    boolean valid(final TypeElement impl, final Element loc) {
        {
            final Set modifiers = impl.getModifiers();
            if (!modifiers.contains(PUBLIC)
                    || modifiers.contains(ABSTRACT)
                    || impl.getKind() != CLASS)
                return error("Not a public and non-abstract class.", loc);
            if (impl.getNestingKind().isNested()) {
                if (!modifiers.contains(STATIC))
                    return error("Impossible to instantiate without an instance of the enclosing class.", loc);
                //warning("Bad practice: Not a top-level class.", loc);
            }
        }
        final Collection
                ctors = new LinkedList();
        for (final Element elem : impl.getEnclosedElements())
            if (elem.getKind() == CONSTRUCTOR)
                ctors.add((ExecutableElement) elem);
        return ctors.isEmpty() || valid(ctors)
                || error("No public constructor with zero parameters available.", loc);
    }

    private boolean valid(final Collection ctors) {
        for (final ExecutableElement ctor : ctors) if (valid(ctor)) return true;
        return false;
    }

    private boolean valid(final ExecutableElement ctor) {
        return ctor.getModifiers().contains(PUBLIC)
                && ctor.getParameters().isEmpty();
    }

    private boolean processAnnotations(
            final TypeElement impl,
            final Registry registry) {
        final DeclaredType implType = (DeclaredType) impl.asType();
        for (final AnnotationMirror mirror
                : processingEnv.getElementUtils().getAllAnnotationMirrors(impl)) {
            if (!ServiceImplementation.class.getName().equals(
                    ((TypeElement) mirror.getAnnotationType().asElement()).getQualifiedName().toString()))
                continue;
            final Map
                    values = mirror.getElementValues();
            for (final Entry entry
                    : values.entrySet()) {
                final ExecutableElement element = entry.getKey();
                if (!"value".equals(element.getSimpleName().toString()))
                    continue;

                class Visitor extends SimpleAnnotationValueVisitor6 {

                    Visitor() { super(false); }

                    @Override
                    public Boolean visitType(
                            final TypeMirror type,
                            final Void p) {
                        if (processingEnv.getTypeUtils().isAssignable(implType, type))
                            registry.add(impl, (TypeElement) ((DeclaredType) type).asElement());
                        else
                            error("Unassignable to " + type + ".", impl);
                        return Boolean.TRUE;
                    }

                    @Override
                    public Boolean visitArray(
                            final List values,
                            final Void p) {
                        boolean found = false;
                        for (final AnnotationValue value : values)
                            found |= value.accept(this, p);
                        return found;
                    }
                } // Visitor

                return entry.getValue().accept(new Visitor(), null);
            }
        }
        return false;
    }

    private boolean processTypeHierarchy(
            final TypeElement impl,
            final Registry registry) {

        class Visitor extends SimpleTypeVisitor6 {

            Visitor() { super(false); }

            @Override
            public Boolean visitDeclared(DeclaredType type, Void p) {
                boolean found = false;
                final TypeElement elem = (TypeElement) type.asElement();
                if (null != elem.getAnnotation(ServiceSpecification.class)) {
                    found = true;
                    registry.add(impl, elem);
                }
                for (final TypeMirror m : elem.getInterfaces())
                    found |= m.accept(this, p);
                return elem.getSuperclass().accept(this, p) || found;
            }
        } // Visitor

        return impl.asType().accept(new Visitor(), null);
    }

    private final class Registry {
        final Elements elements = processingEnv.getElementUtils();
        final Map>
            services = new HashMap>();

        void add(final TypeElement impl, final TypeElement spec) {
            Collection coll = services.get(spec);
            if (null == coll) coll = new TreeSet(TYPE_ELEMENT_COMPARATOR);
            coll.add(impl);
            services.put(spec, coll);
        }

        void persist() {
            final Filer filer = processingEnv.getFiler();
            final Messager messager = getMessager();
            for (final Entry> entry : services.entrySet()) {
                final TypeElement spec = entry.getKey();
                final Collection coll = entry.getValue();
                if (coll.isEmpty()) continue;
                final String path = "META-INF/services/" + name(spec);
                try {
                    final FileObject fo = filer
                            .createResource(CLASS_OUTPUT, "", path);
                    final Writer w = fo.openWriter();
                    try {
                        for (final TypeElement impl : coll) {
                            w.append(name(impl)).append("\n");
                            debug(String.format("Registered at: %s", path), impl);
                        }
                    } finally {
                        w.close();
                    }
                } catch (final IOException ex) {
                    messager.printMessage(ERROR, String.format("Failed to register %d service implementation class(es) at: %s: %s" , coll.size(), path, ex.getMessage()));
                }
            }
        }

        CharSequence name(TypeElement elem) {
            return elements.getBinaryName(elem);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy