uk.autores.processors.ContextFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of annotations Show documentation
Show all versions of annotations Show documentation
Annotation driven library for handling embedded resources
// 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;
}
}
}