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