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

net.jbock.common.Util Maven / Gradle / Ivy

There is a newer version: 5.18
Show newest version
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();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy