001package ball.annotation.processing;
002/*-
003 * ##########################################################################
004 * Utilities
005 * $Id: CompileTimeCheckProcessor.java 6099 2020-06-03 17:01:31Z ball $
006 * $HeadURL: svn+ssh://svn.hcf.dev/var/spool/scm/repository.svn/ball-util/trunk/src/main/java/ball/annotation/processing/CompileTimeCheckProcessor.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.annotation.CompileTimeCheck;
024import ball.annotation.ServiceProviderFor;
025import ball.tools.javac.AbstractTaskListener;
026import com.sun.source.util.TaskEvent;
027import java.lang.reflect.InvocationTargetException;
028import java.util.EnumSet;
029import java.util.Iterator;
030import java.util.Map;
031import java.util.TreeMap;
032import javax.annotation.processing.Processor;
033import javax.annotation.processing.RoundEnvironment;
034import javax.lang.model.element.AnnotationMirror;
035/* import javax.lang.model.element.AnnotationValue; */
036import javax.lang.model.element.Element;
037import javax.lang.model.element.Modifier;
038import javax.lang.model.element.TypeElement;
039import javax.lang.model.element.VariableElement;
040import lombok.NoArgsConstructor;
041import lombok.ToString;
042
043import static javax.lang.model.element.Modifier.FINAL;
044import static javax.lang.model.element.Modifier.STATIC;
045import static javax.lang.model.util.ElementFilter.fieldsIn;
046import static javax.tools.Diagnostic.Kind.ERROR;
047import static javax.tools.Diagnostic.Kind.WARNING;
048
049/**
050 * {@link CompileTimeCheck} {@link Processor}.
051 *
052 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
053 * @version $Revision: 6099 $
054 */
055@ServiceProviderFor({ Processor.class })
056@For({ CompileTimeCheck.class })
057@NoArgsConstructor @ToString
058public class CompileTimeCheckProcessor extends AnnotatedProcessor {
059    private static final EnumSet<Modifier> FIELD_MODIFIERS =
060        EnumSet.of(STATIC, FINAL);
061
062    private final Map<String,String> map = new TreeMap<>();
063
064    @Override
065    protected void whenAnnotationProcessingFinished() {
066        javac.addTaskListener(new TaskListenerImpl());
067    }
068
069    @Override
070    protected void process(RoundEnvironment roundEnv,
071                           TypeElement annotation, Element element) {
072        super.process(roundEnv, annotation, element);
073
074        switch (element.getKind()) {
075        case FIELD:
076            TypeElement type = (TypeElement) element.getEnclosingElement();
077            String key =
078                type.getQualifiedName() + ":" + element.getSimpleName();
079            String value = elements.getBinaryName(type).toString();
080
081            if (! map.containsKey(key)) {
082                if (with(FIELD_MODIFIERS, t -> t.getModifiers()).test(element)) {
083                    map.put(key, value);
084                } else {
085                    print(ERROR, element,
086                          "%s must be %s", element.getKind(), FIELD_MODIFIERS);
087                }
088            }
089            break;
090
091        default:
092            throw new IllegalStateException(element.getKind().name());
093            /* break; */
094        }
095    }
096
097    @NoArgsConstructor @ToString
098    private class TaskListenerImpl extends AbstractTaskListener {
099        @Override
100        public void finished(TaskEvent event) {
101            switch (event.getKind()) {
102            case GENERATE:
103                ClassLoader loader = getClassPathClassLoader(fm);
104                Iterator<Map.Entry<String,String>> iterator =
105                    map.entrySet().iterator();
106
107                while (iterator.hasNext()) {
108                    Map.Entry<String,String> entry = iterator.next();
109                    String[] names = entry.getKey().split(":", 2);
110                    TypeElement type = elements.getTypeElement(names[0]);
111                    VariableElement element =
112                        fieldsIn(type.getEnclosedElements())
113                        .stream()
114                        .filter(t -> t.getSimpleName().contentEquals(names[1]))
115                        .findFirst().orElse(null);
116                    AnnotationMirror annotation =
117                        getAnnotationMirror(element, CompileTimeCheck.class);
118                    /*
119                     * AnnotationValue value =
120                     *     getAnnotationValue(annotation, "value");
121                     */
122                    try {
123                        Class.forName(entry.getValue(), true, loader);
124                        iterator.remove();
125                    } catch (ClassNotFoundException exception) {
126                        continue;
127                    } catch (NoClassDefFoundError error) {
128                        continue;
129                    } catch (Throwable throwable) {
130                        while (throwable instanceof ExceptionInInitializerError) {
131                            throwable = throwable.getCause();
132                        }
133
134                        while (throwable instanceof InvocationTargetException) {
135                            throwable = throwable.getCause();
136                        }
137
138                        print(WARNING, element /* , annotation */,
139                              "Invalid %s initializer\n%s: %s",
140                              element.getKind(),
141                              throwable.getClass().getName(),
142                              throwable.getMessage());
143                        iterator.remove();
144                        continue;
145                    }
146                }
147                break;
148
149            default:
150                break;
151            }
152        }
153    }
154}