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