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