001package ball.annotation.processing; 002/*- 003 * ########################################################################## 004 * Utilities 005 * $Id: AnnotatedProcessor.java 7479 2021-02-14 02:19:05Z 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 - 2021 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 com.sun.source.util.TaskListener; 024import java.lang.annotation.Annotation; 025import java.lang.reflect.InvocationTargetException; 026import java.util.Arrays; 027import java.util.EnumSet; 028import java.util.HashSet; 029import java.util.List; 030import java.util.Objects; 031import java.util.Set; 032import java.util.TreeSet; 033import java.util.stream.Stream; 034import javax.annotation.processing.ProcessingEnvironment; 035import javax.annotation.processing.RoundEnvironment; 036import javax.lang.model.element.AnnotationMirror; 037import javax.lang.model.element.AnnotationValue; 038import javax.lang.model.element.Element; 039import javax.lang.model.element.ElementKind; 040import javax.lang.model.element.ExecutableElement; 041import javax.lang.model.element.Modifier; 042import javax.lang.model.element.PackageElement; 043import javax.lang.model.element.TypeElement; 044import javax.lang.model.element.VariableElement; 045import javax.lang.model.type.TypeMirror; 046import lombok.AllArgsConstructor; 047import lombok.NoArgsConstructor; 048import lombok.ToString; 049 050import static java.util.stream.Collectors.toCollection; 051import static java.util.stream.Collectors.toList; 052import static java.util.stream.Collectors.toSet; 053import static javax.tools.Diagnostic.Kind.ERROR; 054import static lombok.AccessLevel.PROTECTED; 055 056/** 057 * Abstract {@link javax.annotation.processing.Processor} base class for 058 * processing {@link Annotation}s specified by @{@link For}. Provides 059 * built-in support for a number of {@link Annotation} types. 060 * 061 * {@bean.info} 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: 7479 $ 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 TaskListener listener = 106 new COMPILATIONFinishedTaskListener(javac, elements, processed, 107 () -> onCOMPILATIONFinished()); 108 109 javac.addTaskListener(listener); 110 } 111 } catch (Exception exception) { 112 print(ERROR, exception); 113 } 114 } 115 116 @Override 117 public boolean process(Set<? extends TypeElement> annotations, 118 RoundEnvironment roundEnv) { 119 annotations.stream().forEach(t -> process(roundEnv, t)); 120 121 return true; 122 } 123 124 private void process(RoundEnvironment roundEnv, TypeElement annotation) { 125 try { 126 roundEnv.getElementsAnnotatedWith(annotation) 127 .stream() 128 .peek(t -> processed.add(getEnclosingTypeBinaryName(t))) 129 .peek(new AnnotationValueMustConvertToCheck(annotation)) 130 .peek(new TargetMustBeCheck(annotation)) 131 .peek(new TargetMustHaveModifiersCheck(annotation)) 132 .peek(new TargetMustNotHaveModifiersCheck(annotation)) 133 .peek(new TargetMustExtendCheck(annotation)) 134 .peek(new TargetMustHaveConstructorCheck(annotation)) 135 .forEach(t -> process(roundEnv, annotation, t)); 136 } catch (Throwable throwable) { 137 print(ERROR, throwable); 138 } 139 } 140 141 /** 142 * Callback method to process an annotated {@link Element}. Default 143 * implementation does nothing. 144 * 145 * @param roundEnv The {@link RoundEnvironment}. 146 * @param annotation The annotation {@link TypeElement}. 147 * @param element The annotated {@link Element}. 148 */ 149 protected void process(RoundEnvironment roundEnv, 150 TypeElement annotation, Element element) { 151 } 152 153 private String getEnclosingTypeBinaryName(Element element) { 154 String name = null; 155 156 switch (element.getKind()) { 157 case ANNOTATION_TYPE: 158 case CLASS: 159 case ENUM: 160 case INTERFACE: 161 name = elements.getBinaryName((TypeElement) element).toString(); 162 break; 163 164 case PACKAGE: 165 name = 166 ((PackageElement) element).getQualifiedName().toString() 167 + ".package-info"; 168 break; 169 170 default: 171 name = getEnclosingTypeBinaryName(element.getEnclosingElement()); 172 break; 173 } 174 175 return name; 176 } 177 178 private void onCOMPILATIONFinished() { 179 HashSet<Class<?>> set = new HashSet<>(); 180 181 try { 182 ClassLoader loader = getClassPathClassLoader(fm); 183 184 for (String name : ClassFileProcessor.list(fm)) { 185 try { 186 set.add(Class.forName(name, true, loader)); 187 } catch (Throwable throwable) { 188 } 189 } 190 191 ((ClassFileProcessor) this).process(set, fm); 192 } catch (Exception exception) { 193 print(ERROR, exception); 194 } 195 } 196 197 @AllArgsConstructor @ToString 198 private class AnnotationValueMustConvertToCheck extends Check<Element> { 199 private final TypeElement annotation; 200 201 @Override 202 public void accept(Element element) { 203 AnnotationMirror meta = 204 getAnnotationMirror(annotation, 205 AnnotationValueMustConvertTo.class); 206 207 if (meta != null) { 208 AnnotationValue value = getAnnotationValue(meta, "value"); 209 TypeElement to = 210 (TypeElement) 211 types.asElement((TypeMirror) value.getValue()); 212 String method = 213 (String) getAnnotationValue(meta, "method").getValue(); 214 String name = 215 (String) getAnnotationValue(meta, "name").getValue(); 216 AnnotationMirror mirror = 217 getAnnotationMirror(element, annotation); 218 AnnotationValue from = null; 219 220 try { 221 from = getAnnotationValue(mirror, name); 222 223 Class<?> type = 224 Class.forName(to.getQualifiedName().toString()); 225 226 if (! method.isEmpty()) { 227 type.getMethod(method, from.getValue().getClass()) 228 .invoke(null, from.getValue()); 229 } else { 230 type.getConstructor(from.getValue().getClass()) 231 .newInstance(from.getValue()); 232 } 233 } catch (Exception exception) { 234 Throwable throwable = exception; 235 236 while (throwable instanceof InvocationTargetException) { 237 throwable = throwable.getCause(); 238 } 239 240 print(ERROR, element, mirror, 241 "Cannot convert %s to %s\n%s", 242 from, to.getQualifiedName(), throwable.getMessage()); 243 } 244 } 245 } 246 } 247 248 @AllArgsConstructor @ToString 249 private class TargetMustBeCheck extends Check<Element> { 250 private final TypeElement annotation; 251 252 @Override 253 public void accept(Element element) { 254 AnnotationMirror meta = 255 getAnnotationMirror(annotation, TargetMustBe.class); 256 257 if (meta != null) { 258 AnnotationValue value = getAnnotationValue(meta, "value"); 259 String name = 260 ((VariableElement) value.getValue()).getSimpleName() 261 .toString(); 262 ElementKind kind = ElementKind.valueOf(name); 263 264 if (! kind.equals(element.getKind())) { 265 print(ERROR, element, 266 "@%s: %s is not a %s", 267 annotation.getSimpleName(), element.getKind(), kind); 268 } 269 } 270 } 271 } 272 273 @AllArgsConstructor @ToString 274 private class TargetMustHaveModifiersCheck extends Check<Element> { 275 private final TypeElement annotation; 276 277 @Override 278 public void accept(Element element) { 279 AnnotationMirror meta = 280 getAnnotationMirror(annotation, TargetMustHaveModifiers.class); 281 282 if (meta != null) { 283 EnumSet<Modifier> modifiers = 284 Stream.of(getAnnotationValue(meta, "value")) 285 .filter(Objects::nonNull) 286 .map(t -> (List<?>) t.getValue()) 287 .flatMap(List::stream) 288 .map(t -> ((AnnotationValue) t).getValue()) 289 .map(Objects::toString) 290 .map(Modifier::valueOf) 291 .collect(toCollection(() -> EnumSet.noneOf(Modifier.class))); 292 293 if (! withModifiers(modifiers).test(element)) { 294 print(ERROR, element, 295 "%s must be %s", element.getKind(), modifiers); 296 } 297 } 298 } 299 } 300 301 @AllArgsConstructor @ToString 302 private class TargetMustNotHaveModifiersCheck extends Check<Element> { 303 private final TypeElement annotation; 304 305 @Override 306 public void accept(Element element) { 307 AnnotationMirror meta = 308 getAnnotationMirror(annotation, 309 TargetMustNotHaveModifiers.class); 310 311 if (meta != null) { 312 EnumSet<Modifier> modifiers = 313 Stream.of(getAnnotationValue(meta, "value")) 314 .filter(Objects::nonNull) 315 .map(t -> (List<?>) t.getValue()) 316 .flatMap(List::stream) 317 .map(t -> ((AnnotationValue) t).getValue()) 318 .map(Objects::toString) 319 .map(Modifier::valueOf) 320 .collect(toCollection(() -> EnumSet.noneOf(Modifier.class))); 321 322 if (! withoutModifiers(modifiers).test(element)) { 323 print(ERROR, element, 324 "%s must not be %s", element.getKind(), modifiers); 325 } 326 } 327 } 328 } 329 330 @AllArgsConstructor @ToString 331 private class TargetMustExtendCheck extends Check<Element> { 332 private final TypeElement annotation; 333 334 @Override 335 public void accept(Element element) { 336 AnnotationMirror meta = 337 getAnnotationMirror(annotation, TargetMustExtend.class); 338 339 if (meta != null) { 340 AnnotationValue value = getAnnotationValue(meta, "value"); 341 TypeElement type = 342 (TypeElement) 343 types.asElement((TypeMirror) value.getValue()); 344 345 if (! types.isAssignable(element.asType(), type.asType())) { 346 print(ERROR, element, 347 "@%s: %s does not extend %s", 348 annotation.getSimpleName(), 349 element, type.getQualifiedName()); 350 } 351 } 352 } 353 } 354 355 @AllArgsConstructor @ToString 356 private class TargetMustHaveConstructorCheck extends Check<Element> { 357 private final TypeElement annotation; 358 359 @Override 360 public void accept(Element element) { 361 AnnotationMirror meta = 362 getAnnotationMirror(annotation, 363 TargetMustHaveConstructor.class); 364 365 if (meta != null) { 366 AnnotationValue value = getAnnotationValue(meta, "value"); 367 String name = 368 ((VariableElement) value.getValue()).getSimpleName() 369 .toString(); 370 Modifier modifier = Modifier.valueOf(name); 371 List<TypeMirror> parameters = 372 Stream.of(getAnnotationValue(meta, "parameters")) 373 .filter(Objects::nonNull) 374 .map(t -> (List<?>) t.getValue()) 375 .flatMap(List::stream) 376 .map(t -> (AnnotationValue) t) 377 .map(t -> (TypeMirror) t.getValue()) 378 .collect(toList()); 379 ExecutableElement constructor = 380 getConstructor((TypeElement) element, parameters); 381 boolean found = 382 (constructor != null 383 && constructor.getModifiers().contains(modifier)); 384 385 if (! found) { 386 print(ERROR, element, 387 "@%s: No %s matching constructor", 388 annotation.getSimpleName(), modifier); 389 } 390 } 391 } 392 } 393}