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