001    /*
002     * Copyright 2010-2013 JetBrains s.r.o.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.jetbrains.jet.lang.resolve.java.resolver;
018    
019    import org.jetbrains.annotations.NotNull;
020    import org.jetbrains.annotations.Nullable;
021    import org.jetbrains.jet.lang.descriptors.*;
022    import org.jetbrains.jet.lang.resolve.DescriptorFactory;
023    import org.jetbrains.jet.lang.resolve.DescriptorUtils;
024    import org.jetbrains.jet.lang.resolve.java.DescriptorSearchRule;
025    import org.jetbrains.jet.lang.resolve.java.JavaClassFinder;
026    import org.jetbrains.jet.lang.resolve.java.JvmAbi;
027    import org.jetbrains.jet.lang.resolve.java.descriptor.ClassDescriptorFromJvmBytecode;
028    import org.jetbrains.jet.lang.resolve.java.descriptor.JavaClassDescriptor;
029    import org.jetbrains.jet.lang.resolve.java.descriptor.JavaEnumClassObjectDescriptor;
030    import org.jetbrains.jet.lang.resolve.java.sam.SingleAbstractMethodUtils;
031    import org.jetbrains.jet.lang.resolve.java.scope.JavaClassNonStaticMembersScope;
032    import org.jetbrains.jet.lang.resolve.java.scope.JavaEnumClassObjectScope;
033    import org.jetbrains.jet.lang.resolve.java.structure.JavaClass;
034    import org.jetbrains.jet.lang.resolve.java.structure.JavaMethod;
035    import org.jetbrains.jet.lang.resolve.kotlin.DeserializedDescriptorResolver;
036    import org.jetbrains.jet.lang.resolve.kotlin.KotlinClassFinder;
037    import org.jetbrains.jet.lang.resolve.kotlin.KotlinJvmBinaryClass;
038    import org.jetbrains.jet.lang.resolve.name.FqName;
039    import org.jetbrains.jet.lang.resolve.name.FqNameUnsafe;
040    import org.jetbrains.jet.lang.resolve.name.Name;
041    import org.jetbrains.jet.lang.resolve.scopes.JetScope;
042    import org.jetbrains.jet.lang.resolve.scopes.RedeclarationHandler;
043    import org.jetbrains.jet.lang.resolve.scopes.WritableScope;
044    import org.jetbrains.jet.lang.resolve.scopes.WritableScopeImpl;
045    import org.jetbrains.jet.lang.types.JetType;
046    import org.jetbrains.jet.lang.types.TypeUtils;
047    import org.jetbrains.jet.lang.types.checker.JetTypeChecker;
048    import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
049    
050    import javax.inject.Inject;
051    import java.util.*;
052    
053    import static org.jetbrains.jet.lang.resolve.java.DescriptorSearchRule.INCLUDE_KOTLIN_SOURCES;
054    import static org.jetbrains.jet.lang.resolve.name.SpecialNames.getClassObjectName;
055    
056    public final class JavaClassResolver {
057        @NotNull
058        private final Map<FqNameUnsafe, ClassDescriptor> classDescriptorCache = new HashMap<FqNameUnsafe, ClassDescriptor>();
059    
060        @NotNull
061        private final Set<FqNameUnsafe> unresolvedCache = new HashSet<FqNameUnsafe>();
062    
063        private JavaResolverCache cache;
064        private JavaTypeParameterResolver typeParameterResolver;
065        private JavaMemberResolver memberResolver;
066        private JavaAnnotationResolver annotationResolver;
067        private JavaClassFinder javaClassFinder;
068        private JavaNamespaceResolver namespaceResolver;
069        private JavaSupertypeResolver supertypesResolver;
070        private JavaFunctionResolver functionResolver;
071        private DeserializedDescriptorResolver deserializedDescriptorResolver;
072        private KotlinClassFinder kotlinClassFinder;
073    
074        public JavaClassResolver() {
075        }
076    
077        @Inject
078        public void setCache(JavaResolverCache cache) {
079            this.cache = cache;
080        }
081    
082        @Inject
083        public void setDeserializedDescriptorResolver(DeserializedDescriptorResolver deserializedDescriptorResolver) {
084            this.deserializedDescriptorResolver = deserializedDescriptorResolver;
085        }
086    
087        @Inject
088        public void setTypeParameterResolver(JavaTypeParameterResolver typeParameterResolver) {
089            this.typeParameterResolver = typeParameterResolver;
090        }
091    
092        @Inject
093        public void setMemberResolver(JavaMemberResolver memberResolver) {
094            this.memberResolver = memberResolver;
095        }
096    
097        @Inject
098        public void setAnnotationResolver(JavaAnnotationResolver annotationResolver) {
099            this.annotationResolver = annotationResolver;
100        }
101    
102        @Inject
103        public void setJavaClassFinder(JavaClassFinder javaClassFinder) {
104            this.javaClassFinder = javaClassFinder;
105        }
106    
107        @Inject
108        public void setNamespaceResolver(JavaNamespaceResolver namespaceResolver) {
109            this.namespaceResolver = namespaceResolver;
110        }
111    
112        @Inject
113        public void setSupertypesResolver(JavaSupertypeResolver supertypesResolver) {
114            this.supertypesResolver = supertypesResolver;
115        }
116    
117        @Inject
118        public void setFunctionResolver(JavaFunctionResolver functionResolver) {
119            this.functionResolver = functionResolver;
120        }
121    
122        @Inject
123        public void setKotlinClassFinder(KotlinClassFinder kotlinClassFinder) {
124            this.kotlinClassFinder = kotlinClassFinder;
125        }
126    
127        @Nullable
128        public ClassDescriptor resolveClass(@NotNull FqName qualifiedName, @NotNull DescriptorSearchRule searchRule) {
129            PostponedTasks postponedTasks = new PostponedTasks();
130            ClassDescriptor classDescriptor = resolveClass(qualifiedName, searchRule, postponedTasks);
131            postponedTasks.performTasks();
132            return classDescriptor;
133        }
134    
135        @Nullable
136        public ClassDescriptor resolveClass(
137                @NotNull FqName qualifiedName,
138                @NotNull DescriptorSearchRule searchRule,
139                @NotNull PostponedTasks tasks
140        ) {
141            if (isTraitImplementation(qualifiedName)) {
142                return null;
143            }
144    
145            ClassDescriptor builtinClassDescriptor = getKotlinBuiltinClassDescriptor(qualifiedName);
146            if (builtinClassDescriptor != null) {
147                return builtinClassDescriptor;
148            }
149    
150            if (searchRule == INCLUDE_KOTLIN_SOURCES) {
151                ClassDescriptor kotlinClassDescriptor = cache.getClassResolvedFromSource(qualifiedName);
152                if (kotlinClassDescriptor != null) {
153                    return kotlinClassDescriptor;
154                }
155            }
156    
157            FqNameUnsafe fqName = javaClassToKotlinFqName(qualifiedName);
158            ClassDescriptor cachedDescriptor = classDescriptorCache.get(fqName);
159            if (cachedDescriptor != null) {
160                return cachedDescriptor;
161            }
162    
163            if (unresolvedCache.contains(fqName)) {
164                return null;
165            }
166    
167            return doResolveClass(qualifiedName, tasks);
168        }
169    
170        @Nullable
171        private static ClassDescriptor getKotlinBuiltinClassDescriptor(@NotNull FqName qualifiedName) {
172            if (!qualifiedName.firstSegmentIs(KotlinBuiltIns.BUILT_INS_PACKAGE_NAME)) return null;
173    
174            List<Name> segments = qualifiedName.pathSegments();
175            if (segments.size() < 2) return null;
176    
177            JetScope scope = KotlinBuiltIns.getInstance().getBuiltInsScope();
178            for (int i = 1, size = segments.size(); i < size; i++) {
179                ClassifierDescriptor classifier = scope.getClassifier(segments.get(i));
180                if (classifier == null) return null;
181                assert classifier instanceof ClassDescriptor : "Unexpected classifier in built-ins: " + classifier;
182                scope = ((ClassDescriptor) classifier).getUnsubstitutedInnerClassesScope();
183            }
184    
185            return (ClassDescriptor) scope.getContainingDeclaration();
186        }
187    
188        private ClassDescriptor doResolveClass(@NotNull FqName qualifiedName, @NotNull PostponedTasks tasks) {
189            //TODO: correct scope
190            KotlinJvmBinaryClass kotlinClass = kotlinClassFinder.find(qualifiedName);
191            if (kotlinClass != null) {
192                ClassDescriptor deserializedDescriptor = deserializedDescriptorResolver.resolveClass(kotlinClass);
193                if (deserializedDescriptor != null) {
194                    cache(javaClassToKotlinFqName(qualifiedName), deserializedDescriptor);
195                    return deserializedDescriptor;
196                }
197            }
198    
199            JavaClass javaClass = javaClassFinder.findClass(qualifiedName);
200            if (javaClass == null) {
201                cacheNegativeValue(javaClassToKotlinFqName(qualifiedName));
202                return null;
203            }
204    
205            // Class may have been resolved previously by different Java resolver instance, and we are reusing its trace
206            ClassDescriptor alreadyResolved = cache.getClass(javaClass);
207            if (alreadyResolved != null) {
208                return alreadyResolved;
209            }
210    
211            ClassOrNamespaceDescriptor containingDeclaration = resolveParentDescriptor(qualifiedName, javaClass.getOuterClass());
212            // class may be resolved during resolution of parent
213            ClassDescriptor cachedDescriptor = classDescriptorCache.get(javaClassToKotlinFqName(qualifiedName));
214            if (cachedDescriptor != null) {
215                return cachedDescriptor;
216            }
217            assert !unresolvedCache.contains(qualifiedName.toUnsafe())
218                    : "We can resolve the class, so it can't be 'unresolved' during parent resolution";
219    
220            checkFqNamesAreConsistent(javaClass, qualifiedName);
221    
222            assert javaClass.getOriginKind() != JavaClass.OriginKind.KOTLIN_LIGHT_CLASS :
223                    "Trying to resolve a light class as a regular PsiClass: " + javaClass.getFqName();
224    
225            return doCreateClassDescriptor(qualifiedName, javaClass, tasks, containingDeclaration);
226        }
227    
228        private void cacheNegativeValue(@NotNull FqNameUnsafe fqNameUnsafe) {
229            if (unresolvedCache.contains(fqNameUnsafe) || classDescriptorCache.containsKey(fqNameUnsafe)) {
230                throw new IllegalStateException("rewrite at " + fqNameUnsafe);
231            }
232            unresolvedCache.add(fqNameUnsafe);
233        }
234    
235        private static boolean isTraitImplementation(@NotNull FqName qualifiedName) {
236            // TODO: only if -$$TImpl class is created by Kotlin
237            return qualifiedName.asString().endsWith(JvmAbi.TRAIT_IMPL_SUFFIX);
238        }
239    
240        @NotNull
241        private JavaClassDescriptor doCreateClassDescriptor(
242                @NotNull FqName fqName,
243                @NotNull JavaClass javaClass,
244                @NotNull PostponedTasks taskList,
245                @NotNull ClassOrNamespaceDescriptor containingDeclaration
246        ) {
247            ClassDescriptorFromJvmBytecode classDescriptor =
248                    new ClassDescriptorFromJvmBytecode(containingDeclaration, javaClass.getName(), determineClassKind(javaClass), isInnerClass(javaClass));
249    
250            cache(javaClassToKotlinFqName(fqName), classDescriptor);
251    
252    
253            JavaTypeParameterResolver.Initializer typeParameterInitializer = typeParameterResolver.resolveTypeParameters(classDescriptor, javaClass);
254            classDescriptor.setTypeParameterDescriptors(typeParameterInitializer.getDescriptors());
255    
256            List<JetType> supertypes = new ArrayList<JetType>();
257            classDescriptor.setSupertypes(supertypes);
258            classDescriptor.setVisibility(javaClass.getVisibility());
259            classDescriptor.setModality(determineClassModality(javaClass));
260            classDescriptor.createTypeConstructor();
261    
262            JavaClassNonStaticMembersScope scope = new JavaClassNonStaticMembersScope(classDescriptor, javaClass, memberResolver);
263            classDescriptor.setScopeForMemberLookup(scope);
264            classDescriptor.setScopeForConstructorResolve(scope);
265    
266            typeParameterInitializer.initialize();
267    
268            // TODO: ugly hack: tests crash if initializeTypeParameters called with class containing proper supertypes
269            List<TypeParameterDescriptor> classTypeParameters = classDescriptor.getTypeConstructor().getParameters();
270            supertypes.addAll(supertypesResolver.getSupertypes(classDescriptor, javaClass, classTypeParameters));
271    
272            if (javaClass.isEnum()) {
273                JavaEnumClassObjectDescriptor enumClassObject = createEnumClassObject(classDescriptor, javaClass);
274                createEnumSyntheticMethods(enumClassObject, classDescriptor.getDefaultType());
275                cache(getFqNameForClassObject(javaClass), enumClassObject);
276                classDescriptor.getBuilder().setClassObjectDescriptor(enumClassObject);
277            }
278    
279            classDescriptor.setAnnotations(annotationResolver.resolveAnnotations(javaClass, taskList));
280    
281            cache.recordClass(javaClass, classDescriptor);
282    
283            JavaMethod samInterfaceMethod = SingleAbstractMethodUtils.getSamInterfaceMethod(javaClass);
284            if (samInterfaceMethod != null) {
285                SimpleFunctionDescriptor abstractMethod = resolveFunctionOfSamInterface(samInterfaceMethod, classDescriptor);
286                classDescriptor.setFunctionTypeForSamInterface(SingleAbstractMethodUtils.getFunctionTypeForAbstractMethod(abstractMethod));
287            }
288    
289            return classDescriptor;
290        }
291    
292        @NotNull
293        private static ClassKind determineClassKind(@NotNull JavaClass klass) {
294            if (klass.isInterface()) {
295                return klass.isAnnotationType() ? ClassKind.ANNOTATION_CLASS : ClassKind.TRAIT;
296            }
297            return klass.isEnum() ? ClassKind.ENUM_CLASS : ClassKind.CLASS;
298        }
299    
300        @NotNull
301        private static Modality determineClassModality(@NotNull JavaClass klass) {
302            return klass.isAnnotationType()
303                   ? Modality.FINAL
304                   : Modality.convertFromFlags(klass.isAbstract() || klass.isInterface(), !klass.isFinal());
305        }
306    
307        @NotNull
308        private static FqNameUnsafe getFqNameForClassObject(@NotNull JavaClass javaClass) {
309            FqName fqName = javaClass.getFqName();
310            assert fqName != null : "Reading java class with no qualified name";
311            return fqName.toUnsafe().child(getClassObjectName(javaClass.getName()));
312        }
313    
314        @NotNull
315        private SimpleFunctionDescriptor resolveFunctionOfSamInterface(
316                @NotNull JavaMethod samInterfaceMethod,
317                @NotNull JavaClassDescriptor samInterface
318        ) {
319            JavaClass methodContainer = samInterfaceMethod.getContainingClass();
320            FqName containerFqName = methodContainer.getFqName();
321            assert containerFqName != null : "qualified name is null for " + methodContainer;
322    
323            if (DescriptorUtils.getFQName(samInterface).equalsTo(containerFqName)) {
324                SimpleFunctionDescriptor abstractMethod = functionResolver.resolveFunctionMutely(samInterfaceMethod, samInterface);
325                assert abstractMethod != null : "couldn't resolve method " + samInterfaceMethod;
326                return abstractMethod;
327            }
328            else {
329                return findFunctionWithMostSpecificReturnType(TypeUtils.getAllSupertypes(samInterface.getDefaultType()));
330            }
331        }
332    
333        @NotNull
334        private static SimpleFunctionDescriptor findFunctionWithMostSpecificReturnType(@NotNull Set<JetType> supertypes) {
335            List<SimpleFunctionDescriptor> candidates = new ArrayList<SimpleFunctionDescriptor>(supertypes.size());
336            for (JetType supertype : supertypes) {
337                List<CallableMemberDescriptor> abstractMembers = SingleAbstractMethodUtils.getAbstractMembers(supertype);
338                if (!abstractMembers.isEmpty()) {
339                    candidates.add((SimpleFunctionDescriptor) abstractMembers.get(0));
340                }
341            }
342            if (candidates.isEmpty()) {
343                throw new IllegalStateException("Couldn't find abstract method in supertypes " + supertypes);
344            }
345            SimpleFunctionDescriptor currentMostSpecificType = candidates.get(0);
346            for (SimpleFunctionDescriptor candidate : candidates) {
347                JetType candidateReturnType = candidate.getReturnType();
348                JetType currentMostSpecificReturnType = currentMostSpecificType.getReturnType();
349                assert candidateReturnType != null && currentMostSpecificReturnType != null : candidate + ", " + currentMostSpecificReturnType;
350                if (JetTypeChecker.INSTANCE.isSubtypeOf(candidateReturnType, currentMostSpecificReturnType)) {
351                    currentMostSpecificType = candidate;
352                }
353            }
354            return currentMostSpecificType;
355        }
356    
357        private void cache(@NotNull FqNameUnsafe fqName, @Nullable ClassDescriptor classDescriptor) {
358            if (classDescriptor == null) {
359                cacheNegativeValue(fqName);
360            }
361            else {
362                ClassDescriptor oldValue = classDescriptorCache.put(fqName, classDescriptor);
363                assert oldValue == null;
364            }
365        }
366    
367        private void checkFqNamesAreConsistent(@NotNull JavaClass javaClass, @NotNull FqName desiredFqName) {
368            FqName fqName = javaClass.getFqName();
369            assert desiredFqName.equals(fqName) : "Inconsistent FQ names: " + fqName + ", " + desiredFqName;
370            FqNameUnsafe correctedName = javaClassToKotlinFqName(fqName);
371            if (classDescriptorCache.containsKey(correctedName) || unresolvedCache.contains(correctedName)) {
372                throw new IllegalStateException("Cache already contains FQ name: " + fqName.asString());
373            }
374        }
375    
376        @NotNull
377        private ClassOrNamespaceDescriptor resolveParentDescriptor(@NotNull FqName childClassFQName, JavaClass parentClass) {
378            if (parentClass != null) {
379                FqName parentFqName = parentClass.getFqName();
380                ClassDescriptor parentClassDescriptor = resolveClass(parentFqName, INCLUDE_KOTLIN_SOURCES);
381                if (parentClassDescriptor == null) {
382                    throw new IllegalStateException("Could not resolve " + parentFqName + " required to be parent for " + childClassFQName);
383                }
384                return parentClassDescriptor;
385            }
386            else {
387                FqName parentFqName = childClassFQName.parent();
388                NamespaceDescriptor parentNamespace = namespaceResolver.resolveNamespace(parentFqName, INCLUDE_KOTLIN_SOURCES);
389                if (parentNamespace == null) {
390                    throw new IllegalStateException("Could not resolve " + parentFqName + " required to be parent for " + childClassFQName);
391                }
392                return parentNamespace;
393            }
394        }
395    
396        // This method replaces "object" segments of FQ name to "<class-object-for-...>"
397        @NotNull
398        private static FqNameUnsafe javaClassToKotlinFqName(@NotNull FqName rawFqName) {
399            List<Name> correctedSegments = new ArrayList<Name>();
400            for (Name segment : rawFqName.pathSegments()) {
401                if (JvmAbi.CLASS_OBJECT_CLASS_NAME.equals(segment.asString())) {
402                    assert !correctedSegments.isEmpty();
403                    Name previous = correctedSegments.get(correctedSegments.size() - 1);
404                    correctedSegments.add(getClassObjectName(previous));
405                }
406                else {
407                    correctedSegments.add(segment);
408                }
409            }
410            return FqNameUnsafe.fromSegments(correctedSegments);
411        }
412    
413        private static boolean isInnerClass(@NotNull JavaClass javaClass) {
414            return javaClass.getOuterClass() != null && !javaClass.isStatic();
415        }
416    
417        private static void createEnumSyntheticMethods(@NotNull JavaEnumClassObjectDescriptor classObject, @NotNull JetType enumType) {
418            JetType valuesReturnType = KotlinBuiltIns.getInstance().getArrayType(enumType);
419            SimpleFunctionDescriptor valuesMethod = DescriptorFactory.createEnumClassObjectValuesMethod(classObject, valuesReturnType);
420            classObject.getBuilder().addFunctionDescriptor(valuesMethod);
421    
422            SimpleFunctionDescriptor valueOfMethod = DescriptorFactory.createEnumClassObjectValueOfMethod(classObject, enumType);
423            classObject.getBuilder().addFunctionDescriptor(valueOfMethod);
424        }
425    
426        @NotNull
427        private JavaEnumClassObjectDescriptor createEnumClassObject(@NotNull ClassDescriptor enumClass, @NotNull JavaClass javaClass) {
428            JavaEnumClassObjectDescriptor classObject = new JavaEnumClassObjectDescriptor(enumClass);
429    
430            classObject.setModality(Modality.FINAL);
431            classObject.setVisibility(DescriptorUtils.getSyntheticClassObjectVisibility());
432            classObject.setTypeParameterDescriptors(Collections.<TypeParameterDescriptor>emptyList());
433            classObject.createTypeConstructor();
434    
435            JavaEnumClassObjectScope scope = new JavaEnumClassObjectScope(classObject, javaClass, memberResolver);
436            WritableScopeImpl writableScope =
437                    new WritableScopeImpl(scope, classObject, RedeclarationHandler.THROW_EXCEPTION, "Enum class object scope");
438            writableScope.changeLockLevel(WritableScope.LockLevel.BOTH);
439            classObject.setScopeForMemberLookup(writableScope);
440    
441            return classObject;
442        }
443    }