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}