/* * Copyright (C) 2014 The Dagger Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package dagger.internal.codegen.validation; import static dagger.internal.codegen.base.ElementFormatter.elementToString; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static javax.tools.Diagnostic.Kind.ERROR; import static javax.tools.Diagnostic.Kind.NOTE; import static javax.tools.Diagnostic.Kind.WARNING; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableSet; import com.google.common.graph.Traverser; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CheckReturnValue; import java.util.Optional; import javax.annotation.processing.Messager; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.tools.Diagnostic; import javax.tools.Diagnostic.Kind; /** A collection of issues to report for source code. */ public final class ValidationReport { private static final Traverser> SUBREPORTS = Traverser.forTree(report -> report.subreports); private final T subject; private final ImmutableSet items; private final ImmutableSet> subreports; private final boolean markedDirty; private boolean hasPrintedErrors; private ValidationReport( T subject, ImmutableSet items, ImmutableSet> subreports, boolean markedDirty) { this.subject = subject; this.items = items; this.subreports = subreports; this.markedDirty = markedDirty; } /** Returns the items from this report and all transitive subreports. */ public ImmutableSet allItems() { return ImmutableSet.copyOf(SUBREPORTS.depthFirstPreOrder(this)) .stream() .flatMap(report -> report.items.stream()) .collect(toImmutableSet()); } /** * Returns {@code true} if there are no errors in this report or any subreports and markedDirty is * {@code false}. */ public boolean isClean() { if (markedDirty) { return false; } for (Item item : items) { switch (item.kind()) { case ERROR: return false; default: break; } } for (ValidationReport subreport : subreports) { if (!subreport.isClean()) { return false; } } return true; } /** * Prints all messages to {@code messager} (and recurs for subreports). If a * message's {@linkplain Item#element() element} is contained within the report's subject, * associates the message with the message's element. Otherwise, since {@link Diagnostic} * reporting is expected to be associated with elements that are currently being compiled, * associates the message with the subject itself and prepends a reference to the item's element. */ public void printMessagesTo(Messager messager) { if (hasPrintedErrors) { // Avoid printing the errors from this validation report more than once. return; } hasPrintedErrors = true; for (Item item : items) { if (isEnclosedIn(subject, item.element())) { if (item.annotation().isPresent()) { if (item.annotationValue().isPresent()) { messager.printMessage( item.kind(), item.message(), item.element(), item.annotation().get(), item.annotationValue().get()); } else { messager.printMessage( item.kind(), item.message(), item.element(), item.annotation().get()); } } else { messager.printMessage(item.kind(), item.message(), item.element()); } } else { String message = String.format("[%s] %s", elementToString(item.element()), item.message()); messager.printMessage(item.kind(), message, subject); } } for (ValidationReport subreport : subreports) { subreport.printMessagesTo(messager); } } private static boolean isEnclosedIn(Element parent, Element child) { Element current = child; while (current != null) { if (current.equals(parent)) { return true; } current = current.getEnclosingElement(); } return false; } /** Metadata about a {@link ValidationReport} item. */ @AutoValue public abstract static class Item { public abstract String message(); public abstract Kind kind(); public abstract Element element(); public abstract Optional annotation(); abstract Optional annotationValue(); } public static Builder about(T subject) { return new Builder<>(subject); } /** A {@link ValidationReport} builder. */ @CanIgnoreReturnValue public static final class Builder { private final T subject; private final ImmutableSet.Builder items = ImmutableSet.builder(); private final ImmutableSet.Builder> subreports = ImmutableSet.builder(); private boolean markedDirty; private Builder(T subject) { this.subject = subject; } @CheckReturnValue T getSubject() { return subject; } Builder addItems(Iterable newItems) { items.addAll(newItems); return this; } public Builder addError(String message) { return addError(message, subject); } public Builder addError(String message, Element element) { return addItem(message, ERROR, element); } public Builder addError(String message, Element element, AnnotationMirror annotation) { return addItem(message, ERROR, element, annotation); } public Builder addError( String message, Element element, AnnotationMirror annotation, AnnotationValue annotationValue) { return addItem(message, ERROR, element, annotation, annotationValue); } Builder addWarning(String message) { return addWarning(message, subject); } Builder addWarning(String message, Element element) { return addItem(message, WARNING, element); } Builder addWarning(String message, Element element, AnnotationMirror annotation) { return addItem(message, WARNING, element, annotation); } Builder addWarning( String message, Element element, AnnotationMirror annotation, AnnotationValue annotationValue) { return addItem(message, WARNING, element, annotation, annotationValue); } Builder addNote(String message) { return addNote(message, subject); } Builder addNote(String message, Element element) { return addItem(message, NOTE, element); } Builder addNote(String message, Element element, AnnotationMirror annotation) { return addItem(message, NOTE, element, annotation); } Builder addNote( String message, Element element, AnnotationMirror annotation, AnnotationValue annotationValue) { return addItem(message, NOTE, element, annotation, annotationValue); } Builder addItem(String message, Kind kind, Element element) { return addItem(message, kind, element, Optional.empty(), Optional.empty()); } Builder addItem(String message, Kind kind, Element element, AnnotationMirror annotation) { return addItem(message, kind, element, Optional.of(annotation), Optional.empty()); } Builder addItem( String message, Kind kind, Element element, AnnotationMirror annotation, AnnotationValue annotationValue) { return addItem(message, kind, element, Optional.of(annotation), Optional.of(annotationValue)); } private Builder addItem( String message, Kind kind, Element element, Optional annotation, Optional annotationValue) { items.add( new AutoValue_ValidationReport_Item(message, kind, element, annotation, annotationValue)); return this; } /** * If called, then {@link #isClean()} will return {@code false} even if there are no error items * in the report. */ void markDirty() { this.markedDirty = true; } public Builder addSubreport(ValidationReport subreport) { subreports.add(subreport); return this; } @CheckReturnValue public ValidationReport build() { return new ValidationReport<>(subject, items.build(), subreports.build(), markedDirty); } } }