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                        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 ErrorValue.create("Unresolved enum entry: " + enumClassName.getInternalName() + "." + name);
200                }
201    
202                @Override
203                public void visitEnd() {
204                    result.add(annotation);
205                }
206    
207                private void setArgumentValueByName(@NotNull Name name, @NotNull CompileTimeConstant<?> argumentValue) {
208                    ValueParameterDescriptor parameter = DescriptorResolverUtils.getAnnotationParameterByName(name, annotationClass);
209                    if (parameter != null) {
210                        annotation.setValueArgument(parameter, argumentValue);
211                    }
212                }
213            };
214        }
215    
216        @NotNull
217        private ClassDescriptor resolveClass(@NotNull JvmClassName className) {
218            ClassDescriptor annotationClass = javaClassResolver.resolveClass(className.getFqNameForClassNameWithoutDollars(),
219                                                                             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            return findClassAndLoadMemberAnnotations(container, proto, nameResolver, kind, signature);
235        }
236    
237        @NotNull
238        private List<AnnotationDescriptor> findClassAndLoadMemberAnnotations(
239                @NotNull ClassOrNamespaceDescriptor container,
240                @NotNull ProtoBuf.Callable proto,
241                @NotNull NameResolver nameResolver,
242                @NotNull AnnotatedCallableKind kind,
243                @NotNull MemberSignature signature
244        ) {
245            KotlinJvmBinaryClass kotlinClass = findClassWithMemberAnnotations(container, proto, nameResolver, kind);
246            if (kotlinClass == null) {
247                errorReporter.reportAnnotationLoadingError("Kotlin class for loading member annotations is not found: " + container, null);
248                return Collections.emptyList();
249            }
250    
251            List<AnnotationDescriptor> annotations = memberAnnotations.invoke(kotlinClass).get(signature);
252            return annotations == null ? Collections.<AnnotationDescriptor>emptyList() : annotations;
253        }
254    
255        @Nullable
256        private KotlinJvmBinaryClass findClassWithMemberAnnotations(
257                @NotNull ClassOrNamespaceDescriptor container,
258                @NotNull ProtoBuf.Callable proto,
259                @NotNull NameResolver nameResolver,
260                @NotNull AnnotatedCallableKind kind
261        ) {
262            if (container instanceof NamespaceDescriptor) {
263                return loadPackageFragmentClassFqName((NamespaceDescriptor) container, proto, nameResolver);
264            }
265            else if (isClassObject(container) && isStaticFieldInOuter(proto)) {
266                // Backing fields of properties of a class object are generated in the outer class
267                return findKotlinClassByDescriptor((ClassOrNamespaceDescriptor) container.getContainingDeclaration());
268            }
269            else if (isTrait(container) && kind == AnnotatedCallableKind.PROPERTY) {
270                NamespaceDescriptor containingPackage = DescriptorUtils.getParentOfType(container, NamespaceDescriptor.class);
271                assert containingPackage != null : "Trait must have a namespace among his parents: " + container;
272    
273                if (proto.hasExtension(JavaProtoBuf.implClassName)) {
274                    Name tImplName = nameResolver.getName(proto.getExtension(JavaProtoBuf.implClassName));
275                    return kotlinClassFinder.find(containingPackage.getFqName().child(tImplName));
276                }
277                return null;
278            }
279    
280            return findKotlinClassByDescriptor(container);
281        }
282    
283        @Nullable
284        private KotlinJvmBinaryClass loadPackageFragmentClassFqName(
285                @NotNull NamespaceDescriptor container,
286                @NotNull ProtoBuf.Callable proto,
287                @NotNull NameResolver nameResolver
288        ) {
289            if (proto.hasExtension(JavaProtoBuf.implClassName)) {
290                Name name = nameResolver.getName(proto.getExtension(JavaProtoBuf.implClassName));
291                FqName fqName = PackageClassUtils.getPackageClassFqName(container.getFqName()).parent().child(name);
292                return kotlinClassFinder.find(fqName);
293            }
294            return null;
295        }
296    
297        private static boolean isStaticFieldInOuter(@NotNull ProtoBuf.Callable proto) {
298            if (!proto.hasExtension(JavaProtoBuf.propertySignature)) return false;
299            JavaProtoBuf.JavaPropertySignature propertySignature = proto.getExtension(JavaProtoBuf.propertySignature);
300            return propertySignature.hasField() && propertySignature.getField().getIsStaticInOuter();
301        }
302    
303        @Nullable
304        private static MemberSignature getCallableSignature(
305                @NotNull ProtoBuf.Callable proto,
306                @NotNull NameResolver nameResolver,
307                @NotNull AnnotatedCallableKind kind
308        ) {
309            SignatureDeserializer deserializer = new SignatureDeserializer(nameResolver);
310            switch (kind) {
311                case FUNCTION:
312                    if (proto.hasExtension(JavaProtoBuf.methodSignature)) {
313                        return deserializer.methodSignature(proto.getExtension(JavaProtoBuf.methodSignature));
314                    }
315                    break;
316                case PROPERTY_GETTER:
317                    if (proto.hasExtension(JavaProtoBuf.propertySignature)) {
318                        return deserializer.methodSignature(proto.getExtension(JavaProtoBuf.propertySignature).getGetter());
319                    }
320                    break;
321                case PROPERTY_SETTER:
322                    if (proto.hasExtension(JavaProtoBuf.propertySignature)) {
323                        return deserializer.methodSignature(proto.getExtension(JavaProtoBuf.propertySignature).getSetter());
324                    }
325                    break;
326                case PROPERTY:
327                    if (proto.hasExtension(JavaProtoBuf.propertySignature)) {
328                        JavaProtoBuf.JavaPropertySignature propertySignature = proto.getExtension(JavaProtoBuf.propertySignature);
329    
330                        if (propertySignature.hasField()) {
331                            JavaProtoBuf.JavaFieldSignature field = propertySignature.getField();
332                            String type = deserializer.typeDescriptor(field.getType());
333                            Name name = nameResolver.getName(field.getName());
334                            return MemberSignature.fromFieldNameAndDesc(name, type);
335                        }
336                        else if (propertySignature.hasSyntheticMethod()) {
337                            return deserializer.methodSignature(propertySignature.getSyntheticMethod());
338                        }
339                    }
340                    break;
341            }
342            return null;
343        }
344    
345        @NotNull
346        private Map<MemberSignature, List<AnnotationDescriptor>> loadMemberAnnotationsFromClass(@NotNull KotlinJvmBinaryClass kotlinClass)
347                throws IOException {
348            final Map<MemberSignature, List<AnnotationDescriptor>> memberAnnotations =
349                    new HashMap<MemberSignature, List<AnnotationDescriptor>>();
350    
351            kotlinClass.loadMemberAnnotations(new KotlinJvmBinaryClass.MemberVisitor() {
352                @Nullable
353                @Override
354                public KotlinJvmBinaryClass.MethodAnnotationVisitor visitMethod(@NotNull Name name, @NotNull String desc) {
355                    return new AnnotationVisitorForMethod(MemberSignature.fromMethodNameAndDesc(name, desc));
356                }
357    
358                @Nullable
359                @Override
360                public KotlinJvmBinaryClass.AnnotationVisitor visitField(@NotNull Name name, @NotNull String desc) {
361                    return new MemberAnnotationVisitor(MemberSignature.fromFieldNameAndDesc(name, desc));
362                }
363    
364                class AnnotationVisitorForMethod extends MemberAnnotationVisitor implements KotlinJvmBinaryClass.MethodAnnotationVisitor {
365                    public AnnotationVisitorForMethod(@NotNull MemberSignature signature) {
366                        super(signature);
367                    }
368    
369                    @Nullable
370                    @Override
371                    public KotlinJvmBinaryClass.AnnotationArgumentVisitor visitParameterAnnotation(int index, @NotNull JvmClassName className) {
372                        MemberSignature paramSignature = MemberSignature.fromMethodSignatureAndParameterIndex(signature, index);
373                        List<AnnotationDescriptor> result = memberAnnotations.get(paramSignature);
374                        if (result == null) {
375                            result = new ArrayList<AnnotationDescriptor>();
376                            memberAnnotations.put(paramSignature, result);
377                        }
378                        return resolveAnnotation(className, result);
379                    }
380                }
381    
382                class MemberAnnotationVisitor implements KotlinJvmBinaryClass.AnnotationVisitor {
383                    private final List<AnnotationDescriptor> result = new ArrayList<AnnotationDescriptor>();
384                    protected final MemberSignature signature;
385    
386                    public MemberAnnotationVisitor(@NotNull MemberSignature signature) {
387                        this.signature = signature;
388                    }
389    
390                    @Nullable
391                    @Override
392                    public KotlinJvmBinaryClass.AnnotationArgumentVisitor visitAnnotation(@NotNull JvmClassName className) {
393                        return resolveAnnotation(className, result);
394                    }
395    
396                    @Override
397                    public void visitEnd() {
398                        if (!result.isEmpty()) {
399                            memberAnnotations.put(signature, result);
400                        }
401                    }
402                }
403            });
404    
405            return memberAnnotations;
406        }
407    
408        // 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
409        // into a map indexed by these signatures
410        private static final class MemberSignature {
411            private final String signature;
412    
413            private MemberSignature(@NotNull String signature) {
414                this.signature = signature;
415            }
416    
417            @NotNull
418            public static MemberSignature fromMethodNameAndDesc(@NotNull Name name, @NotNull String desc) {
419                return new MemberSignature(name.asString() + desc);
420            }
421    
422            @NotNull
423            public static MemberSignature fromFieldNameAndDesc(@NotNull Name name, @NotNull String desc) {
424                return new MemberSignature(name.asString() + "#" + desc);
425            }
426    
427            @NotNull
428            public static MemberSignature fromMethodSignatureAndParameterIndex(@NotNull MemberSignature signature, int index) {
429                return new MemberSignature(signature.signature + "@" + index);
430            }
431    
432            @Override
433            public int hashCode() {
434                return signature.hashCode();
435            }
436    
437            @Override
438            public boolean equals(Object o) {
439                return o instanceof MemberSignature && signature.equals(((MemberSignature) o).signature);
440            }
441    
442            @Override
443            public String toString() {
444                return signature;
445            }
446        }
447    
448        private static class SignatureDeserializer {
449            // These types are ordered according to their sorts, this is significant for deserialization
450            private static final char[] PRIMITIVE_TYPES = new char[] { 'V', 'Z', 'C', 'B', 'S', 'I', 'F', 'J', 'D' };
451    
452            private final NameResolver nameResolver;
453    
454            public SignatureDeserializer(@NotNull NameResolver nameResolver) {
455                this.nameResolver = nameResolver;
456            }
457    
458            @NotNull
459            public MemberSignature methodSignature(@NotNull JavaProtoBuf.JavaMethodSignature signature) {
460                Name name = nameResolver.getName(signature.getName());
461    
462                StringBuilder sb = new StringBuilder();
463                sb.append('(');
464                for (int i = 0, length = signature.getParameterTypeCount(); i < length; i++) {
465                    typeDescriptor(signature.getParameterType(i), sb);
466                }
467                sb.append(')');
468                typeDescriptor(signature.getReturnType(), sb);
469    
470                return MemberSignature.fromMethodNameAndDesc(name, sb.toString());
471            }
472    
473            @NotNull
474            public String typeDescriptor(@NotNull JavaProtoBuf.JavaType type) {
475                return typeDescriptor(type, new StringBuilder()).toString();
476            }
477    
478            @NotNull
479            private StringBuilder typeDescriptor(@NotNull JavaProtoBuf.JavaType type, @NotNull StringBuilder sb) {
480                for (int i = 0; i < type.getArrayDimension(); i++) {
481                    sb.append('[');
482                }
483    
484                if (type.hasPrimitiveType()) {
485                    sb.append(PRIMITIVE_TYPES[type.getPrimitiveType().ordinal()]);
486                }
487                else {
488                    sb.append("L");
489                    sb.append(fqNameToInternalName(nameResolver.getFqName(type.getClassFqName())));
490                    sb.append(";");
491                }
492    
493                return sb;
494            }
495    
496            @NotNull
497            private static String fqNameToInternalName(@NotNull FqName fqName) {
498                return fqName.asString().replace('.', '/');
499            }
500        }
501    
502        @NotNull
503        @Override
504        public List<AnnotationDescriptor> loadValueParameterAnnotations(
505                @NotNull ClassOrNamespaceDescriptor container,
506                @NotNull ProtoBuf.Callable callable,
507                @NotNull NameResolver nameResolver,
508                @NotNull AnnotatedCallableKind kind,
509                @NotNull ProtoBuf.Callable.ValueParameter proto
510        ) {
511            MemberSignature methodSignature = getCallableSignature(callable, nameResolver, kind);
512            if (methodSignature != null) {
513                if (proto.hasExtension(JavaProtoBuf.index)) {
514                    MemberSignature paramSignature =
515                            MemberSignature.fromMethodSignatureAndParameterIndex(methodSignature, proto.getExtension(JavaProtoBuf.index));
516                    return findClassAndLoadMemberAnnotations(container, callable, nameResolver, kind, paramSignature);
517                }
518            }
519    
520            return Collections.emptyList();
521        }
522    }