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