001package ball.annotation.processing; 002/*- 003 * ########################################################################## 004 * Utilities 005 * $Id: AnnotatedProcessor.java 6124 2020-06-06 14:30:08Z ball $ 006 * $HeadURL: svn+ssh://svn.hcf.dev/var/spool/scm/repository.svn/ball-util/trunk/src/main/java/ball/annotation/processing/AnnotatedProcessor.java $ 007 * %% 008 * Copyright (C) 2008 - 2020 Allen D. Ball 009 * %% 010 * Licensed under the Apache License, Version 2.0 (the "License"); 011 * you may not use this file except in compliance with the License. 012 * You may obtain a copy of the License at 013 * 014 * http://www.apache.org/licenses/LICENSE-2.0 015 * 016 * Unless required by applicable law or agreed to in writing, software 017 * distributed under the License is distributed on an "AS IS" BASIS, 018 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 019 * See the License for the specific language governing permissions and 020 * limitations under the License. 021 * ########################################################################## 022 */ 023import ball.tools.javac.AbstractTaskListener; 024import com.sun.source.util.TaskEvent; 025import java.lang.annotation.Annotation; 026import java.lang.reflect.InvocationTargetException; 027import java.util.Arrays; 028import java.util.EnumSet; 029import java.util.HashSet; 030import java.util.List; 031import java.util.Objects; 032import java.util.Set; 033import java.util.TreeSet; 034import java.util.stream.Stream; 035import javax.annotation.processing.ProcessingEnvironment; 036import javax.annotation.processing.RoundEnvironment; 037import javax.lang.model.element.AnnotationMirror; 038import javax.lang.model.element.AnnotationValue; 039import javax.lang.model.element.Element; 040import javax.lang.model.element.ElementKind; 041import javax.lang.model.element.ExecutableElement; 042import javax.lang.model.element.Modifier; 043import javax.lang.model.element.PackageElement; 044import javax.lang.model.element.TypeElement; 045import javax.lang.model.element.VariableElement; 046import javax.lang.model.type.TypeMirror; 047import lombok.AllArgsConstructor; 048import lombok.NoArgsConstructor; 049import lombok.RequiredArgsConstructor; 050import lombok.ToString; 051 052import static java.util.stream.Collectors.toCollection; 053import static java.util.stream.Collectors.toList; 054import static java.util.stream.Collectors.toSet; 055import static javax.tools.Diagnostic.Kind.ERROR; 056import static lombok.AccessLevel.PROTECTED; 057 058/** 059 * Abstract {@link javax.annotation.processing.Processor} base class for 060 * processing {@link Annotation}s specified by @{@link For}. Provides 061 * built-in support for a number of {@link Annotation} types. 062 * 063 * @see AnnotationValueMustConvertTo 064 * @see TargetMustBe 065 * @see TargetMustExtend 066 * @see TargetMustHaveConstructor 067 * @see TargetMustHaveModifiers 068 * @see TargetMustNotHaveModifiers 069 * 070 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball} 071 * @version $Revision: 6124 $ 072 */ 073@NoArgsConstructor(access = PROTECTED) @ToString 074public abstract class AnnotatedProcessor extends AbstractProcessor { 075 private final Set<String> processed = new TreeSet<>(); 076 077 /** 078 * Method to get the {@link List} of supported {@link Annotation} 079 * {@link Class}es. 080 * 081 * @return The {@link List} of supported {@link Annotation} 082 * {@link Class}es. 083 */ 084 protected List<Class<? extends Annotation>> getSupportedAnnotationTypeList() { 085 return Arrays.asList(getClass().getAnnotation(For.class).value()); 086 } 087 088 @Override 089 public Set<String> getSupportedAnnotationTypes() { 090 Set<String> set = 091 getSupportedAnnotationTypeList() 092 .stream() 093 .map(Class::getCanonicalName) 094 .collect(toSet()); 095 096 return set; 097 } 098 099 @Override 100 public void init(ProcessingEnvironment processingEnv) { 101 super.init(processingEnv); 102 103 try { 104 if (this instanceof ClassFileProcessor) { 105 javac.addTaskListener(new Invoker((ClassFileProcessor) this)); 106 } 107 } catch (Exception exception) { 108 print(ERROR, exception); 109 } 110 } 111 112 @Override 113 public boolean process(Set<? extends TypeElement> annotations, 114 RoundEnvironment roundEnv) { 115 annotations.stream().forEach(t -> process(roundEnv, t)); 116 117 return true; 118 } 119 120 private void process(RoundEnvironment roundEnv, TypeElement annotation) { 121 try { 122 roundEnv.getElementsAnnotatedWith(annotation) 123 .stream() 124 .peek(t -> processed.add(getEnclosingTypeBinaryName(t))) 125 .peek(new AnnotationValueMustConvertToCheck(annotation)) 126 .peek(new TargetMustBeCheck(annotation)) 127 .peek(new TargetMustHaveModifiersCheck(annotation)) 128 .peek(new TargetMustNotHaveModifiersCheck(annotation)) 129 .peek(new TargetMustExtendCheck(annotation)) 130 .peek(new TargetMustHaveConstructorCheck(annotation)) 131 .forEach(t -> process(roundEnv, annotation, t)); 132 } catch (Throwable throwable) { 133 print(ERROR, throwable); 134 } 135 } 136 137 /** 138 * Callback method to process an annotated {@link Element}. Default 139 * implementation does nothing. 140 * 141 * @param roundEnv The {@link RoundEnvironment}. 142 * @param annotation The annotation {@link TypeElement}. 143 * @param element The annotated {@link Element}. 144 */ 145 protected void process(RoundEnvironment roundEnv, 146 TypeElement annotation, Element element) { 147 } 148 149 private String getEnclosingTypeBinaryName(Element element) { 150 String name = null; 151 152 switch (element.getKind()) { 153 case ANNOTATION_TYPE: 154 case CLASS: 155 case ENUM: 156 case INTERFACE: 157 name = elements.getBinaryName((TypeElement) element).toString(); 158 break; 159 160 case PACKAGE: 161 name = 162 ((PackageElement) element).getQualifiedName().toString() 163 + ".package-info"; 164 break; 165 166 default: 167 name = getEnclosingTypeBinaryName(element.getEnclosingElement()); 168 break; 169 } 170 171 return name; 172 } 173 174 @RequiredArgsConstructor @ToString 175 private class Invoker extends AbstractTaskListener { 176 private final ClassFileProcessor processor; 177 private final Set<String> generated = new TreeSet<>(); 178 179 @Override @SuppressWarnings({ "fallthrough" }) 180 public void finished(TaskEvent event) { 181 switch (event.getKind()) { 182 case GENERATE: 183 String name = 184 elements.getBinaryName(event.getTypeElement()).toString(); 185 186 generated.add(name); 187 /* 188 * Fall-through 189 */ 190 case ANNOTATION_PROCESSING: 191 if (processed.isEmpty() || generated.containsAll(processed)) { 192 try { 193 process(); 194 195 javac.removeTaskListener(this); 196 } catch (Throwable throwable) { 197 } 198 } 199 break; 200 201 default: 202 break; 203 } 204 } 205 206 private void process() throws Throwable { 207 HashSet<Class<?>> set = new HashSet<>(); 208 ClassLoader loader = getClassPathClassLoader(fm); 209 210 for (String name : ClassFileProcessor.list(fm)) { 211 set.add(Class.forName(name, true, loader)); 212 } 213 214 processor.process(set, fm); 215 } 216 } 217 218 @AllArgsConstructor @ToString 219 private class AnnotationValueMustConvertToCheck extends Check<Element> { 220 private final TypeElement annotation; 221 222 @Override 223 public void accept(Element element) { 224 AnnotationMirror meta = 225 getAnnotationMirror(annotation, 226 AnnotationValueMustConvertTo.class); 227 228 if (meta != null) { 229 AnnotationValue value = getAnnotationValue(meta, "value"); 230 TypeElement to = 231 (TypeElement) 232 types.asElement((TypeMirror) value.getValue()); 233 String method = 234 (String) getAnnotationValue(meta, "method").getValue(); 235 String name = 236 (String) getAnnotationValue(meta, "name").getValue(); 237 AnnotationMirror mirror = 238 getAnnotationMirror(element, annotation); 239 AnnotationValue from = null; 240 241 try { 242 from = getAnnotationValue(mirror, name); 243 244 Class<?> type = 245 Class.forName(to.getQualifiedName().toString()); 246 247 if (! method.isEmpty()) { 248 type.getMethod(method, from.getValue().getClass()) 249 .invoke(null, from.getValue()); 250 } else { 251 type.getConstructor(from.getValue().getClass()) 252 .newInstance(from.getValue()); 253 } 254 } catch (Exception exception) { 255 Throwable throwable = exception; 256 257 while (throwable instanceof InvocationTargetException) { 258 throwable = throwable.getCause(); 259 } 260 261 print(ERROR, element, mirror, 262 "Cannot convert %s to %s\n%s", 263 from, to.getQualifiedName(), throwable.getMessage()); 264 } 265 } 266 } 267 } 268 269 @AllArgsConstructor @ToString 270 private class TargetMustBeCheck extends Check<Element> { 271 private final TypeElement annotation; 272 273 @Override 274 public void accept(Element element) { 275 AnnotationMirror meta = 276 getAnnotationMirror(annotation, TargetMustBe.class); 277 278 if (meta != null) { 279 AnnotationValue value = getAnnotationValue(meta, "value"); 280 String name = 281 ((VariableElement) value.getValue()).getSimpleName() 282 .toString(); 283 ElementKind kind = ElementKind.valueOf(name); 284 285 if (! kind.equals(element.getKind())) { 286 print(ERROR, element, 287 "@%s: %s is not a %s", 288 annotation.getSimpleName(), element.getKind(), kind); 289 } 290 } 291 } 292 } 293 294 @AllArgsConstructor @ToString 295 private class TargetMustHaveModifiersCheck extends Check<Element> { 296 private final TypeElement annotation; 297 298 @Override 299 public void accept(Element element) { 300 AnnotationMirror meta = 301 getAnnotationMirror(annotation, TargetMustHaveModifiers.class); 302 303 if (meta != null) { 304 EnumSet<Modifier> modifiers = 305 Stream.of(getAnnotationValue(meta, "value")) 306 .filter(Objects::nonNull) 307 .map(t -> (List<?>) t.getValue()) 308 .flatMap(List::stream) 309 .map(t -> ((AnnotationValue) t).getValue()) 310 .map(Objects::toString) 311 .map(Modifier::valueOf) 312 .collect(toCollection(() -> EnumSet.noneOf(Modifier.class))); 313 314 if (! withModifiers(modifiers).test(element)) { 315 print(ERROR, element, 316 "%s must be %s", element.getKind(), modifiers); 317 } 318 } 319 } 320 } 321 322 @AllArgsConstructor @ToString 323 private class TargetMustNotHaveModifiersCheck extends Check<Element> { 324 private final TypeElement annotation; 325 326 @Override 327 public void accept(Element element) { 328 AnnotationMirror meta = 329 getAnnotationMirror(annotation, 330 TargetMustNotHaveModifiers.class); 331 332 if (meta != null) { 333 EnumSet<Modifier> modifiers = 334 Stream.of(getAnnotationValue(meta, "value")) 335 .filter(Objects::nonNull) 336 .map(t -> (List<?>) t.getValue()) 337 .flatMap(List::stream) 338 .map(t -> ((AnnotationValue) t).getValue()) 339 .map(Objects::toString) 340 .map(Modifier::valueOf) 341 .collect(toCollection(() -> EnumSet.noneOf(Modifier.class))); 342 343 if (! withoutModifiers(modifiers).test(element)) { 344 print(ERROR, element, 345 "%s must not be %s", element.getKind(), modifiers); 346 } 347 } 348 } 349 } 350 351 @AllArgsConstructor @ToString 352 private class TargetMustExtendCheck extends Check<Element> { 353 private final TypeElement annotation; 354 355 @Override 356 public void accept(Element element) { 357 AnnotationMirror meta = 358 getAnnotationMirror(annotation, TargetMustExtend.class); 359 360 if (meta != null) { 361 AnnotationValue value = getAnnotationValue(meta, "value"); 362 TypeElement type = 363 (TypeElement) 364 types.asElement((TypeMirror) value.getValue()); 365 366 if (! types.isAssignable(element.asType(), type.asType())) { 367 print(ERROR, element, 368 "@%s: %s does not extend %s", 369 annotation.getSimpleName(), 370 element, type.getQualifiedName()); 371 } 372 } 373 } 374 } 375 376 @AllArgsConstructor @ToString 377 private class TargetMustHaveConstructorCheck extends Check<Element> { 378 private final TypeElement annotation; 379 380 @Override 381 public void accept(Element element) { 382 AnnotationMirror meta = 383 getAnnotationMirror(annotation, 384 TargetMustHaveConstructor.class); 385 386 if (meta != null) { 387 AnnotationValue value = getAnnotationValue(meta, "value"); 388 String name = 389 ((VariableElement) value.getValue()).getSimpleName() 390 .toString(); 391 Modifier modifier = Modifier.valueOf(name); 392 List<TypeMirror> parameters = 393 Stream.of(getAnnotationValue(meta, "parameters")) 394 .filter(Objects::nonNull) 395 .map(t -> (List<?>) t.getValue()) 396 .flatMap(List::stream) 397 .map(t -> (AnnotationValue) t) 398 .map(t -> (TypeMirror) t.getValue()) 399 .collect(toList()); 400 ExecutableElement constructor = 401 getConstructor((TypeElement) element, parameters); 402 boolean found = 403 (constructor != null 404 && constructor.getModifiers().contains(modifier)); 405 406 if (! found) { 407 print(ERROR, element, 408 "@%s: No %s matching constructor", 409 annotation.getSimpleName(), modifier); 410 } 411 } 412 } 413 } 414}