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}