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 com.intellij.openapi.application.ApplicationManager;
020    import com.intellij.openapi.diagnostic.Logger;
021    import com.intellij.openapi.vfs.VirtualFile;
022    import org.jetbrains.annotations.NotNull;
023    import org.jetbrains.annotations.Nullable;
024    import org.jetbrains.jet.descriptors.serialization.ClassId;
025    import org.jetbrains.jet.lang.descriptors.*;
026    import org.jetbrains.jet.lang.resolve.DescriptorResolver;
027    import org.jetbrains.jet.lang.resolve.DescriptorUtils;
028    import org.jetbrains.jet.lang.resolve.java.DescriptorSearchRule;
029    import org.jetbrains.jet.lang.resolve.java.JavaClassFinder;
030    import org.jetbrains.jet.lang.resolve.java.JvmAbi;
031    import org.jetbrains.jet.lang.resolve.java.JvmAnnotationNames;
032    import org.jetbrains.jet.lang.resolve.java.descriptor.ClassDescriptorFromJvmBytecode;
033    import org.jetbrains.jet.lang.resolve.java.sam.SingleAbstractMethodUtils;
034    import org.jetbrains.jet.lang.resolve.java.scope.JavaClassNonStaticMembersScope;
035    import org.jetbrains.jet.lang.resolve.java.structure.JavaClass;
036    import org.jetbrains.jet.lang.resolve.java.structure.JavaMethod;
037    import org.jetbrains.jet.lang.resolve.java.vfilefinder.VirtualFileFinder;
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.DescriptorUtils.getClassObjectName;
054    import static org.jetbrains.jet.lang.resolve.java.DescriptorSearchRule.INCLUDE_KOTLIN_SOURCES;
055    
056    public final class JavaClassResolver {
057        private static final Logger LOG = Logger.getInstance(JavaClassResolver.class);
058    
059        @NotNull
060        private final Map<FqNameUnsafe, ClassDescriptor> classDescriptorCache = new HashMap<FqNameUnsafe, ClassDescriptor>();
061    
062        @NotNull
063        private final Set<FqNameUnsafe> unresolvedCache = new HashSet<FqNameUnsafe>();
064    
065        private JavaResolverCache cache;
066        private JavaTypeParameterResolver typeParameterResolver;
067        private JavaMemberResolver memberResolver;
068        private JavaAnnotationResolver annotationResolver;
069        private JavaClassFinder javaClassFinder;
070        private JavaNamespaceResolver namespaceResolver;
071        private JavaSupertypeResolver supertypesResolver;
072        private JavaFunctionResolver functionResolver;
073        private DeserializedDescriptorResolver deserializedDescriptorResolver;
074        private VirtualFileFinder virtualFileFinder;
075    
076        public JavaClassResolver() {
077        }
078    
079        @Inject
080        public void setVirtualFileFinder(VirtualFileFinder virtualFileFinder) {
081            this.virtualFileFinder = virtualFileFinder;
082        }
083    
084        @Inject
085        public void setCache(JavaResolverCache cache) {
086            this.cache = cache;
087        }
088    
089        @Inject
090        public void setDeserializedDescriptorResolver(DeserializedDescriptorResolver deserializedDescriptorResolver) {
091            this.deserializedDescriptorResolver = deserializedDescriptorResolver;
092        }
093    
094        @Inject
095        public void setTypeParameterResolver(JavaTypeParameterResolver typeParameterResolver) {
096            this.typeParameterResolver = typeParameterResolver;
097        }
098    
099        @Inject
100        public void setMemberResolver(JavaMemberResolver memberResolver) {
101            this.memberResolver = memberResolver;
102        }
103    
104        @Inject
105        public void setAnnotationResolver(JavaAnnotationResolver annotationResolver) {
106            this.annotationResolver = annotationResolver;
107        }
108    
109        @Inject
110        public void setJavaClassFinder(JavaClassFinder javaClassFinder) {
111            this.javaClassFinder = javaClassFinder;
112        }
113    
114        @Inject
115        public void setNamespaceResolver(JavaNamespaceResolver namespaceResolver) {
116            this.namespaceResolver = namespaceResolver;
117        }
118    
119        @Inject
120        public void setSupertypesResolver(JavaSupertypeResolver supertypesResolver) {
121            this.supertypesResolver = supertypesResolver;
122        }
123    
124        @Inject
125        public void setFunctionResolver(JavaFunctionResolver functionResolver) {
126            this.functionResolver = functionResolver;
127        }
128    
129        @Nullable
130        public ClassDescriptor resolveClass(@NotNull FqName qualifiedName, @NotNull DescriptorSearchRule searchRule) {
131            PostponedTasks postponedTasks = new PostponedTasks();
132            ClassDescriptor classDescriptor = resolveClass(qualifiedName, searchRule, postponedTasks);
133            postponedTasks.performTasks();
134            return classDescriptor;
135        }
136    
137        @Nullable
138        public ClassDescriptor resolveClass(
139                @NotNull FqName qualifiedName,
140                @NotNull DescriptorSearchRule searchRule,
141                @NotNull PostponedTasks tasks
142        ) {
143            if (isTraitImplementation(qualifiedName)) {
144                return null;
145            }
146    
147            ClassDescriptor builtinClassDescriptor = getKotlinBuiltinClassDescriptor(qualifiedName);
148            if (builtinClassDescriptor != null) {
149                return builtinClassDescriptor;
150            }
151    
152            if (searchRule == INCLUDE_KOTLIN_SOURCES) {
153                ClassDescriptor kotlinClassDescriptor = cache.getClassResolvedFromSource(qualifiedName);
154                if (kotlinClassDescriptor != null) {
155                    return kotlinClassDescriptor;
156                }
157            }
158    
159            FqNameUnsafe fqName = javaClassToKotlinFqName(qualifiedName);
160            ClassDescriptor cachedDescriptor = classDescriptorCache.get(fqName);
161            if (cachedDescriptor != null) {
162                return cachedDescriptor;
163            }
164    
165            if (unresolvedCache.contains(fqName)) {
166                return null;
167            }
168    
169            return doResolveClass(qualifiedName, tasks);
170        }
171    
172        @Nullable
173        private static ClassDescriptor getKotlinBuiltinClassDescriptor(@NotNull FqName qualifiedName) {
174            if (!qualifiedName.firstSegmentIs(KotlinBuiltIns.BUILT_INS_PACKAGE_NAME)) return null;
175    
176            List<Name> segments = qualifiedName.pathSegments();
177            if (segments.size() < 2) return null;
178    
179            JetScope scope = KotlinBuiltIns.getInstance().getBuiltInsScope();
180            for (int i = 1, size = segments.size(); i < size; i++) {
181                ClassifierDescriptor classifier = scope.getClassifier(segments.get(i));
182                if (classifier == null) return null;
183                assert classifier instanceof ClassDescriptor : "Unexpected classifier in built-ins: " + classifier;
184                scope = ((ClassDescriptor) classifier).getUnsubstitutedInnerClassesScope();
185            }
186    
187            return (ClassDescriptor) scope.getContainingDeclaration();
188        }
189    
190        private ClassDescriptor doResolveClass(@NotNull FqName qualifiedName, @NotNull PostponedTasks tasks) {
191            //TODO: correct scope
192            VirtualFile file = virtualFileFinder.find(qualifiedName);
193            if (file != null) {
194                //TODO: code duplication
195                //TODO: it is a hackish way to determine whether it is inner class or not
196                boolean isInnerClass = file.getName().contains("$");
197                ClassOrNamespaceDescriptor containingDeclaration = resolveParentDescriptor(qualifiedName, isInnerClass);
198                // class may be resolved during resolution of parent
199                ClassDescriptor cachedDescriptor = classDescriptorCache.get(javaClassToKotlinFqName(qualifiedName));
200                if (cachedDescriptor != null) {
201                    return cachedDescriptor;
202                }
203                assert !unresolvedCache.contains(qualifiedName.toUnsafe())
204                        : "We can resolve the class, so it can't be 'unresolved' during parent resolution";
205    
206                ClassId id = ClassId.fromFqNameAndContainingDeclaration(qualifiedName.toUnsafe(), containingDeclaration);
207                ClassDescriptor deserializedDescriptor = deserializedDescriptorResolver.resolveClass(id, file);
208                if (deserializedDescriptor != null) {
209                    cache(javaClassToKotlinFqName(qualifiedName), deserializedDescriptor);
210                    return deserializedDescriptor;
211                }
212            }
213    
214            JavaClass javaClass = javaClassFinder.findClass(qualifiedName);
215            if (javaClass == null) {
216                cacheNegativeValue(javaClassToKotlinFqName(qualifiedName));
217                return null;
218            }
219    
220            if (KotlinBuiltIns.BUILT_INS_PACKAGE_FQ_NAME.equals(qualifiedName.parent())) {
221                if (javaClass.findAnnotation(JvmAnnotationNames.ASSERT_INVISIBLE_IN_RESOLVER.getFqName()) != null) {
222                    if (ApplicationManager.getApplication().isInternal()) {
223                        LOG.error("classpath is configured incorrectly:" +
224                                  " class " + qualifiedName + " from runtime must not be loaded by compiler");
225                    }
226                    return null;
227                }
228            }
229    
230            // Class may have been resolved previously by different Java resolver instance, and we are reusing its trace
231            ClassDescriptor alreadyResolved = cache.getClass(javaClass);
232            if (alreadyResolved != null) {
233                return alreadyResolved;
234            }
235    
236            //TODO: code duplication
237            ClassOrNamespaceDescriptor containingDeclaration = resolveParentDescriptor(qualifiedName, javaClass.getOuterClass() != null);
238            // class may be resolved during resolution of parent
239            ClassDescriptor cachedDescriptor = classDescriptorCache.get(javaClassToKotlinFqName(qualifiedName));
240            if (cachedDescriptor != null) {
241                return cachedDescriptor;
242            }
243            assert !unresolvedCache.contains(qualifiedName.toUnsafe())
244                    : "We can resolve the class, so it can't be 'unresolved' during parent resolution";
245    
246            checkFqNamesAreConsistent(javaClass, qualifiedName);
247    
248            assert javaClass.getOriginKind() != JavaClass.OriginKind.KOTLIN_LIGHT_CLASS :
249                    "Trying to resolve a light class as a regular PsiClass: " + javaClass.getFqName();
250    
251            return doCreateClassDescriptor(qualifiedName, javaClass, tasks, containingDeclaration);
252        }
253    
254        private void cacheNegativeValue(@NotNull FqNameUnsafe fqNameUnsafe) {
255            if (unresolvedCache.contains(fqNameUnsafe) || classDescriptorCache.containsKey(fqNameUnsafe)) {
256                throw new IllegalStateException("rewrite at " + fqNameUnsafe);
257            }
258            unresolvedCache.add(fqNameUnsafe);
259        }
260    
261        private static boolean isTraitImplementation(@NotNull FqName qualifiedName) {
262            // TODO: only if -$$TImpl class is created by Kotlin
263            return qualifiedName.asString().endsWith(JvmAbi.TRAIT_IMPL_SUFFIX);
264        }
265    
266        @NotNull
267        private ClassDescriptorFromJvmBytecode doCreateClassDescriptor(
268                @NotNull FqName fqName,
269                @NotNull JavaClass javaClass,
270                @NotNull PostponedTasks taskList,
271                @NotNull ClassOrNamespaceDescriptor containingDeclaration
272        ) {
273            ClassDescriptorFromJvmBytecode classDescriptor =
274                    new ClassDescriptorFromJvmBytecode(containingDeclaration, determineClassKind(javaClass), isInnerClass(javaClass));
275    
276            cache(javaClassToKotlinFqName(fqName), classDescriptor);
277    
278            classDescriptor.setName(javaClass.getName());
279    
280            JavaTypeParameterResolver.Initializer typeParameterInitializer = typeParameterResolver.resolveTypeParameters(classDescriptor, javaClass);
281            classDescriptor.setTypeParameterDescriptors(typeParameterInitializer.getDescriptors());
282    
283            List<JetType> supertypes = new ArrayList<JetType>();
284            classDescriptor.setSupertypes(supertypes);
285            classDescriptor.setVisibility(javaClass.getVisibility());
286            classDescriptor.setModality(determineClassModality(javaClass));
287            classDescriptor.createTypeConstructor();
288    
289            JavaClassNonStaticMembersScope scope = new JavaClassNonStaticMembersScope(classDescriptor, javaClass, false, memberResolver);
290            classDescriptor.setScopeForMemberLookup(scope);
291            classDescriptor.setScopeForConstructorResolve(scope);
292    
293            typeParameterInitializer.initialize();
294    
295            // TODO: ugly hack: tests crash if initializeTypeParameters called with class containing proper supertypes
296            List<TypeParameterDescriptor> classTypeParameters = classDescriptor.getTypeConstructor().getParameters();
297            supertypes.addAll(supertypesResolver.getSupertypes(classDescriptor, javaClass, classTypeParameters));
298    
299            if (javaClass.isEnum()) {
300                ClassDescriptorFromJvmBytecode classObjectDescriptor = createClassObjectDescriptorForEnum(classDescriptor, javaClass);
301                cache(getFqNameForClassObject(javaClass), classObjectDescriptor);
302                classDescriptor.getBuilder().setClassObjectDescriptor(classObjectDescriptor);
303            }
304    
305            classDescriptor.setAnnotations(annotationResolver.resolveAnnotations(javaClass, taskList));
306    
307            cache.recordClass(javaClass, classDescriptor);
308    
309            JavaMethod samInterfaceMethod = SingleAbstractMethodUtils.getSamInterfaceMethod(javaClass);
310            if (samInterfaceMethod != null) {
311                SimpleFunctionDescriptor abstractMethod = resolveFunctionOfSamInterface(samInterfaceMethod, classDescriptor);
312                classDescriptor.setFunctionTypeForSamInterface(SingleAbstractMethodUtils.getFunctionTypeForAbstractMethod(abstractMethod));
313            }
314    
315            return classDescriptor;
316        }
317    
318        @NotNull
319        private static ClassKind determineClassKind(@NotNull JavaClass klass) {
320            if (klass.isInterface()) {
321                return klass.isAnnotationType() ? ClassKind.ANNOTATION_CLASS : ClassKind.TRAIT;
322            }
323            return klass.isEnum() ? ClassKind.ENUM_CLASS : ClassKind.CLASS;
324        }
325    
326        @NotNull
327        private static Modality determineClassModality(@NotNull JavaClass klass) {
328            return klass.isAnnotationType()
329                   ? Modality.FINAL
330                   : Modality.convertFromFlags(klass.isAbstract() || klass.isInterface(), !klass.isFinal());
331        }
332    
333        @NotNull
334        private static FqNameUnsafe getFqNameForClassObject(@NotNull JavaClass javaClass) {
335            FqName fqName = javaClass.getFqName();
336            assert fqName != null : "Reading java class with no qualified name";
337            return fqName.toUnsafe().child(getClassObjectName(javaClass.getName()));
338        }
339    
340        @NotNull
341        private SimpleFunctionDescriptor resolveFunctionOfSamInterface(
342                @NotNull JavaMethod samInterfaceMethod,
343                @NotNull ClassDescriptorFromJvmBytecode samInterface
344        ) {
345            JavaClass methodContainer = samInterfaceMethod.getContainingClass();
346            FqName containerFqName = methodContainer.getFqName();
347            assert containerFqName != null : "qualified name is null for " + methodContainer;
348    
349            if (DescriptorUtils.getFQName(samInterface).equalsTo(containerFqName)) {
350                SimpleFunctionDescriptor abstractMethod = functionResolver.resolveFunctionMutely(samInterfaceMethod, samInterface);
351                assert abstractMethod != null : "couldn't resolve method " + samInterfaceMethod;
352                return abstractMethod;
353            }
354            else {
355                return findFunctionWithMostSpecificReturnType(TypeUtils.getAllSupertypes(samInterface.getDefaultType()));
356            }
357        }
358    
359        @NotNull
360        private static SimpleFunctionDescriptor findFunctionWithMostSpecificReturnType(@NotNull Set<JetType> supertypes) {
361            List<SimpleFunctionDescriptor> candidates = new ArrayList<SimpleFunctionDescriptor>(supertypes.size());
362            for (JetType supertype : supertypes) {
363                List<CallableMemberDescriptor> abstractMembers = SingleAbstractMethodUtils.getAbstractMembers(supertype);
364                if (!abstractMembers.isEmpty()) {
365                    candidates.add((SimpleFunctionDescriptor) abstractMembers.get(0));
366                }
367            }
368            if (candidates.isEmpty()) {
369                throw new IllegalStateException("Couldn't find abstract method in supertypes " + supertypes);
370            }
371            SimpleFunctionDescriptor currentMostSpecificType = candidates.get(0);
372            for (SimpleFunctionDescriptor candidate : candidates) {
373                JetType candidateReturnType = candidate.getReturnType();
374                JetType currentMostSpecificReturnType = currentMostSpecificType.getReturnType();
375                assert candidateReturnType != null && currentMostSpecificReturnType != null : candidate + ", " + currentMostSpecificReturnType;
376                if (JetTypeChecker.INSTANCE.isSubtypeOf(candidateReturnType, currentMostSpecificReturnType)) {
377                    currentMostSpecificType = candidate;
378                }
379            }
380            return currentMostSpecificType;
381        }
382    
383        private void cache(@NotNull FqNameUnsafe fqName, @Nullable ClassDescriptor classDescriptor) {
384            if (classDescriptor == null) {
385                cacheNegativeValue(fqName);
386            }
387            else {
388                ClassDescriptor oldValue = classDescriptorCache.put(fqName, classDescriptor);
389                assert oldValue == null;
390            }
391        }
392    
393        private void checkFqNamesAreConsistent(@NotNull JavaClass javaClass, @NotNull FqName desiredFqName) {
394            FqName fqName = javaClass.getFqName();
395            assert desiredFqName.equals(fqName) : "Inconsistent FQ names: " + fqName + ", " + desiredFqName;
396            FqNameUnsafe correctedName = javaClassToKotlinFqName(fqName);
397            if (classDescriptorCache.containsKey(correctedName) || unresolvedCache.contains(correctedName)) {
398                throw new IllegalStateException("Cache already contains FQ name: " + fqName.asString());
399            }
400        }
401    
402        @NotNull
403        private ClassOrNamespaceDescriptor resolveParentDescriptor(@NotNull FqName childClassFQName, boolean isInnerClass) {
404            FqName parentFqName = childClassFQName.parent();
405            if (isInnerClass) {
406                ClassDescriptor parentClass = resolveClass(parentFqName, INCLUDE_KOTLIN_SOURCES);
407                if (parentClass == null) {
408                    throw new IllegalStateException("Could not resolve " + parentFqName + " required to be parent for " + childClassFQName);
409                }
410                return parentClass;
411            }
412            else {
413                NamespaceDescriptor parentNamespace = namespaceResolver.resolveNamespace(parentFqName, INCLUDE_KOTLIN_SOURCES);
414                if (parentNamespace == null) {
415                    throw new IllegalStateException("Could not resolve " + parentFqName + " required to be parent for " + childClassFQName);
416                }
417                return parentNamespace;
418            }
419        }
420    
421        // This method replaces "object" segments of FQ name to "<class-object-for-...>"
422        @NotNull
423        private static FqNameUnsafe javaClassToKotlinFqName(@NotNull FqName rawFqName) {
424            List<Name> correctedSegments = new ArrayList<Name>();
425            for (Name segment : rawFqName.pathSegments()) {
426                if (JvmAbi.CLASS_OBJECT_CLASS_NAME.equals(segment.asString())) {
427                    assert !correctedSegments.isEmpty();
428                    Name previous = correctedSegments.get(correctedSegments.size() - 1);
429                    correctedSegments.add(DescriptorUtils.getClassObjectName(previous));
430                }
431                else {
432                    correctedSegments.add(segment);
433                }
434            }
435            return FqNameUnsafe.fromSegments(correctedSegments);
436        }
437    
438        private static boolean isInnerClass(@NotNull JavaClass javaClass) {
439            return javaClass.getOuterClass() != null && !javaClass.isStatic();
440        }
441    
442        @NotNull
443        private ClassDescriptorFromJvmBytecode createClassObjectDescriptorForEnum(
444                @NotNull ClassDescriptor containing,
445                @NotNull JavaClass javaClass
446        ) {
447            ClassDescriptorFromJvmBytecode classObjectDescriptor = createSyntheticClassObject(containing, javaClass);
448    
449            JetType valuesReturnType = KotlinBuiltIns.getInstance().getArrayType(containing.getDefaultType());
450            SimpleFunctionDescriptor valuesMethod =
451                    DescriptorResolver.createEnumClassObjectValuesMethod(classObjectDescriptor, valuesReturnType);
452            classObjectDescriptor.getBuilder().addFunctionDescriptor(valuesMethod);
453    
454            JetType valueOfReturnType = containing.getDefaultType();
455            SimpleFunctionDescriptor valueOfMethod =
456                    DescriptorResolver.createEnumClassObjectValueOfMethod(classObjectDescriptor, valueOfReturnType);
457            classObjectDescriptor.getBuilder().addFunctionDescriptor(valueOfMethod);
458    
459            return classObjectDescriptor;
460        }
461    
462        @NotNull
463        private ClassDescriptorFromJvmBytecode createSyntheticClassObject(@NotNull ClassDescriptor containing, @NotNull JavaClass javaClass) {
464            ClassDescriptorFromJvmBytecode classObjectDescriptor =
465                    new ClassDescriptorFromJvmBytecode(containing, ClassKind.CLASS_OBJECT, false);
466    
467            classObjectDescriptor.setName(getClassObjectName(containing.getName()));
468            classObjectDescriptor.setModality(Modality.FINAL);
469            classObjectDescriptor.setVisibility(containing.getVisibility());
470            classObjectDescriptor.setTypeParameterDescriptors(Collections.<TypeParameterDescriptor>emptyList());
471            classObjectDescriptor.createTypeConstructor();
472    
473            JavaClassNonStaticMembersScope scope = new JavaClassNonStaticMembersScope(classObjectDescriptor, javaClass, true, memberResolver);
474            WritableScopeImpl writableScope =
475                    new WritableScopeImpl(scope, classObjectDescriptor, RedeclarationHandler.THROW_EXCEPTION, "Member lookup scope");
476            writableScope.changeLockLevel(WritableScope.LockLevel.BOTH);
477            classObjectDescriptor.setScopeForMemberLookup(writableScope);
478            classObjectDescriptor.setScopeForConstructorResolve(scope);
479    
480            return classObjectDescriptor;
481        }
482    }