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

uk.autores.processors.ContextFactory Maven / Gradle / Ivy

There is a newer version: 11.0.35-beta
Show newest version
// Copyright 2024 https://github.com/autores-uk/autores/blob/main/LICENSE.txt
// SPDX-License-Identifier: Apache-2.0
package uk.autores.processors;

import uk.autores.Processing;
import uk.autores.handling.*;
import uk.autores.naming.Namer;

import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.Name;
import javax.lang.model.type.MirroredTypeException;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import javax.tools.JavaFileManager;
import javax.tools.StandardLocation;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.util.Collections.emptyList;
import static uk.autores.processors.Compare.nullOrEmpty;

abstract class ContextFactory {

    private final ProcessingEnvironment env;
    private final Class single;
    private final Class repeating;

    ContextFactory(ProcessingEnvironment env, Class single, Class repeating) {
        this.env = env;
        this.single = single;
        this.repeating = repeating;
    }

    boolean supported(Name annotationName) {
        return Compare.sameSeq(annotationName, single.getName())
                || Compare.sameSeq(annotationName, repeating.getName());
    }

    List contexts(Name name, Element annotated) {
        List contexts = new ArrayList<>();
        List annotations;
        if (Compare.sameSeq(name, single.getName())) {
            annotations = Collections.singletonList(annotated.getAnnotation(single));
        } else {
            R r = annotated.getAnnotation(repeating);
            annotations = Arrays.asList(expand(r));
        }
        for (S s : annotations) {
            Processing p = processing(s);
            Context builder = init(s, annotated, p);
            Handler handler = handler(s);
            contexts.add(new Pair(builder, handler));
        }
        return contexts;
    }

    abstract Handler handler(S single);

    abstract List config(S s);

    abstract S[] expand(R repeating);

    abstract Processing processing(S single);

    abstract String[] resources(S single);

    private Context init(S s, Element annotated, Processing p) {
        Pkg pkg = pkg(annotated);
        List r = resources(resources(s), p.locations(), pkg, annotated);
        Namer namer = instance(p::namer);
        List configs = config(s);
        List locations = locationList(p.locations());
        return Context.builder()
                .setAnnotated(annotated)
                .setConfig(configs)
                .setEnv(env)
                .setLocation(locations)
                .setNamer(namer)
                .setPkg(pkg)
                .setResources(r)
                .build();
    }

    private Pkg pkg(Element annotated) {
        Name qualified = env.getElementUtils()
                .getPackageOf(annotated)
                .getQualifiedName();
        return Pkg.named(qualified);
    }


    @SuppressWarnings("unchecked")
    protected  T instance(Supplier> s) {
        TypeMirror mirror = mirror(s);
        String className = mirror.toString();

        try {
            Class c = getClass()
                    .getClassLoader()
                    .loadClass(className);
            return (T) c.getConstructor()
                    .newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private TypeMirror mirror(Supplier s) {
        // https://hauchee.blogspot.com/2015/12/compile-time-annotation-processing-getting-class-value.html
        try {
            s.get();
        } catch (MirroredTypeException e) {
            return e.getTypeMirror();
        }
        throw new AssertionError();
    }

    private List resources(String[] res,
                             String[] locations,
                             Pkg annotationPackage,
                             Element annotated) {
        List resources = new ArrayList<>(res.length);
        CharSequence pkg = "";
        CharSequence value = "";
        try {
            Filer filer = env.getFiler();

            for (String resource : res) {
                if (nullOrEmpty(resource)) {
                    String msg = "Resource paths cannot be null or empty";
                    env.getMessager()
                            .printMessage(Diagnostic.Kind.ERROR, msg, annotated);
                    return emptyList();
                }

                pkg = ResourceFiling.pkg(annotationPackage, resource);
                value = ResourceFiling.relativeName(resource);
                FileObject fo = getResource(filer, locations, pkg, value);
                try (InputStream is = fo.openInputStream()) {
                    // NOOP; if file can be opened it exists
                    assert is != null;
                }
                resources.add(new Resource(fo::openInputStream, resource));
            }

        } catch (Exception e) {
            String msg = Errors.resourceErrorMessage(e, value, pkg);
            env.getMessager()
                    .printMessage(Diagnostic.Kind.ERROR, msg, annotated);
            return emptyList();
        }

        return resources;
    }

    private FileObject getResource(Filer filer, String[] locations, CharSequence pkg, CharSequence value) throws IOException {
        Set errors = new LinkedHashSet<>();
        for (String location : locations) {
            JavaFileManager.Location jfml = StandardLocation.locationFor(location);
            try {
                FileObject fo = filer.getResource(jfml, pkg, value);
                try (InputStream is = fo.openInputStream()) {
                    // NOOP; if file can be opened it exists
                    assert is != null;
                }
                return fo;
            } catch (Exception e) {
                errors.add(e.toString());
            }
        }
        String reason = String.join("; ", errors);
        throw new IOException(reason);
    }

    private List locationList(String[] locations) {
        return Stream.of(locations)
                .map(StandardLocation::locationFor)
                .collect(Collectors.toList());
    }

    static final class Pair {
        final Context context;
        final Handler handler;

        Pair(Context context, Handler handler) {
            this.context = context;
            this.handler = handler;
        }
    }
}