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