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}