net.jbock.common.Util Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jbock-compiler Show documentation
Show all versions of jbock-compiler Show documentation
jbock annotation processor
package net.jbock.common;
import io.jbock.simple.Inject;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.STATIC;
import static javax.lang.model.element.NestingKind.MEMBER;
import static javax.lang.model.util.ElementFilter.constructorsIn;
import static net.jbock.common.TypeTool.AS_TYPE_ELEMENT;
public final class Util {
private final SafeTypes types;
private final TypeTool tool;
@Inject
public Util(SafeTypes types, TypeTool tool) {
this.types = types;
this.tool = tool;
}
/* Left-Optional
*/
public Optional commonTypeChecks(TypeElement classToCheck) {
return checkNesting(classToCheck)
.map(f -> f.prepend("invalid class: "))
.or(() -> checkDefaultConstructor(classToCheck));
}
private Optional checkNesting(TypeElement classToCheck) {
if (classToCheck.getNestingKind().isNested() && !classToCheck.getModifiers().contains(STATIC)) {
return Optional.of(new ValidationFailure("nested class '" +
classToCheck.getSimpleName() +
"' must be static", classToCheck));
}
if (classToCheck.getModifiers().contains(PRIVATE)) {
return Optional.of(new ValidationFailure("class '" +
classToCheck.getSimpleName() +
" may not be private", classToCheck));
}
for (TypeElement element : getEnclosingElements(classToCheck)) {
if (element.getModifiers().contains(PRIVATE)) {
return Optional.of(new ValidationFailure("enclosing class '" +
element.getSimpleName() +
"' may not be private", element));
}
if (element.getNestingKind().isNested() && !element.getModifiers().contains(STATIC)) {
return Optional.of(new ValidationFailure("nested class '" +
element.getSimpleName() +
"' must be static", element));
}
}
return Optional.empty();
}
private Optional checkDefaultConstructor(TypeElement classToCheck) {
List constructors = constructorsIn(classToCheck.getEnclosedElements());
if (constructors.isEmpty()) {
return Optional.empty();
}
return constructors.stream()
.filter(c -> c.getParameters().isEmpty())
.map(c -> {
if (c.getModifiers().contains(PRIVATE)) {
return Optional.of(new ValidationFailure("invalid constructor:" +
" visibility may not be private", c));
}
return checkExceptionsInDeclaration(c);
})
.flatMap(Optional::stream)
.findAny()
.or(() -> {
if (constructors.stream().anyMatch(c -> c.getParameters().isEmpty())) {
return Optional.empty();
}
return Optional.of(new ValidationFailure("invalid class:" +
" default constructor not found", classToCheck));
});
}
public Optional checkExceptionsInDeclaration(ExecutableElement element) {
return element.getThrownTypes().stream()
.map(thrown -> checkChecked(element, thrown, thrown))
.flatMap(Optional::stream)
.findAny();
}
private Optional checkChecked(
ExecutableElement element,
TypeMirror thrown,
TypeMirror mirror) {
if (tool.isSameType(mirror, RuntimeException.class) ||
tool.isSameType(mirror, Error.class)) {
return Optional.empty();
}
if (tool.isSameType(mirror, Throwable.class)) {
return Optional.of(new ValidationFailure("invalid throws clause:" +
" found checked exception " +
typeToString(thrown), element));
}
return types.asElement(mirror)
.flatMap(AS_TYPE_ELEMENT::visit)
.flatMap(t -> checkNesting(t)
.map(f -> f.prepend("invalid throws clause: declared exception " +
typeToString(thrown) + " is invalid: ").about(element))
.or(() -> checkChecked(element, thrown, t.getSuperclass())));
}
public List getEnclosingElements(TypeElement sourceElement) {
if (sourceElement.getNestingKind() != MEMBER) {
return List.of();
}
List result = new ArrayList<>();
TypeElement current = sourceElement;
while (current.getNestingKind() == MEMBER) {
Optional enclosing = AS_TYPE_ELEMENT.visit(current.getEnclosingElement());
if (enclosing.isEmpty()) {
break;
}
TypeElement e = enclosing.orElseThrow();
result.add(e);
current = e;
}
return result;
}
public static String typeToString(TypeMirror type) {
return TypeTool.AS_DECLARED.visit(type).flatMap(declared ->
AS_TYPE_ELEMENT.visit(declared.asElement()).map(t -> {
String base = t.getSimpleName().toString();
if (declared.getTypeArguments().isEmpty()) {
return base;
}
return base + declared.getTypeArguments().stream().map(Util::typeToString)
.collect(joining(", ", "<", ">"));
})).orElseGet(type::toString);
}
/* Left-Optional
*/
public static Optional checkNoDuplicateAnnotations(
Element element,
List> annotations) {
List> present = annotations.stream()
.filter(ann -> element.getAnnotation(ann) != null)
.collect(toList());
if (present.size() >= 2) {
return Optional.of(new ValidationFailure("annotate with either @" + present.get(0).getSimpleName() +
" or @" + present.get(1).getSimpleName() + ", but not both", element));
}
return Optional.empty();
}
}