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}