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.kotlin;
018    
019    import jet.Function1;
020    import org.jetbrains.annotations.NotNull;
021    import org.jetbrains.annotations.Nullable;
022    import org.jetbrains.jet.descriptors.serialization.JavaProtoBuf;
023    import org.jetbrains.jet.descriptors.serialization.NameResolver;
024    import org.jetbrains.jet.descriptors.serialization.ProtoBuf;
025    import org.jetbrains.jet.descriptors.serialization.descriptors.AnnotationDeserializer;
026    import org.jetbrains.jet.lang.descriptors.*;
027    import org.jetbrains.jet.lang.descriptors.annotations.AnnotationDescriptor;
028    import org.jetbrains.jet.lang.descriptors.annotations.AnnotationDescriptorImpl;
029    import org.jetbrains.jet.lang.resolve.DescriptorUtils;
030    import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant;
031    import org.jetbrains.jet.lang.resolve.constants.EnumValue;
032    import org.jetbrains.jet.lang.resolve.constants.ErrorValue;
033    import org.jetbrains.jet.lang.resolve.java.JvmAnnotationNames;
034    import org.jetbrains.jet.lang.resolve.java.JvmClassName;
035    import org.jetbrains.jet.lang.resolve.java.PackageClassUtils;
036    import org.jetbrains.jet.lang.resolve.java.resolver.*;
037    import org.jetbrains.jet.lang.resolve.name.FqName;
038    import org.jetbrains.jet.lang.resolve.name.Name;
039    import org.jetbrains.jet.lang.types.ErrorUtils;
040    import org.jetbrains.jet.storage.LockBasedStorageManager;
041    import org.jetbrains.jet.storage.MemoizedFunctionToNotNull;
042    
043    import javax.inject.Inject;
044    import java.io.IOException;
045    import java.util.*;
046    
047    import static org.jetbrains.jet.lang.resolve.DescriptorUtils.isClassObject;
048    import static org.jetbrains.jet.lang.resolve.DescriptorUtils.isTrait;
049    import static org.jetbrains.jet.lang.resolve.java.DescriptorSearchRule.IGNORE_KOTLIN_SOURCES;
050    import static org.jetbrains.jet.lang.resolve.kotlin.DeserializedResolverUtils.kotlinFqNameToJavaFqName;
051    import static org.jetbrains.jet.lang.resolve.kotlin.DeserializedResolverUtils.naiveKotlinFqName;
052    
053    public class AnnotationDescriptorDeserializer implements AnnotationDeserializer {
054        private JavaClassResolver javaClassResolver;
055        private KotlinClassFinder kotlinClassFinder;
056        private ErrorReporter errorReporter;
057    
058        // TODO: a single instance of StorageManager for all computations in resolve-java
059        private final LockBasedStorageManager storageManager = new LockBasedStorageManager();
060    
061        private final MemoizedFunctionToNotNull<KotlinJvmBinaryClass, Map<MemberSignature, List<AnnotationDescriptor>>> memberAnnotations =
062                storageManager.createMemoizedFunction(
063                        new Function1<KotlinJvmBinaryClass, Map<MemberSignature, List<AnnotationDescriptor>>>() {
064                            @NotNull
065                            @Override
066                            public Map<MemberSignature, List<AnnotationDescriptor>> invoke(@NotNull KotlinJvmBinaryClass kotlinClass) {
067                                try {
068                                    return loadMemberAnnotationsFromClass(kotlinClass);
069                                }
070                                catch (IOException e) {
071                                    errorReporter.reportAnnotationLoadingError(
072                                            "Error loading member annotations from Kotlin class: " + kotlinClass, e);
073                                    return Collections.emptyMap();
074                                }
075                            }
076                        });
077    
078        @Inject
079        public void setJavaClassResolver(JavaClassResolver javaClassResolver) {
080            this.javaClassResolver = javaClassResolver;
081        }
082    
083        @Inject
084        public void setKotlinClassFinder(KotlinClassFinder kotlinClassFinder) {
085            this.kotlinClassFinder = kotlinClassFinder;
086        }
087    
088        @Inject
089        public void setErrorReporter(ErrorReporter errorReporter) {
090            this.errorReporter = errorReporter;
091        }
092    
093        @NotNull
094        @Override
095        public List<AnnotationDescriptor> loadClassAnnotations(@NotNull ClassDescriptor descriptor, @NotNull ProtoBuf.Class classProto) {
096            KotlinJvmBinaryClass kotlinClass = findKotlinClassByDescriptor(descriptor);
097            if (kotlinClass == null) {
098                // This means that the resource we're constructing the descriptor from is no longer present: KotlinClassFinder had found the
099                // class earlier, but it can't now
100                errorReporter.reportAnnotationLoadingError("Kotlin class for loading class annotations is not found: " + descriptor, null);
101                return Collections.emptyList();
102            }
103            try {
104                return loadClassAnnotationsFromClass(kotlinClass);
105            }
106            catch (IOException e) {
107                errorReporter.reportAnnotationLoadingError("Error loading member annotations from Kotlin class: " + kotlinClass, e);
108                return Collections.emptyList();
109            }
110        }
111    
112        @Nullable
113        private KotlinJvmBinaryClass findKotlinClassByDescriptor(@NotNull ClassOrNamespaceDescriptor descriptor) {
114            if (descriptor instanceof ClassDescriptor) {
115                return kotlinClassFinder.find(kotlinFqNameToJavaFqName(naiveKotlinFqName((ClassDescriptor) descriptor)));
116            }
117            else if (descriptor instanceof NamespaceDescriptor) {
118                return kotlinClassFinder.find(PackageClassUtils.getPackageClassFqName(DescriptorUtils.getFQName(descriptor).toSafe()));
119            }
120            else {
121                throw new IllegalStateException("Unrecognized descriptor: " + descriptor);
122            }
123        }
124    
125        @NotNull
126        private List<AnnotationDescriptor> loadClassAnnotationsFromClass(@NotNull KotlinJvmBinaryClass kotlinClass) throws IOException {
127            final List<AnnotationDescriptor> result = new ArrayList<AnnotationDescriptor>();
128    
129            kotlinClass.loadClassAnnotations(new KotlinJvmBinaryClass.AnnotationVisitor() {
130                @Nullable
131                @Override
132                public KotlinJvmBinaryClass.AnnotationArgumentVisitor visitAnnotation(@NotNull JvmClassName className) {
133                    return resolveAnnotation(className, result);
134                }
135    
136                @Override
137                public void visitEnd() {
138                }
139            });
140    
141            return result;
142        }
143    
144        private static boolean ignoreAnnotation(@NotNull JvmClassName className) {
145            return className.equals(JvmClassName.byFqNameWithoutInnerClasses(JvmAnnotationNames.KOTLIN_CLASS))
146                   || className.equals(JvmClassName.byFqNameWithoutInnerClasses(JvmAnnotationNames.KOTLIN_PACKAGE))
147                   || className.equals(JvmClassName.byFqNameWithoutInnerClasses(JavaAnnotationResolver.JETBRAINS_NOT_NULL_ANNOTATION))
148                   || className.equals(JvmClassName.byFqNameWithoutInnerClasses(JavaAnnotationResolver.JETBRAINS_NULLABLE_ANNOTATION))
149                   || className.getInternalName().startsWith("jet/runtime/typeinfo/");
150        }
151    
152        @Nullable
153        private KotlinJvmBinaryClass.AnnotationArgumentVisitor resolveAnnotation(
154                @NotNull JvmClassName className,
155                @NotNull final List<AnnotationDescriptor> result
156        ) {
157            if (ignoreAnnotation(className)) return null;
158    
159            final ClassDescriptor annotationClass = resolveClass(className);
160            final AnnotationDescriptorImpl annotation = new AnnotationDescriptorImpl();
161            annotation.setAnnotationType(annotationClass.getDefaultType());
162    
163            return new KotlinJvmBinaryClass.AnnotationArgumentVisitor() {
164                @Override
165                public void visit(@Nullable Name name, @Nullable Object value) {
166                    if (name != null) {
167                        CompileTimeConstant<?> argument = JavaAnnotationArgumentResolver.resolveCompileTimeConstantValue(value, null);
168                        setArgumentValueByName(name, argument != null ? argument : ErrorValue.create("Unsupported annotation argument: " + name));
169                    }
170                }
171    
172                @Override
173                public void visitEnum(@NotNull Name name, @NotNull JvmClassName enumClassName, @NotNull Name enumEntryName) {
174                    setArgumentValueByName(name, enumEntryValue(enumClassName, enumEntryName));
175                }
176    
177                @Nullable
178                @Override
179                public KotlinJvmBinaryClass.AnnotationArgumentVisitor visitArray(@NotNull Name name) {
180                    // TODO: support arrays
181                    return null;
182                }
183    
184                @NotNull
185                private CompileTimeConstant<?> enumEntryValue(@NotNull JvmClassName enumClassName, @NotNull Name name) {
186                    ClassDescriptor enumClass = resolveClass(enumClassName);
187                    if (enumClass.getKind() == ClassKind.ENUM_CLASS) {
188                        ClassifierDescriptor classifier = enumClass.getUnsubstitutedInnerClassesScope().getClassifier(name);
189                        if (classifier instanceof ClassDescriptor) {
190                            return new EnumValue((ClassDescriptor) classifier);
191                        }
192                    }
193                    return ErrorValue.create("Unresolved enum entry: " + enumClassName.getInternalName() + "." + name);
194                }
195    
196                @Override
197                public void visitEnd() {
198                    result.add(annotation);
199                }
200    
201                private void setArgumentValueByName(@NotNull Name name, @NotNull CompileTimeConstant<?> argumentValue) {
202                    ValueParameterDescriptor parameter = DescriptorResolverUtils.getAnnotationParameterByName(name, annotationClass);
203                    if (parameter != null) {
204                        annotation.setValueArgument(parameter, argumentValue);
205                    }
206                }
207            };
208        }
209    
210        @NotNull
211        private ClassDescriptor resolveClass(@NotNull JvmClassName className) {
212            ClassDescriptor annotationClass = javaClassResolver.resolveClass(className.getFqNameForClassNameWithoutDollars(),
213                                                                             IGNORE_KOTLIN_SOURCES);
214            return annotationClass != null ? annotationClass : ErrorUtils.getErrorClass();
215        }
216    
217        @NotNull
218        @Override
219        public List<AnnotationDescriptor> loadCallableAnnotations(
220                @NotNull ClassOrNamespaceDescriptor container,
221                @NotNull ProtoBuf.Callable proto,
222                @NotNull NameResolver nameResolver,
223                @NotNull AnnotatedCallableKind kind
224        ) {
225            MemberSignature signature = getCallableSignature(proto, nameResolver, kind);
226            if (signature == null) return Collections.emptyList();
227    
228            return findClassAndLoadMemberAnnotations(container, proto, nameResolver, kind, signature);
229        }
230    
231        @NotNull
232        private List<AnnotationDescriptor> findClassAndLoadMemberAnnotations(
233                @NotNull ClassOrNamespaceDescriptor container,
234                @NotNull ProtoBuf.Callable proto,
235                @NotNull NameResolver nameResolver,
236                @NotNull AnnotatedCallableKind kind,
237                @NotNull MemberSignature signature
238        ) {
239            KotlinJvmBinaryClass kotlinClass = findClassWithMemberAnnotations(container, proto, nameResolver, kind);
240            if (kotlinClass == null) {
241                errorReporter.reportAnnotationLoadingError("Kotlin class for loading member annotations is not found: " + container, null);
242                return Collections.emptyList();
243            }
244    
245            List<AnnotationDescriptor> annotations = memberAnnotations.invoke(kotlinClass).get(signature);
246            return annotations == null ? Collections.<AnnotationDescriptor>emptyList() : annotations;
247        }
248    
249        @Nullable
250        private KotlinJvmBinaryClass findClassWithMemberAnnotations(
251                @NotNull ClassOrNamespaceDescriptor container,
252                @NotNull ProtoBuf.Callable proto,
253                @NotNull NameResolver nameResolver,
254                @NotNull AnnotatedCallableKind kind
255        ) {
256            if (container instanceof NamespaceDescriptor) {
257                return loadPackageFragmentClassFqName((NamespaceDescriptor) container, proto, nameResolver);
258            }
259            else if (isClassObject(container) && isStaticFieldInOuter(proto)) {
260                // Backing fields of properties of a class object are generated in the outer class
261                return findKotlinClassByDescriptor((ClassOrNamespaceDescriptor) container.getContainingDeclaration());
262            }
263            else if (isTrait(container) && kind == AnnotatedCallableKind.PROPERTY) {
264                NamespaceDescriptor containingPackage = DescriptorUtils.getParentOfType(container, NamespaceDescriptor.class);
265                assert containingPackage != null : "Trait must have a namespace among his parents: " + container;
266    
267                if (proto.hasExtension(JavaProtoBuf.implClassName)) {
268                    Name tImplName = nameResolver.getName(proto.getExtension(JavaProtoBuf.implClassName));
269                    return kotlinClassFinder.find(containingPackage.getFqName().child(tImplName));
270                }
271                return null;
272            }
273    
274            return findKotlinClassByDescriptor(container);
275        }
276    
277        @Nullable
278        private KotlinJvmBinaryClass loadPackageFragmentClassFqName(
279                @NotNull NamespaceDescriptor container,
280                @NotNull ProtoBuf.Callable proto,
281                @NotNull NameResolver nameResolver
282        ) {
283            if (proto.hasExtension(JavaProtoBuf.implClassName)) {
284                Name name = nameResolver.getName(proto.getExtension(JavaProtoBuf.implClassName));
285                FqName fqName = PackageClassUtils.getPackageClassFqName(container.getFqName()).parent().child(name);
286                return kotlinClassFinder.find(fqName);
287            }
288            return null;
289        }
290    
291        private static boolean isStaticFieldInOuter(@NotNull ProtoBuf.Callable proto) {
292            if (!proto.hasExtension(JavaProtoBuf.propertySignature)) return false;
293            JavaProtoBuf.JavaPropertySignature propertySignature = proto.getExtension(JavaProtoBuf.propertySignature);
294            return propertySignature.hasField() && propertySignature.getField().getIsStaticInOuter();
295        }
296    
297        @Nullable
298        private static MemberSignature getCallableSignature(
299                @NotNull ProtoBuf.Callable proto,
300                @NotNull NameResolver nameResolver,
301                @NotNull AnnotatedCallableKind kind
302        ) {
303            SignatureDeserializer deserializer = new SignatureDeserializer(nameResolver);
304            switch (kind) {
305                case FUNCTION:
306                    if (proto.hasExtension(JavaProtoBuf.methodSignature)) {
307                        return deserializer.methodSignature(proto.getExtension(JavaProtoBuf.methodSignature));
308                    }
309                    break;
310                case PROPERTY_GETTER:
311                    if (proto.hasExtension(JavaProtoBuf.propertySignature)) {
312                        return deserializer.methodSignature(proto.getExtension(JavaProtoBuf.propertySignature).getGetter());
313                    }
314                    break;
315                case PROPERTY_SETTER:
316                    if (proto.hasExtension(JavaProtoBuf.propertySignature)) {
317                        return deserializer.methodSignature(proto.getExtension(JavaProtoBuf.propertySignature).getSetter());
318                    }
319                    break;
320                case PROPERTY:
321                    if (proto.hasExtension(JavaProtoBuf.propertySignature)) {
322                        JavaProtoBuf.JavaPropertySignature propertySignature = proto.getExtension(JavaProtoBuf.propertySignature);
323    
324                        if (propertySignature.hasField()) {
325                            JavaProtoBuf.JavaFieldSignature field = propertySignature.getField();
326                            String type = deserializer.typeDescriptor(field.getType());
327                            Name name = nameResolver.getName(field.getName());
328                            return MemberSignature.fromFieldNameAndDesc(name, type);
329                        }
330                        else if (propertySignature.hasSyntheticMethod()) {
331                            return deserializer.methodSignature(propertySignature.getSyntheticMethod());
332                        }
333                    }
334                    break;
335            }
336            return null;
337        }
338    
339        @NotNull
340        private Map<MemberSignature, List<AnnotationDescriptor>> loadMemberAnnotationsFromClass(@NotNull KotlinJvmBinaryClass kotlinClass)
341                throws IOException {
342            final Map<MemberSignature, List<AnnotationDescriptor>> memberAnnotations =
343                    new HashMap<MemberSignature, List<AnnotationDescriptor>>();
344    
345            kotlinClass.loadMemberAnnotations(new KotlinJvmBinaryClass.MemberVisitor() {
346                @Nullable
347                @Override
348                public KotlinJvmBinaryClass.MethodAnnotationVisitor visitMethod(@NotNull Name name, @NotNull String desc) {
349                    return new AnnotationVisitorForMethod(MemberSignature.fromMethodNameAndDesc(name, desc));
350                }
351    
352                @Nullable
353                @Override
354                public KotlinJvmBinaryClass.AnnotationVisitor visitField(@NotNull Name name, @NotNull String desc) {
355                    return new MemberAnnotationVisitor(MemberSignature.fromFieldNameAndDesc(name, desc));
356                }
357    
358                class AnnotationVisitorForMethod extends MemberAnnotationVisitor implements KotlinJvmBinaryClass.MethodAnnotationVisitor {
359                    public AnnotationVisitorForMethod(@NotNull MemberSignature signature) {
360                        super(signature);
361                    }
362    
363                    @Nullable
364                    @Override
365                    public KotlinJvmBinaryClass.AnnotationArgumentVisitor visitParameterAnnotation(int index, @NotNull JvmClassName className) {
366                        MemberSignature paramSignature = MemberSignature.fromMethodSignatureAndParameterIndex(signature, index);
367                        List<AnnotationDescriptor> result = memberAnnotations.get(paramSignature);
368                        if (result == null) {
369                            result = new ArrayList<AnnotationDescriptor>();
370                            memberAnnotations.put(paramSignature, result);
371                        }
372                        return resolveAnnotation(className, result);
373                    }
374                }
375    
376                class MemberAnnotationVisitor implements KotlinJvmBinaryClass.AnnotationVisitor {
377                    private final List<AnnotationDescriptor> result = new ArrayList<AnnotationDescriptor>();
378                    protected final MemberSignature signature;
379    
380                    public MemberAnnotationVisitor(@NotNull MemberSignature signature) {
381                        this.signature = signature;
382                    }
383    
384                    @Nullable
385                    @Override
386                    public KotlinJvmBinaryClass.AnnotationArgumentVisitor visitAnnotation(@NotNull JvmClassName className) {
387                        return resolveAnnotation(className, result);
388                    }
389    
390                    @Override
391                    public void visitEnd() {
392                        if (!result.isEmpty()) {
393                            memberAnnotations.put(signature, result);
394                        }
395                    }
396                }
397            });
398    
399            return memberAnnotations;
400        }
401    
402        // 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
403        // into a map indexed by these signatures
404        private static final class MemberSignature {
405            private final String signature;
406    
407            private MemberSignature(@NotNull String signature) {
408                this.signature = signature;
409            }
410    
411            @NotNull
412            public static MemberSignature fromMethodNameAndDesc(@NotNull Name name, @NotNull String desc) {
413                return new MemberSignature(name.asString() + desc);
414            }
415    
416            @NotNull
417            public static MemberSignature fromFieldNameAndDesc(@NotNull Name name, @NotNull String desc) {
418                return new MemberSignature(name.asString() + "#" + desc);
419            }
420    
421            @NotNull
422            public static MemberSignature fromMethodSignatureAndParameterIndex(@NotNull MemberSignature signature, int index) {
423                return new MemberSignature(signature.signature + "@" + index);
424            }
425    
426            @Override
427            public int hashCode() {
428                return signature.hashCode();
429            }
430    
431            @Override
432            public boolean equals(Object o) {
433                return o instanceof MemberSignature && signature.equals(((MemberSignature) o).signature);
434            }
435    
436            @Override
437            public String toString() {
438                return signature;
439            }
440        }
441    
442        private static class SignatureDeserializer {
443            // These types are ordered according to their sorts, this is significant for deserialization
444            private static final char[] PRIMITIVE_TYPES = new char[] { 'V', 'Z', 'C', 'B', 'S', 'I', 'F', 'J', 'D' };
445    
446            private final NameResolver nameResolver;
447    
448            public SignatureDeserializer(@NotNull NameResolver nameResolver) {
449                this.nameResolver = nameResolver;
450            }
451    
452            @NotNull
453            public MemberSignature methodSignature(@NotNull JavaProtoBuf.JavaMethodSignature signature) {
454                Name name = nameResolver.getName(signature.getName());
455    
456                StringBuilder sb = new StringBuilder();
457                sb.append('(');
458                for (int i = 0, length = signature.getParameterTypeCount(); i < length; i++) {
459                    typeDescriptor(signature.getParameterType(i), sb);
460                }
461                sb.append(')');
462                typeDescriptor(signature.getReturnType(), sb);
463    
464                return MemberSignature.fromMethodNameAndDesc(name, sb.toString());
465            }
466    
467            @NotNull
468            public String typeDescriptor(@NotNull JavaProtoBuf.JavaType type) {
469                return typeDescriptor(type, new StringBuilder()).toString();
470            }
471    
472            @NotNull
473            private StringBuilder typeDescriptor(@NotNull JavaProtoBuf.JavaType type, @NotNull StringBuilder sb) {
474                for (int i = 0; i < type.getArrayDimension(); i++) {
475                    sb.append('[');
476                }
477    
478                if (type.hasPrimitiveType()) {
479                    sb.append(PRIMITIVE_TYPES[type.getPrimitiveType().ordinal()]);
480                }
481                else {
482                    sb.append("L");
483                    sb.append(fqNameToInternalName(nameResolver.getFqName(type.getClassFqName())));
484                    sb.append(";");
485                }
486    
487                return sb;
488            }
489    
490            @NotNull
491            private static String fqNameToInternalName(@NotNull FqName fqName) {
492                return fqName.asString().replace('.', '/');
493            }
494        }
495    
496        @NotNull
497        @Override
498        public List<AnnotationDescriptor> loadValueParameterAnnotations(
499                @NotNull ClassOrNamespaceDescriptor container,
500                @NotNull ProtoBuf.Callable callable,
501                @NotNull NameResolver nameResolver,
502                @NotNull AnnotatedCallableKind kind,
503                @NotNull ProtoBuf.Callable.ValueParameter proto
504        ) {
505            MemberSignature methodSignature = getCallableSignature(callable, nameResolver, kind);
506            if (methodSignature != null) {
507                if (proto.hasExtension(JavaProtoBuf.index)) {
508                    MemberSignature paramSignature =
509                            MemberSignature.fromMethodSignatureAndParameterIndex(methodSignature, proto.getExtension(JavaProtoBuf.index));
510                    return findClassAndLoadMemberAnnotations(container, callable, nameResolver, kind, paramSignature);
511                }
512            }
513    
514            return Collections.emptyList();
515        }
516    }