001package ball.annotation.processing;
002/*-
003 * ##########################################################################
004 * Utilities
005 * $Id: AnnotatedNoAnnotationProcessor.java 7215 2021-01-03 18:39:51Z ball $
006 * $HeadURL: svn+ssh://svn.hcf.dev/var/spool/scm/repository.svn/ball-util/trunk/src/main/java/ball/annotation/processing/AnnotatedNoAnnotationProcessor.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 java.lang.annotation.Annotation;
024import java.util.ArrayList;
025import java.util.Arrays;
026import java.util.Collections;
027import java.util.EnumSet;
028import java.util.List;
029import java.util.Set;
030import java.util.function.Consumer;
031import java.util.function.Predicate;
032import javax.annotation.processing.ProcessingEnvironment;
033import javax.annotation.processing.RoundEnvironment;
034import javax.lang.model.element.Element;
035import javax.lang.model.element.ElementKind;
036import javax.lang.model.element.Modifier;
037import javax.lang.model.element.TypeElement;
038import lombok.NoArgsConstructor;
039import lombok.ToString;
040
041import static ball.util.Walker.walk;
042import static java.util.Collections.disjoint;
043import static javax.lang.model.element.Modifier.ABSTRACT;
044import static javax.tools.Diagnostic.Kind.ERROR;
045import static javax.tools.Diagnostic.Kind.WARNING;
046import static lombok.AccessLevel.PROTECTED;
047
048/**
049 * Abstract {@link javax.annotation.processing.Processor} base class for
050 * processing "no" {@link java.lang.annotation.Annotation} ({@code "*"}).
051 *
052 * {@bean.info}
053 *
054 * @see ForElementKinds
055 * @see ForSubclassesOf
056 * @see MustImplement
057 *
058 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
059 * @version $Revision: 7215 $
060 */
061@NoArgsConstructor(access = PROTECTED) @ToString
062public abstract class AnnotatedNoAnnotationProcessor extends AbstractProcessor {
063    protected final List<Predicate<Element>> criteria = new ArrayList<>();
064    protected final List<Consumer<Element>> checks = new ArrayList<>();
065
066    /**
067     * See {@link ForElementKinds}.
068     *
069     * @return  The {@link EnumSet} of {@link ElementKind}s specified by the
070     *          annotation ({@code null} if no annotation present).
071     */
072    protected EnumSet<ElementKind> getForElementKinds() {
073        EnumSet<ElementKind> value = null;
074
075        if (getClass().isAnnotationPresent(ForElementKinds.class)) {
076            ElementKind[] array =
077                getClass().getAnnotation(ForElementKinds.class).value();
078
079            value = toEnumSet(array);
080        }
081
082        return value;
083    }
084
085    /**
086     * See {@link WithModifiers}.
087     *
088     * @return  The {@link EnumSet} of {@link Modifier}s specified by the
089     *          annotation ({@code null} if no annotation present).
090     */
091    protected EnumSet<Modifier> getWithModifiers() {
092        EnumSet<Modifier> value = null;
093
094        if (getClass().isAnnotationPresent(WithModifiers.class)) {
095            Modifier[] array =
096                getClass().getAnnotation(WithModifiers.class).value();
097
098            value = toEnumSet(array);
099        }
100
101        return value;
102    }
103
104    /**
105     * See {@link WithoutModifiers}.
106     *
107     * @return  The {@link EnumSet} of {@link Modifier}s specified by the
108     *          annotation ({@code null} if no annotation present).
109     */
110    protected EnumSet<Modifier> getWithoutModifiers() {
111        EnumSet<Modifier> value = null;
112
113        if (getClass().isAnnotationPresent(WithoutModifiers.class)) {
114            Modifier[] array =
115                getClass().getAnnotation(WithoutModifiers.class).value();
116
117            value = toEnumSet(array);
118        }
119
120        return value;
121    }
122
123    /**
124     * See {@link ForSubclassesOf}.
125     *
126     * @return  The {@link Class} specified by the annotation ({@code null}
127     *          if no annotation present).
128     */
129    protected Class<?> getForSubclassesOf() {
130        Class<?> value = null;
131
132        if (getClass().isAnnotationPresent(ForSubclassesOf.class)) {
133            value = getClass().getAnnotation(ForSubclassesOf.class).value();
134        }
135
136        return value;
137    }
138
139    /**
140     * See {@link MustImplement}.
141     *
142     * @return  The array of {@link Class}es specified by the annotation
143     *          ({@code null} if no annotation present).
144     */
145    protected Class<?>[] getMustImplement() {
146        Class<?>[] value = null;
147
148        if (getClass().isAnnotationPresent(MustImplement.class)) {
149            value = getClass().getAnnotation(MustImplement.class).value();
150        }
151
152        return value;
153    }
154
155    @Override
156    public Set<String> getSupportedAnnotationTypes() {
157        return Collections.singleton("*");
158    }
159
160    @Override
161    public void init(ProcessingEnvironment processingEnv) {
162        super.init(processingEnv);
163
164        try {
165            EnumSet<ElementKind> kinds = EnumSet.allOf(ElementKind.class);
166
167            criteria.add(t -> kinds.contains(t.getKind()));
168
169            if (getClass().isAnnotationPresent(ForElementKinds.class)) {
170                kinds.retainAll(getForElementKinds());
171            }
172
173            if (getClass().isAnnotationPresent(WithModifiers.class)) {
174                criteria.add(withModifiers(getWithModifiers()));
175            }
176
177            if (getClass().isAnnotationPresent(WithoutModifiers.class)) {
178                criteria.add(withoutModifiers(getWithoutModifiers()));
179            }
180
181            if (getClass().isAnnotationPresent(ForSubclassesOf.class)) {
182                kinds.retainAll(ForSubclassesOf.ELEMENT_KINDS);
183                criteria.add(isAssignableTo(getForSubclassesOf()));
184            }
185
186            if (getClass().isAnnotationPresent(MustImplement.class)) {
187                criteria.add(new MustImplementCriterion());
188            }
189        } catch (Exception exception) {
190            criteria.clear();
191            criteria.add(t -> false);
192
193            print(WARNING, "%s disabled", getClass().getName());
194            /* print(WARNING, exception); */
195        }
196    }
197
198    /**
199     * @return  {@code false} always.
200     */
201    @Override
202    public boolean process(Set<? extends TypeElement> annotations,
203                           RoundEnvironment roundEnv) {
204        try {
205            walk(roundEnv.getRootElements(), Element::getEnclosedElements)
206                .filter(criteria.stream().reduce(t -> true, Predicate::and))
207                .peek(checks.stream().reduce(t -> {}, Consumer::andThen))
208                .forEach(t -> process(roundEnv, t));
209        } catch (Throwable throwable) {
210            print(ERROR, throwable);
211        }
212
213        return false;
214    }
215
216    /**
217     * Method to process each {@link Element}.  Default implementation does
218     * nothing.
219     *
220     * @param   roundEnv        The {@link RoundEnvironment}.
221     * @param   element         The {@link Element}.
222     */
223    protected void process(RoundEnvironment roundEnv, Element element) {
224    }
225
226    @NoArgsConstructor @ToString
227    private class MustImplementCriterion extends Criterion<Element> {
228        private final Class<?>[] types = getMustImplement();
229
230        @Override
231        public boolean test(Element element) {
232            boolean match = withoutModifiers(ABSTRACT).test(element);
233
234            if (match) {
235                for (Class<?> type : types) {
236                    if (! isAssignableTo(type).test(element)) {
237                        match &= false;
238
239                        print(ERROR, element,
240                              "@%s: @%s does not implement %s",
241                              MustImplement.class.getSimpleName(),
242                              element.getKind(), type.getName());
243                    }
244                }
245            }
246
247            return match;
248        }
249    }
250}