001 /*
002 * Copyright 2010-2013 JetBrains s.r.o.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017 package org.jetbrains.jet.lang.resolve.java.resolver;
018
019 import com.intellij.openapi.diagnostic.Logger;
020 import com.intellij.openapi.vfs.VirtualFile;
021 import org.jetbrains.annotations.NotNull;
022 import org.jetbrains.annotations.Nullable;
023 import org.jetbrains.asm4.*;
024 import org.jetbrains.asm4.commons.Method;
025 import org.jetbrains.jet.descriptors.serialization.JavaProtoBufUtil;
026 import org.jetbrains.jet.descriptors.serialization.NameResolver;
027 import org.jetbrains.jet.descriptors.serialization.ProtoBuf;
028 import org.jetbrains.jet.descriptors.serialization.descriptors.AnnotationDeserializer;
029 import org.jetbrains.jet.lang.descriptors.*;
030 import org.jetbrains.jet.lang.descriptors.annotations.AnnotationDescriptor;
031 import org.jetbrains.jet.lang.resolve.DescriptorUtils;
032 import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant;
033 import org.jetbrains.jet.lang.resolve.constants.EnumValue;
034 import org.jetbrains.jet.lang.resolve.constants.ErrorValue;
035 import org.jetbrains.jet.lang.resolve.java.JvmAbi;
036 import org.jetbrains.jet.lang.resolve.java.JvmAnnotationNames;
037 import org.jetbrains.jet.lang.resolve.java.PackageClassUtils;
038 import org.jetbrains.jet.lang.resolve.java.vfilefinder.VirtualFileFinder;
039 import org.jetbrains.jet.lang.resolve.lazy.storage.LockBasedStorageManager;
040 import org.jetbrains.jet.lang.resolve.lazy.storage.MemoizedFunctionToNotNull;
041 import org.jetbrains.jet.lang.resolve.lazy.storage.StorageManager;
042 import org.jetbrains.jet.lang.resolve.name.FqName;
043 import org.jetbrains.jet.lang.resolve.name.Name;
044 import org.jetbrains.jet.lang.types.ErrorUtils;
045
046 import javax.inject.Inject;
047 import java.io.IOException;
048 import java.util.*;
049
050 import static org.jetbrains.asm4.ClassReader.*;
051 import static org.jetbrains.jet.lang.resolve.java.DescriptorSearchRule.IGNORE_KOTLIN_SOURCES;
052 import static org.jetbrains.jet.lang.resolve.java.resolver.DeserializedResolverUtils.kotlinFqNameToJavaFqName;
053 import static org.jetbrains.jet.lang.resolve.java.resolver.DeserializedResolverUtils.naiveKotlinFqName;
054
055 public class AnnotationDescriptorDeserializer implements AnnotationDeserializer {
056 private static final Logger LOG = Logger.getInstance(AnnotationDescriptorDeserializer.class);
057
058 private JavaClassResolver javaClassResolver;
059
060 private VirtualFileFinder virtualFileFinder;
061
062 // TODO: a single instance of StorageManager for all computations in resolve-java
063 private final LockBasedStorageManager storageManager = new LockBasedStorageManager();
064
065 private final MemoizedFunctionToNotNull<VirtualFile, Map<MemberSignature, List<AnnotationDescriptor>>> memberAnnotations =
066 storageManager.createMemoizedFunction(
067 new MemoizedFunctionToNotNull<VirtualFile, Map<MemberSignature, List<AnnotationDescriptor>>>() {
068 @NotNull
069 @Override
070 public Map<MemberSignature, List<AnnotationDescriptor>> fun(@NotNull VirtualFile file) {
071 try {
072 return loadMemberAnnotationsFromFile(file);
073 }
074 catch (IOException e) {
075 LOG.error("Error loading member annotations from file: " + file, e);
076 return Collections.emptyMap();
077 }
078 }
079 }, StorageManager.ReferenceKind.STRONG);
080
081 @Inject
082 public void setVirtualFileFinder(VirtualFileFinder virtualFileFinder) {
083 this.virtualFileFinder = virtualFileFinder;
084 }
085
086 @Inject
087 public void setJavaClassResolver(JavaClassResolver javaClassResolver) {
088 this.javaClassResolver = javaClassResolver;
089 }
090
091 @NotNull
092 @Override
093 public List<AnnotationDescriptor> loadClassAnnotations(@NotNull ClassDescriptor descriptor, @NotNull ProtoBuf.Class classProto) {
094 VirtualFile virtualFile = findVirtualFileByClass(descriptor);
095 if (virtualFile == null) {
096 // This means that the resource we're constructing the descriptor from is no longer present: VirtualFileFinder had found the
097 // file earlier, but it can't now
098 LOG.error("Virtual file for loading class annotations is not found: " + descriptor);
099 return Collections.emptyList();
100 }
101 try {
102 return loadClassAnnotationsFromFile(virtualFile);
103 }
104 catch (IOException e) {
105 LOG.error("Error loading member annotations from file: " + virtualFile, e);
106 return Collections.emptyList();
107 }
108 }
109
110 @Nullable
111 private VirtualFile findVirtualFileByDescriptor(@NotNull ClassOrNamespaceDescriptor descriptor) {
112 if (descriptor instanceof ClassDescriptor) {
113 return findVirtualFileByClass((ClassDescriptor) descriptor);
114 }
115 else if (descriptor instanceof NamespaceDescriptor) {
116 return findVirtualFileByPackage((NamespaceDescriptor) descriptor);
117 }
118 else {
119 throw new IllegalStateException("Unrecognized descriptor: " + descriptor);
120 }
121 }
122
123 @Nullable
124 private VirtualFile findVirtualFileByClass(@NotNull ClassDescriptor descriptor) {
125 return virtualFileFinder.find(kotlinFqNameToJavaFqName(naiveKotlinFqName(descriptor)));
126 }
127
128 @Nullable
129 private VirtualFile findVirtualFileByPackage(@NotNull NamespaceDescriptor descriptor) {
130 return virtualFileFinder.find(PackageClassUtils.getPackageClassFqName(DescriptorUtils.getFQName(descriptor).toSafe()));
131 }
132
133 @NotNull
134 private List<AnnotationDescriptor> loadClassAnnotationsFromFile(@NotNull VirtualFile virtualFile) throws IOException {
135 final List<AnnotationDescriptor> result = new ArrayList<AnnotationDescriptor>();
136
137 new ClassReader(virtualFile.getInputStream()).accept(new ClassVisitor(Opcodes.ASM4) {
138 @Override
139 public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
140 return resolveAnnotation(desc, result);
141 }
142 }, SKIP_CODE | SKIP_DEBUG | SKIP_FRAMES);
143
144 return result;
145 }
146
147 private static boolean ignoreAnnotation(@NotNull String desc) {
148 // TODO: JvmAbi.JETBRAINS_NOT_NULL_ANNOTATION ?
149 return desc.equals(JvmAnnotationNames.KOTLIN_CLASS.getDescriptor())
150 || desc.equals(JvmAnnotationNames.KOTLIN_PACKAGE.getDescriptor())
151 || desc.startsWith("Ljet/runtime/typeinfo/");
152 }
153
154 @NotNull
155 private static FqName convertJvmDescriptorToFqName(@NotNull String desc) {
156 assert desc.startsWith("L") && desc.endsWith(";") : "Not a JVM descriptor: " + desc;
157 String fqName = desc.substring(1, desc.length() - 1).replace('$', '.').replace('/', '.');
158 return new FqName(fqName);
159 }
160
161 @Nullable
162 private AnnotationVisitor resolveAnnotation(@NotNull String desc, @NotNull final List<AnnotationDescriptor> result) {
163 if (ignoreAnnotation(desc)) return null;
164
165 FqName annotationFqName = convertJvmDescriptorToFqName(desc);
166 final ClassDescriptor annotationClass = resolveAnnotationClass(annotationFqName);
167 final AnnotationDescriptor annotation = new AnnotationDescriptor();
168 annotation.setAnnotationType(annotationClass.getDefaultType());
169
170 return new AnnotationVisitor(Opcodes.ASM4) {
171 // TODO: arrays, annotations, java.lang.Class
172 @Override
173 public void visit(String name, Object value) {
174 CompileTimeConstant<?> argument = JavaAnnotationArgumentResolver.resolveCompileTimeConstantValue(value, null);
175 setArgumentValueByName(name, argument != null ? argument : new ErrorValue("Unsupported annotation argument: " + name));
176 }
177
178 @Override
179 public void visitEnum(String name, String desc, String value) {
180 FqName fqName = convertJvmDescriptorToFqName(desc);
181 setArgumentValueByName(name, enumEntryValue(fqName, Name.identifier(value)));
182 }
183
184 @NotNull
185 private CompileTimeConstant<?> enumEntryValue(@NotNull FqName enumFqName, @NotNull Name name) {
186 ClassDescriptor enumClass = javaClassResolver.resolveClass(enumFqName, IGNORE_KOTLIN_SOURCES);
187 if (enumClass != null && enumClass.getKind() == ClassKind.ENUM_CLASS) {
188 ClassDescriptor classObject = enumClass.getClassObjectDescriptor();
189 if (classObject != null) {
190 Collection<VariableDescriptor> properties = classObject.getDefaultType().getMemberScope().getProperties(name);
191 if (properties.size() == 1) {
192 VariableDescriptor property = properties.iterator().next();
193 if (property instanceof PropertyDescriptor) {
194 return new EnumValue((PropertyDescriptor) property);
195 }
196 }
197 }
198 }
199 return new ErrorValue("Unresolved enum entry: " + enumFqName + "." + name);
200 }
201
202 @Override
203 public void visitEnd() {
204 result.add(annotation);
205 }
206
207 private void setArgumentValueByName(@NotNull String name, @NotNull CompileTimeConstant<?> argumentValue) {
208 ValueParameterDescriptor parameter =
209 DescriptorResolverUtils.getAnnotationParameterByName(Name.identifier(name), annotationClass);
210 if (parameter != null) {
211 annotation.setValueArgument(parameter, argumentValue);
212 }
213 }
214 };
215 }
216
217 @NotNull
218 private ClassDescriptor resolveAnnotationClass(@NotNull FqName fqName) {
219 ClassDescriptor annotationClass = javaClassResolver.resolveClass(fqName, IGNORE_KOTLIN_SOURCES);
220 return annotationClass != null ? annotationClass : ErrorUtils.getErrorClass();
221 }
222
223 @NotNull
224 @Override
225 public List<AnnotationDescriptor> loadCallableAnnotations(
226 @NotNull ClassOrNamespaceDescriptor container,
227 @NotNull ProtoBuf.Callable proto,
228 @NotNull NameResolver nameResolver,
229 @NotNull AnnotatedCallableKind kind
230 ) {
231 MemberSignature signature = getCallableSignature(proto, nameResolver, kind);
232 if (signature == null) return Collections.emptyList();
233
234 VirtualFile file = getVirtualFileWithMemberAnnotations(container, proto, nameResolver);
235 if (file == null) {
236 LOG.error("Virtual file for loading member annotations is not found: " + container);
237 }
238
239 List<AnnotationDescriptor> annotations = memberAnnotations.fun(file).get(signature);
240 return annotations == null ? Collections.<AnnotationDescriptor>emptyList() : annotations;
241 }
242
243 @Nullable
244 private VirtualFile getVirtualFileWithMemberAnnotations(
245 @NotNull ClassOrNamespaceDescriptor container,
246 @NotNull ProtoBuf.Callable proto,
247 @NotNull NameResolver nameResolver
248 ) {
249 if (container instanceof NamespaceDescriptor) {
250 Name name = JavaProtoBufUtil.loadSrcClassName(proto, nameResolver);
251 if (name != null) {
252 // To locate a package$src class, we first find the facade virtual file (*Package.class) and then look up the $src file in
253 // the same directory. This hack is needed because FileManager doesn't find classfiles for $src classes
254 VirtualFile facadeFile = findVirtualFileByPackage((NamespaceDescriptor) container);
255 if (facadeFile != null) {
256 VirtualFile srcFile = facadeFile.getParent().findChild(name + ".class");
257 if (srcFile != null) {
258 return srcFile;
259 }
260 }
261 }
262 return null;
263 }
264 else if (container instanceof ClassDescriptor && ((ClassDescriptor) container).getKind() == ClassKind.CLASS_OBJECT) {
265 // Backing fields of properties of a class object are generated in the outer class
266 if (JavaProtoBufUtil.isStaticFieldInOuter(proto)) {
267 return findVirtualFileByDescriptor((ClassOrNamespaceDescriptor) container.getContainingDeclaration());
268 }
269 }
270
271 return findVirtualFileByDescriptor(container);
272 }
273
274 @Nullable
275 private static MemberSignature getCallableSignature(
276 @NotNull ProtoBuf.Callable proto,
277 @NotNull NameResolver nameResolver,
278 @NotNull AnnotatedCallableKind kind
279 ) {
280 switch (kind) {
281 case FUNCTION:
282 return MemberSignature.fromMethod(JavaProtoBufUtil.loadMethodSignature(proto, nameResolver));
283 case PROPERTY_GETTER:
284 return MemberSignature.fromMethod(JavaProtoBufUtil.loadPropertyGetterSignature(proto, nameResolver));
285 case PROPERTY_SETTER:
286 return MemberSignature.fromMethod(JavaProtoBufUtil.loadPropertySetterSignature(proto, nameResolver));
287 case PROPERTY:
288 JavaProtoBufUtil.PropertyData data = JavaProtoBufUtil.loadPropertyData(proto, nameResolver);
289 return data == null ? null :
290 MemberSignature.fromPropertyData(data.getFieldType(), data.getFieldName(), data.getSyntheticMethodName());
291 default:
292 return null;
293 }
294 }
295
296 @NotNull
297 private Map<MemberSignature, List<AnnotationDescriptor>> loadMemberAnnotationsFromFile(@NotNull VirtualFile file) throws IOException {
298 final Map<MemberSignature, List<AnnotationDescriptor>> memberAnnotations =
299 new HashMap<MemberSignature, List<AnnotationDescriptor>>();
300
301 new ClassReader(file.getInputStream()).accept(new ClassVisitor(Opcodes.ASM4) {
302 @Override
303 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
304 final MemberSignature methodSignature = MemberSignature.fromMethodNameAndDesc(name, desc);
305 final List<AnnotationDescriptor> result = new ArrayList<AnnotationDescriptor>();
306
307 return new MethodVisitor(Opcodes.ASM4) {
308 @Override
309 public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
310 return resolveAnnotation(desc, result);
311 }
312
313 @Override
314 public void visitEnd() {
315 if (!result.isEmpty()) {
316 memberAnnotations.put(methodSignature, result);
317 }
318 }
319 };
320 }
321
322 @Override
323 public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
324 final MemberSignature fieldSignature = MemberSignature.fromFieldNameAndDesc(name, desc);
325 final List<AnnotationDescriptor> result = new ArrayList<AnnotationDescriptor>();
326
327 return new FieldVisitor(Opcodes.ASM4) {
328 @Override
329 public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
330 return resolveAnnotation(desc, result);
331 }
332
333 @Override
334 public void visitEnd() {
335 if (!result.isEmpty()) {
336 memberAnnotations.put(fieldSignature, result);
337 }
338 }
339 };
340 }
341 }, SKIP_CODE | SKIP_DEBUG | SKIP_FRAMES);
342
343 return memberAnnotations;
344 }
345
346 // The purpose of this class is to hold a unique signature of either a method or a field, so that annotations on a member can be put
347 // into a map indexed by these signatures
348 private static final class MemberSignature {
349 private final String signature;
350
351 private MemberSignature(@NotNull String signature) {
352 this.signature = signature;
353 }
354
355 @Nullable
356 public static MemberSignature fromPropertyData(
357 @Nullable Type fieldType,
358 @Nullable String fieldName,
359 @Nullable String syntheticMethodName
360 ) {
361 if (fieldName != null && fieldType != null) {
362 return fromFieldNameAndDesc(fieldName, fieldType.getDescriptor());
363 }
364 else if (syntheticMethodName != null) {
365 return fromMethodNameAndDesc(syntheticMethodName, JvmAbi.ANNOTATED_PROPERTY_METHOD_SIGNATURE);
366 }
367 else {
368 return null;
369 }
370 }
371
372 @Nullable
373 public static MemberSignature fromMethod(@Nullable Method method) {
374 return method == null ? null : fromMethodNameAndDesc(method.getName(), method.getDescriptor());
375 }
376
377 @NotNull
378 public static MemberSignature fromMethodNameAndDesc(@NotNull String name, @NotNull String desc) {
379 return new MemberSignature(name + desc);
380 }
381
382 @NotNull
383 public static MemberSignature fromFieldNameAndDesc(@NotNull String name, @NotNull String desc) {
384 return new MemberSignature(name + "#" + desc);
385 }
386
387 @Override
388 public int hashCode() {
389 return signature.hashCode();
390 }
391
392 @Override
393 public boolean equals(Object o) {
394 return o instanceof MemberSignature && signature.equals(((MemberSignature) o).signature);
395 }
396
397 @Override
398 public String toString() {
399 return signature;
400 }
401 }
402
403 @NotNull
404 @Override
405 public List<AnnotationDescriptor> loadValueParameterAnnotations(@NotNull ProtoBuf.Callable.ValueParameter parameterProto) {
406 throw new UnsupportedOperationException(); // TODO
407 }
408 }