1 /* 2 * Copyright 2019 Google Inc. All Rights Reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.turbine.processing; 18 19 import static com.google.common.collect.ImmutableList.toImmutableList; 20 import static com.google.common.collect.ImmutableMap.toImmutableMap; 21 import static com.google.common.truth.Truth.assertThat; 22 23 import com.google.auto.common.AnnotationValues; 24 import com.google.common.base.Joiner; 25 import com.google.common.collect.ImmutableList; 26 import com.google.common.collect.ImmutableMap; 27 import com.google.common.collect.ListMultimap; 28 import com.google.common.collect.MultimapBuilder; 29 import com.google.common.collect.Multimaps; 30 import com.google.turbine.binder.Binder; 31 import com.google.turbine.binder.bound.TypeBoundClass; 32 import com.google.turbine.binder.env.CompoundEnv; 33 import com.google.turbine.binder.env.Env; 34 import com.google.turbine.binder.env.SimpleEnv; 35 import com.google.turbine.binder.sym.ClassSymbol; 36 import com.google.turbine.lower.IntegrationTestSupport; 37 import com.google.turbine.lower.IntegrationTestSupport.TestInput; 38 import com.google.turbine.processing.TurbineElement.TurbineTypeElement; 39 import com.google.turbine.testing.TestClassPaths; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.Optional; 43 import java.util.stream.Stream; 44 import javax.lang.model.element.AnnotationMirror; 45 import javax.lang.model.element.AnnotationValue; 46 import javax.lang.model.element.Element; 47 import javax.lang.model.element.ElementKind; 48 import javax.lang.model.element.ExecutableElement; 49 import javax.lang.model.element.TypeElement; 50 import javax.lang.model.element.VariableElement; 51 import javax.lang.model.type.TypeMirror; 52 import javax.lang.model.util.AbstractAnnotationValueVisitor8; 53 import org.junit.Test; 54 import org.junit.runner.RunWith; 55 import org.junit.runners.JUnit4; 56 57 @RunWith(JUnit4.class) 58 public class TurbineAnnotationMirrorTest { 59 getAnnotation( List<? extends AnnotationMirror> annotationMirrors, String name)60 private AnnotationMirror getAnnotation( 61 List<? extends AnnotationMirror> annotationMirrors, String name) { 62 return annotationMirrors.stream() 63 .filter(x -> x.getAnnotationType().asElement().getSimpleName().contentEquals(name)) 64 .findFirst() 65 .get(); 66 } 67 values(AnnotationMirror a)68 private ImmutableMap<String, Object> values(AnnotationMirror a) { 69 return values(a.getElementValues()); 70 } 71 72 /** 73 * Returns a map from the name of annotation elements to their values, see also {@link 74 * #getValue(AnnotationValue)}. 75 */ values( Map<? extends ExecutableElement, ? extends AnnotationValue> values)76 private ImmutableMap<String, Object> values( 77 Map<? extends ExecutableElement, ? extends AnnotationValue> values) { 78 return values.entrySet().stream() 79 .collect( 80 toImmutableMap( 81 e -> e.getKey().getSimpleName().toString(), e -> getValue(e.getValue()))); 82 } 83 84 /** 85 * Returns the given annotation value as an Object (for primitives), or a list (for arrays), or 86 * strings (for compound annotations, enums, and class literals). 87 */ getValue(AnnotationValue value)88 static Object getValue(AnnotationValue value) { 89 return value.accept( 90 new AbstractAnnotationValueVisitor8<Object, Void>() { 91 @Override 92 public Object visitBoolean(boolean b, Void unused) { 93 return b; 94 } 95 96 @Override 97 public Object visitByte(byte b, Void unused) { 98 return b; 99 } 100 101 @Override 102 public Object visitChar(char c, Void unused) { 103 return c; 104 } 105 106 @Override 107 public Object visitDouble(double d, Void unused) { 108 return d; 109 } 110 111 @Override 112 public Object visitFloat(float f, Void unused) { 113 return f; 114 } 115 116 @Override 117 public Object visitInt(int i, Void unused) { 118 return i; 119 } 120 121 @Override 122 public Object visitLong(long i, Void unused) { 123 return i; 124 } 125 126 @Override 127 public Object visitShort(short s, Void unused) { 128 return s; 129 } 130 131 @Override 132 public Object visitString(String s, Void unused) { 133 return s; 134 } 135 136 @Override 137 public Object visitType(TypeMirror t, Void unused) { 138 return AnnotationValues.toString(value); 139 } 140 141 @Override 142 public Object visitEnumConstant(VariableElement c, Void unused) { 143 return AnnotationValues.toString(value); 144 } 145 146 @Override 147 public Object visitAnnotation(AnnotationMirror a, Void unused) { 148 return AnnotationValues.toString(value); 149 } 150 151 @Override 152 public Object visitArray(List<? extends AnnotationValue> vals, Void unused) { 153 return vals.stream().map(v -> v.accept(this, null)).collect(toImmutableList()); 154 } 155 }, 156 null); 157 } 158 159 private static Stream<String> typeAnnotationNames(Element e) { 160 return e.asType().getAnnotationMirrors().stream() 161 .map(anno -> anno.getAnnotationType().asElement().getSimpleName().toString()); 162 } 163 164 @Test 165 public void test() throws Exception { 166 TestInput input = 167 TestInput.parse( 168 Joiner.on('\n') 169 .join( 170 "=== Test.java ===", 171 "import java.lang.annotation.ElementType;", 172 "import java.lang.annotation.Retention;", 173 "import java.lang.annotation.RetentionPolicy;", 174 "import java.lang.annotation.Target;", 175 "import java.util.Map;", 176 "import java.util.Map.Entry;", 177 "@Retention(RetentionPolicy.RUNTIME)", 178 "@interface A {", 179 " int x() default 0;", 180 " int y() default 1;", 181 " int[] z() default {};", 182 "}", 183 "@interface B {", 184 " Class<?> c() default String.class;", 185 " ElementType e() default ElementType.TYPE_USE;", 186 " A f() default @A;", 187 "}", 188 "@Retention(RetentionPolicy.RUNTIME)", 189 "@Target(ElementType.TYPE_USE)", 190 "@interface T {}", 191 "@Target(ElementType.TYPE_USE)", 192 "@interface V {}", 193 "", 194 "@A(y = 42, z = {43})", 195 "@B", 196 "class Test {", 197 " class I {}", 198 " @T Test. @V I f;", 199 " Map. @T Entry g;", 200 " @T Entry h;", 201 "}", 202 "")); 203 204 Binder.BindingResult bound = 205 IntegrationTestSupport.turbineAnalysis( 206 input.sources, 207 ImmutableList.of(), 208 TestClassPaths.TURBINE_BOOTCLASSPATH, 209 Optional.empty()); 210 211 Env<ClassSymbol, TypeBoundClass> env = 212 CompoundEnv.<ClassSymbol, TypeBoundClass>of(bound.classPathEnv()) 213 .append(new SimpleEnv<>(bound.units())); 214 ModelFactory factory = new ModelFactory(env, ClassLoader.getSystemClassLoader(), bound.tli()); 215 TurbineTypes turbineTypes = new TurbineTypes(factory); 216 TurbineElements turbineElements = new TurbineElements(factory, turbineTypes); 217 218 TurbineTypeElement te = factory.typeElement(new ClassSymbol("Test")); 219 220 AnnotationMirror a = getAnnotation(te.getAnnotationMirrors(), "A"); 221 ((TypeElement) a.getAnnotationType().asElement()).getQualifiedName().contentEquals("A"); 222 assertThat(values(a)).containsExactly("y", 42, "z", ImmutableList.of(43)); 223 assertThat(values(turbineElements.getElementValuesWithDefaults(a))) 224 .containsExactly( 225 "x", 0, 226 "y", 42, 227 "z", ImmutableList.of(43)); 228 229 AnnotationMirror b = getAnnotation(te.getAnnotationMirrors(), "B"); 230 assertThat(values(turbineElements.getElementValuesWithDefaults(b))) 231 .containsExactly( 232 "c", "java.lang.String.class", 233 "e", "java.lang.annotation.ElementType.TYPE_USE", 234 "f", "@A"); 235 236 ListMultimap<String, String> fieldTypeAnnotations = 237 te.getEnclosedElements().stream() 238 .filter(e -> e.getKind().equals(ElementKind.FIELD)) 239 .collect( 240 Multimaps.flatteningToMultimap( 241 e -> e.getSimpleName().toString(), 242 e -> typeAnnotationNames(e), 243 MultimapBuilder.linkedHashKeys().arrayListValues()::build)); 244 assertThat(fieldTypeAnnotations) 245 .containsExactly( 246 "f", "V", 247 "g", "T", 248 "h", "T"); 249 } 250 } 251