1 /* 2 * Copyright (C) 2014 The Dagger Authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package dagger.internal.codegen.validation; 18 19 import static dagger.internal.codegen.base.ElementFormatter.elementToString; 20 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; 21 import static javax.tools.Diagnostic.Kind.ERROR; 22 import static javax.tools.Diagnostic.Kind.NOTE; 23 import static javax.tools.Diagnostic.Kind.WARNING; 24 25 import com.google.auto.value.AutoValue; 26 import com.google.common.collect.ImmutableSet; 27 import com.google.common.graph.Traverser; 28 import com.google.errorprone.annotations.CanIgnoreReturnValue; 29 import com.google.errorprone.annotations.CheckReturnValue; 30 import java.util.Optional; 31 import javax.annotation.processing.Messager; 32 import javax.lang.model.element.AnnotationMirror; 33 import javax.lang.model.element.AnnotationValue; 34 import javax.lang.model.element.Element; 35 import javax.tools.Diagnostic; 36 import javax.tools.Diagnostic.Kind; 37 38 /** A collection of issues to report for source code. */ 39 public final class ValidationReport<T extends Element> { 40 private static final Traverser<ValidationReport<?>> SUBREPORTS = 41 Traverser.forTree(report -> report.subreports); 42 43 private final T subject; 44 private final ImmutableSet<Item> items; 45 private final ImmutableSet<ValidationReport<?>> subreports; 46 private final boolean markedDirty; 47 private boolean hasPrintedErrors; 48 ValidationReport( T subject, ImmutableSet<Item> items, ImmutableSet<ValidationReport<?>> subreports, boolean markedDirty)49 private ValidationReport( 50 T subject, 51 ImmutableSet<Item> items, 52 ImmutableSet<ValidationReport<?>> subreports, 53 boolean markedDirty) { 54 this.subject = subject; 55 this.items = items; 56 this.subreports = subreports; 57 this.markedDirty = markedDirty; 58 } 59 60 /** Returns the items from this report and all transitive subreports. */ allItems()61 public ImmutableSet<Item> allItems() { 62 return ImmutableSet.copyOf(SUBREPORTS.depthFirstPreOrder(this)) 63 .stream() 64 .flatMap(report -> report.items.stream()) 65 .collect(toImmutableSet()); 66 } 67 68 /** 69 * Returns {@code true} if there are no errors in this report or any subreports and markedDirty is 70 * {@code false}. 71 */ isClean()72 public boolean isClean() { 73 if (markedDirty) { 74 return false; 75 } 76 for (Item item : items) { 77 switch (item.kind()) { 78 case ERROR: 79 return false; 80 default: 81 break; 82 } 83 } 84 for (ValidationReport<?> subreport : subreports) { 85 if (!subreport.isClean()) { 86 return false; 87 } 88 } 89 return true; 90 } 91 92 /** 93 * Prints all messages to {@code messager} (and recurs for subreports). If a 94 * message's {@linkplain Item#element() element} is contained within the report's subject, 95 * associates the message with the message's element. Otherwise, since {@link Diagnostic} 96 * reporting is expected to be associated with elements that are currently being compiled, 97 * associates the message with the subject itself and prepends a reference to the item's element. 98 */ printMessagesTo(Messager messager)99 public void printMessagesTo(Messager messager) { 100 if (hasPrintedErrors) { 101 // Avoid printing the errors from this validation report more than once. 102 return; 103 } 104 hasPrintedErrors = true; 105 for (Item item : items) { 106 if (isEnclosedIn(subject, item.element())) { 107 if (item.annotation().isPresent()) { 108 if (item.annotationValue().isPresent()) { 109 messager.printMessage( 110 item.kind(), 111 item.message(), 112 item.element(), 113 item.annotation().get(), 114 item.annotationValue().get()); 115 } else { 116 messager.printMessage( 117 item.kind(), item.message(), item.element(), item.annotation().get()); 118 } 119 } else { 120 messager.printMessage(item.kind(), item.message(), item.element()); 121 } 122 } else { 123 String message = String.format("[%s] %s", elementToString(item.element()), item.message()); 124 messager.printMessage(item.kind(), message, subject); 125 } 126 } 127 for (ValidationReport<?> subreport : subreports) { 128 subreport.printMessagesTo(messager); 129 } 130 } 131 isEnclosedIn(Element parent, Element child)132 private static boolean isEnclosedIn(Element parent, Element child) { 133 Element current = child; 134 while (current != null) { 135 if (current.equals(parent)) { 136 return true; 137 } 138 current = current.getEnclosingElement(); 139 } 140 return false; 141 } 142 143 /** Metadata about a {@link ValidationReport} item. */ 144 @AutoValue 145 public abstract static class Item { message()146 public abstract String message(); kind()147 public abstract Kind kind(); element()148 public abstract Element element(); annotation()149 public abstract Optional<AnnotationMirror> annotation(); annotationValue()150 abstract Optional<AnnotationValue> annotationValue(); 151 } 152 about(T subject)153 public static <T extends Element> Builder<T> about(T subject) { 154 return new Builder<>(subject); 155 } 156 157 /** A {@link ValidationReport} builder. */ 158 @CanIgnoreReturnValue 159 public static final class Builder<T extends Element> { 160 private final T subject; 161 private final ImmutableSet.Builder<Item> items = ImmutableSet.builder(); 162 private final ImmutableSet.Builder<ValidationReport<?>> subreports = ImmutableSet.builder(); 163 private boolean markedDirty; 164 Builder(T subject)165 private Builder(T subject) { 166 this.subject = subject; 167 } 168 169 @CheckReturnValue getSubject()170 T getSubject() { 171 return subject; 172 } 173 addItems(Iterable<Item> newItems)174 Builder<T> addItems(Iterable<Item> newItems) { 175 items.addAll(newItems); 176 return this; 177 } 178 addError(String message)179 public Builder<T> addError(String message) { 180 return addError(message, subject); 181 } 182 addError(String message, Element element)183 public Builder<T> addError(String message, Element element) { 184 return addItem(message, ERROR, element); 185 } 186 addError(String message, Element element, AnnotationMirror annotation)187 public Builder<T> addError(String message, Element element, AnnotationMirror annotation) { 188 return addItem(message, ERROR, element, annotation); 189 } 190 addError( String message, Element element, AnnotationMirror annotation, AnnotationValue annotationValue)191 public Builder<T> addError( 192 String message, 193 Element element, 194 AnnotationMirror annotation, 195 AnnotationValue annotationValue) { 196 return addItem(message, ERROR, element, annotation, annotationValue); 197 } 198 addWarning(String message)199 Builder<T> addWarning(String message) { 200 return addWarning(message, subject); 201 } 202 addWarning(String message, Element element)203 Builder<T> addWarning(String message, Element element) { 204 return addItem(message, WARNING, element); 205 } 206 addWarning(String message, Element element, AnnotationMirror annotation)207 Builder<T> addWarning(String message, Element element, AnnotationMirror annotation) { 208 return addItem(message, WARNING, element, annotation); 209 } 210 addWarning( String message, Element element, AnnotationMirror annotation, AnnotationValue annotationValue)211 Builder<T> addWarning( 212 String message, 213 Element element, 214 AnnotationMirror annotation, 215 AnnotationValue annotationValue) { 216 return addItem(message, WARNING, element, annotation, annotationValue); 217 } 218 addNote(String message)219 Builder<T> addNote(String message) { 220 return addNote(message, subject); 221 } 222 addNote(String message, Element element)223 Builder<T> addNote(String message, Element element) { 224 return addItem(message, NOTE, element); 225 } 226 addNote(String message, Element element, AnnotationMirror annotation)227 Builder<T> addNote(String message, Element element, AnnotationMirror annotation) { 228 return addItem(message, NOTE, element, annotation); 229 } 230 addNote( String message, Element element, AnnotationMirror annotation, AnnotationValue annotationValue)231 Builder<T> addNote( 232 String message, 233 Element element, 234 AnnotationMirror annotation, 235 AnnotationValue annotationValue) { 236 return addItem(message, NOTE, element, annotation, annotationValue); 237 } 238 addItem(String message, Kind kind, Element element)239 Builder<T> addItem(String message, Kind kind, Element element) { 240 return addItem(message, kind, element, Optional.empty(), Optional.empty()); 241 } 242 addItem(String message, Kind kind, Element element, AnnotationMirror annotation)243 Builder<T> addItem(String message, Kind kind, Element element, AnnotationMirror annotation) { 244 return addItem(message, kind, element, Optional.of(annotation), Optional.empty()); 245 } 246 addItem( String message, Kind kind, Element element, AnnotationMirror annotation, AnnotationValue annotationValue)247 Builder<T> addItem( 248 String message, 249 Kind kind, 250 Element element, 251 AnnotationMirror annotation, 252 AnnotationValue annotationValue) { 253 return addItem(message, kind, element, Optional.of(annotation), Optional.of(annotationValue)); 254 } 255 addItem( String message, Kind kind, Element element, Optional<AnnotationMirror> annotation, Optional<AnnotationValue> annotationValue)256 private Builder<T> addItem( 257 String message, 258 Kind kind, 259 Element element, 260 Optional<AnnotationMirror> annotation, 261 Optional<AnnotationValue> annotationValue) { 262 items.add( 263 new AutoValue_ValidationReport_Item(message, kind, element, annotation, annotationValue)); 264 return this; 265 } 266 267 /** 268 * If called, then {@link #isClean()} will return {@code false} even if there are no error items 269 * in the report. 270 */ markDirty()271 void markDirty() { 272 this.markedDirty = true; 273 } 274 addSubreport(ValidationReport<?> subreport)275 public Builder<T> addSubreport(ValidationReport<?> subreport) { 276 subreports.add(subreport); 277 return this; 278 } 279 280 @CheckReturnValue build()281 public ValidationReport<T> build() { 282 return new ValidationReport<>(subject, items.build(), subreports.build(), markedDirty); 283 } 284 } 285 } 286