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
017package org.jetbrains.jet.lang.resolve.java.resolver;
018
019import com.google.common.collect.Lists;
020import com.google.common.collect.Sets;
021import com.intellij.openapi.util.text.StringUtil;
022import com.intellij.psi.PsiClass;
023import com.intellij.psi.PsiMethod;
024import com.intellij.psi.PsiModifier;
025import gnu.trove.THashMap;
026import gnu.trove.TObjectHashingStrategy;
027import org.jetbrains.annotations.NotNull;
028import org.jetbrains.annotations.Nullable;
029import org.jetbrains.jet.lang.descriptors.*;
030import org.jetbrains.jet.lang.resolve.BindingContext;
031import org.jetbrains.jet.lang.resolve.BindingTrace;
032import org.jetbrains.jet.lang.resolve.DescriptorUtils;
033import org.jetbrains.jet.lang.resolve.java.*;
034import org.jetbrains.jet.lang.resolve.java.descriptor.ClassDescriptorFromJvmBytecode;
035import org.jetbrains.jet.lang.resolve.java.kt.JetClassAnnotation;
036import org.jetbrains.jet.lang.resolve.java.provider.ClassPsiDeclarationProvider;
037import org.jetbrains.jet.lang.resolve.java.provider.MembersCache;
038import org.jetbrains.jet.lang.resolve.java.sam.SingleAbstractMethodUtils;
039import org.jetbrains.jet.lang.resolve.java.scope.JavaClassNonStaticMembersScope;
040import org.jetbrains.jet.lang.resolve.java.wrapper.PsiClassWrapper;
041import org.jetbrains.jet.lang.resolve.java.wrapper.PsiMethodWrapper;
042import org.jetbrains.jet.lang.resolve.name.FqName;
043import org.jetbrains.jet.lang.resolve.name.FqNameBase;
044import org.jetbrains.jet.lang.resolve.name.FqNameUnsafe;
045import org.jetbrains.jet.lang.resolve.name.Name;
046import org.jetbrains.jet.lang.types.JetType;
047import org.jetbrains.jet.lang.types.TypeUtils;
048import org.jetbrains.jet.lang.types.checker.JetTypeChecker;
049
050import javax.inject.Inject;
051import java.util.ArrayList;
052import java.util.List;
053import java.util.Map;
054import java.util.Set;
055
056public final class JavaClassResolver {
057
058    // NOTE: this complexity is introduced because class descriptors do not always have valid fqnames (class objects)
059    @NotNull
060    private final Map<FqNameBase, ClassDescriptor> classDescriptorCache =
061            new THashMap<FqNameBase, ClassDescriptor>(new TObjectHashingStrategy<FqNameBase>() {
062                @Override
063                public int computeHashCode(FqNameBase o) {
064                    if (o instanceof FqName) {
065                        return ((FqName) o).toUnsafe().hashCode();
066                    }
067                    assert o instanceof FqNameUnsafe;
068                    return o.hashCode();
069                }
070
071                @Override
072                public boolean equals(FqNameBase n1, FqNameBase n2) {
073                    return n1.equalsTo(n2.toString()) && n2.equalsTo(n1.toString());
074                }
075            });
076
077    @NotNull
078    private final Set<FqNameBase> unresolvedCache = Sets.newHashSet();
079
080    private BindingTrace trace;
081    private JavaSignatureResolver signatureResolver;
082    private JavaSemanticServices semanticServices;
083    private JavaAnnotationResolver annotationResolver;
084    private PsiClassFinder psiClassFinder;
085    private JavaNamespaceResolver namespaceResolver;
086    private JavaClassObjectResolver classObjectResolver;
087    private JavaSupertypeResolver supertypesResolver;
088    private JavaFunctionResolver functionResolver;
089
090    public JavaClassResolver() {
091    }
092
093    @Inject
094    public void setTrace(BindingTrace trace) {
095        this.trace = trace;
096    }
097
098    @Inject
099    public void setSignatureResolver(JavaSignatureResolver signatureResolver) {
100        this.signatureResolver = signatureResolver;
101    }
102
103    @Inject
104    public void setClassObjectResolver(JavaClassObjectResolver classObjectResolver) {
105        this.classObjectResolver = classObjectResolver;
106    }
107
108    @Inject
109    public void setSemanticServices(JavaSemanticServices semanticServices) {
110        this.semanticServices = semanticServices;
111    }
112
113    @Inject
114    public void setAnnotationResolver(JavaAnnotationResolver annotationResolver) {
115        this.annotationResolver = annotationResolver;
116    }
117
118    @Inject
119    public void setPsiClassFinder(PsiClassFinder psiClassFinder) {
120        this.psiClassFinder = psiClassFinder;
121    }
122
123    @Inject
124    public void setNamespaceResolver(JavaNamespaceResolver namespaceResolver) {
125        this.namespaceResolver = namespaceResolver;
126    }
127
128    @Inject
129    public void setSupertypesResolver(JavaSupertypeResolver supertypesResolver) {
130        this.supertypesResolver = supertypesResolver;
131    }
132
133    @Inject
134    public void setFunctionResolver(JavaFunctionResolver functionResolver) {
135        this.functionResolver = functionResolver;
136    }
137
138    @Nullable
139    public ClassDescriptor resolveClass(@NotNull FqName qualifiedName, @NotNull DescriptorSearchRule searchRule) {
140        PostponedTasks postponedTasks = new PostponedTasks();
141        ClassDescriptor classDescriptor = resolveClass(qualifiedName, searchRule, postponedTasks);
142        postponedTasks.performTasks();
143        return classDescriptor;
144    }
145
146    @Nullable
147    public ClassDescriptor resolveClass(
148            @NotNull FqName qualifiedName,
149            @NotNull DescriptorSearchRule searchRule,
150            @NotNull PostponedTasks tasks
151    ) {
152        if (isTraitImplementation(qualifiedName)) {
153            return null;
154        }
155
156        ClassDescriptor builtinClassDescriptor = semanticServices.getKotlinBuiltinClassDescriptor(qualifiedName);
157        if (builtinClassDescriptor != null) {
158            return builtinClassDescriptor;
159        }
160
161        // First, let's check that this is a real Java class, not a Java's view on a Kotlin class:
162        ClassDescriptor kotlinClassDescriptor = semanticServices.getKotlinClassDescriptor(qualifiedName);
163        if (kotlinClassDescriptor != null) {
164            return searchRule.processFoundInKotlin(kotlinClassDescriptor);
165        }
166
167        // Not let's take a descriptor of a Java class
168        FqNameUnsafe fqName = javaClassToKotlinFqName(qualifiedName);
169        ClassDescriptor cachedDescriptor = classDescriptorCache.get(fqName);
170        if (cachedDescriptor != null) {
171            return cachedDescriptor;
172        }
173
174        if (unresolvedCache.contains(fqName)) {
175            return null;
176        }
177
178        return doResolveClass(qualifiedName, tasks);
179    }
180
181    private ClassDescriptor doResolveClass(@NotNull FqName qualifiedName, @NotNull PostponedTasks tasks) {
182        PsiClass psiClass = psiClassFinder.findPsiClass(qualifiedName, PsiClassFinder.RuntimeClassesHandleMode.REPORT_ERROR);
183        if (psiClass == null) {
184            cacheNegativeValue(javaClassToKotlinFqName(qualifiedName));
185            return null;
186        }
187
188        // Class may have been resolved previously by different Java resolver instance, and we are reusing its trace
189        ClassDescriptor alreadyResolved = trace.get(BindingContext.CLASS, psiClass);
190        if (alreadyResolved != null) {
191            return alreadyResolved;
192        }
193
194        return createJavaClassDescriptor(qualifiedName, psiClass, tasks);
195    }
196
197    private void cacheNegativeValue(@NotNull FqNameBase qualifiedName) {
198        if (unresolvedCache.contains(qualifiedName) || classDescriptorCache.containsKey(qualifiedName)) {
199            throw new IllegalStateException("rewrite at " + qualifiedName);
200        }
201        unresolvedCache.add(qualifiedName);
202    }
203
204    private static boolean isTraitImplementation(@NotNull FqName qualifiedName) {
205        // TODO: only if -$$TImpl class is created by Kotlin
206        return qualifiedName.asString().endsWith(JvmAbi.TRAIT_IMPL_SUFFIX);
207    }
208
209    @NotNull
210    private ClassDescriptor createJavaClassDescriptor(
211            @NotNull FqName fqName, @NotNull PsiClass psiClass,
212            @NotNull PostponedTasks taskList
213    ) {
214
215        checkFqNamesAreConsistent(psiClass, fqName);
216        DescriptorResolverUtils.checkPsiClassIsNotJet(psiClass);
217
218        ClassOrNamespaceDescriptor containingDeclaration = resolveParentDescriptor(psiClass);
219        // class may be resolved during resolution of parent
220        ClassDescriptor cachedDescriptor = classDescriptorCache.get(javaClassToKotlinFqName(fqName));
221        if (cachedDescriptor != null) {
222            return cachedDescriptor;
223        }
224
225        assert (!unresolvedCache.contains(fqName)) : "We can resolve the class, so it can't be 'unresolved' during parent resolution";
226
227        return doCreateClassDescriptor(fqName, psiClass, taskList, containingDeclaration);
228    }
229
230    @NotNull
231    private ClassDescriptorFromJvmBytecode doCreateClassDescriptor(
232            @NotNull FqName fqName,
233            @NotNull PsiClass psiClass,
234            @NotNull PostponedTasks taskList,
235            @NotNull ClassOrNamespaceDescriptor containingDeclaration
236    ) {
237        JetClassAnnotation jetClassAnnotation = JetClassAnnotation.get(psiClass);
238        AbiVersionUtil.checkAbiVersion(psiClass, jetClassAnnotation, trace);
239
240        ClassKind kind = getClassKind(psiClass, jetClassAnnotation);
241        ClassPsiDeclarationProvider classData = semanticServices.getPsiDeclarationProviderFactory().createBinaryClassData(psiClass);
242        ClassDescriptorFromJvmBytecode classDescriptor = new ClassDescriptorFromJvmBytecode(
243                containingDeclaration, kind, isInnerClass(psiClass));
244
245        cache(javaClassToKotlinFqName(fqName), classDescriptor);
246        classDescriptor.setName(Name.identifier(psiClass.getName()));
247
248        List<JavaSignatureResolver.TypeParameterDescriptorInitialization> typeParameterDescriptorInitializations
249                = signatureResolver.createUninitializedClassTypeParameters(psiClass, classDescriptor);
250
251        classDescriptor.setTypeParameterDescriptors(getTypeParametersDescriptors(typeParameterDescriptorInitializations));
252        List<JetType> supertypes = Lists.newArrayList();
253        classDescriptor.setSupertypes(supertypes);
254        classDescriptor.setVisibility(DescriptorResolverUtils.resolveVisibility(psiClass, jetClassAnnotation));
255        classDescriptor.setModality(resolveModality(psiClass, classDescriptor));
256        classDescriptor.createTypeConstructor();
257        JavaClassNonStaticMembersScope membersScope = new JavaClassNonStaticMembersScope(classDescriptor, classData, semanticServices);
258        classDescriptor.setScopeForMemberLookup(membersScope);
259        classDescriptor.setScopeForConstructorResolve(membersScope);
260
261        String context = "class " + psiClass.getQualifiedName();
262        signatureResolver.initializeTypeParameters(typeParameterDescriptorInitializations, classDescriptor, context);
263
264        // TODO: ugly hack: tests crash if initializeTypeParameters called with class containing proper supertypes
265        List<TypeParameterDescriptor> classTypeParameters = classDescriptor.getTypeConstructor().getParameters();
266        supertypes.addAll(supertypesResolver.getSupertypes(classDescriptor, new PsiClassWrapper(psiClass), classData, classTypeParameters));
267
268        ClassDescriptorFromJvmBytecode classObjectDescriptor = classObjectResolver.createClassObjectDescriptor(classDescriptor, psiClass);
269        cache(DescriptorResolverUtils.getFqNameForClassObject(psiClass), classObjectDescriptor);
270        if (classObjectDescriptor != null) {
271            classDescriptor.getBuilder().setClassObjectDescriptor(classObjectDescriptor);
272        }
273
274        classDescriptor.setAnnotations(annotationResolver.resolveAnnotations(psiClass, taskList));
275
276        trace.record(BindingContext.CLASS, psiClass, classDescriptor);
277
278        PsiMethod samInterfaceMethod = MembersCache.getSamInterfaceMethod(psiClass);
279        if (samInterfaceMethod != null) {
280            SimpleFunctionDescriptor abstractMethod = resolveFunctionOfSamInterface(samInterfaceMethod, classDescriptor);
281            classDescriptor.setFunctionTypeForSamInterface(SingleAbstractMethodUtils.getFunctionTypeForAbstractMethod(abstractMethod));
282        }
283
284        return classDescriptor;
285    }
286
287    @NotNull
288    private SimpleFunctionDescriptor resolveFunctionOfSamInterface(
289            @NotNull PsiMethod samInterfaceMethod,
290            @NotNull ClassDescriptorFromJvmBytecode samInterface
291    ) {
292        PsiClass methodContainer = samInterfaceMethod.getContainingClass();
293        assert methodContainer != null : "method container is null for " + samInterfaceMethod;
294        String containerQualifiedName = methodContainer.getQualifiedName();
295        assert containerQualifiedName != null : "qualified name is null for " + methodContainer;
296
297        if (DescriptorUtils.getFQName(samInterface).asString().equals(containerQualifiedName)) {
298            SimpleFunctionDescriptor abstractMethod =
299                    functionResolver.resolveFunctionMutely(new PsiMethodWrapper(samInterfaceMethod), samInterface);
300            assert abstractMethod != null : "couldn't resolve method " + samInterfaceMethod;
301            return abstractMethod;
302        }
303        else {
304            return findFunctionWithMostSpecificReturnType(TypeUtils.getAllSupertypes(samInterface.getDefaultType()));
305        }
306    }
307
308    private static SimpleFunctionDescriptor findFunctionWithMostSpecificReturnType(@NotNull Set<JetType> supertypes) {
309        List<SimpleFunctionDescriptor> candidates = Lists.newArrayList();
310        for (JetType supertype : supertypes) {
311            List<CallableMemberDescriptor> abstractMembers = SingleAbstractMethodUtils.getAbstractMembers(supertype);
312            if (!abstractMembers.isEmpty()) {
313                candidates.add((SimpleFunctionDescriptor) abstractMembers.get(0));
314            }
315        }
316        if (candidates.isEmpty()) {
317            throw new IllegalStateException("Couldn't find abstract method in supertypes " + supertypes);
318        }
319        SimpleFunctionDescriptor currentMostSpecificType = candidates.get(0);
320        for (SimpleFunctionDescriptor candidate : candidates) {
321            if (JetTypeChecker.INSTANCE.isSubtypeOf(candidate.getReturnType(), currentMostSpecificType.getReturnType())) {
322                currentMostSpecificType = candidate;
323            }
324        }
325        return currentMostSpecificType;
326    }
327
328    private void cache(@NotNull FqNameBase fqName, @Nullable ClassDescriptor classDescriptor) {
329        if (classDescriptor == null) {
330            cacheNegativeValue(fqName);
331        }
332        else {
333            ClassDescriptor oldValue = classDescriptorCache.put(fqName, classDescriptor);
334            assert oldValue == null;
335        }
336    }
337
338    @NotNull
339    private static List<TypeParameterDescriptor> getTypeParametersDescriptors(
340            @NotNull List<JavaSignatureResolver.TypeParameterDescriptorInitialization> typeParameterDescriptorInitializations
341    ) {
342        List<TypeParameterDescriptor> typeParameters = Lists.newArrayList();
343        for (JavaSignatureResolver.TypeParameterDescriptorInitialization typeParameter : typeParameterDescriptorInitializations) {
344            typeParameters.add(typeParameter.getDescriptor());
345        }
346        return typeParameters;
347    }
348
349    @NotNull
350    private static Modality resolveModality(@NotNull PsiClass psiClass, @NotNull ClassDescriptor classDescriptor) {
351        if (classDescriptor.getKind() == ClassKind.ANNOTATION_CLASS) {
352            return Modality.FINAL;
353        }
354        return Modality.convertFromFlags(
355                psiClass.hasModifierProperty(PsiModifier.ABSTRACT) || psiClass.isInterface(),
356                !psiClass.hasModifierProperty(PsiModifier.FINAL));
357    }
358
359    void checkFqNamesAreConsistent(@NotNull PsiClass psiClass, @NotNull FqName desiredFqName) {
360        String qualifiedName = psiClass.getQualifiedName();
361        assert qualifiedName != null;
362
363        FqName fqName = new FqName(qualifiedName);
364        assert fqName.equals(desiredFqName);
365        FqNameUnsafe correctedName = javaClassToKotlinFqName(fqName);
366        if (classDescriptorCache.containsKey(correctedName) || unresolvedCache.contains(correctedName)) {
367            throw new IllegalStateException(qualifiedName);
368        }
369    }
370
371    @NotNull
372    private ClassOrNamespaceDescriptor resolveParentDescriptor(@NotNull PsiClass psiClass) {
373        if (isContainedInClass(psiClass)) {
374            return resolveParentClass(psiClass);
375        }
376        else {
377            return resolveParentNamespace(psiClass);
378        }
379    }
380
381    @NotNull
382    private static FqName getFqName(@NotNull PsiClass psiClass) {
383        String qualifiedName = psiClass.getQualifiedName();
384        assert qualifiedName != null;
385        return new FqName(qualifiedName);
386    }
387
388    // This method replaces "object" segments of FQ name to "<class-object-for-...>"
389    @NotNull
390    private static FqNameUnsafe javaClassToKotlinFqName(@NotNull FqName rawFqName) {
391        List<Name> correctedSegments = new ArrayList<Name>();
392        for (Name segment : rawFqName.pathSegments()) {
393            if (JvmAbi.CLASS_OBJECT_CLASS_NAME.equals(segment.asString())) {
394                assert !correctedSegments.isEmpty();
395                Name previous = correctedSegments.get(correctedSegments.size() - 1);
396                correctedSegments.add(DescriptorUtils.getClassObjectName(previous));
397            }
398            else {
399                correctedSegments.add(segment);
400            }
401        }
402        return new FqNameUnsafe(StringUtil.join(correctedSegments, "."));
403    }
404
405    private static boolean isContainedInClass(@NotNull PsiClass psiClass) {
406        return psiClass.getContainingClass() != null;
407    }
408
409    private static boolean isInnerClass(@NotNull PsiClass psiClass) {
410        return isContainedInClass(psiClass) && !psiClass.hasModifierProperty(PsiModifier.STATIC);
411    }
412
413    @NotNull
414    private ClassOrNamespaceDescriptor resolveParentClass(@NotNull PsiClass psiClass) {
415        PsiClass containingClass = psiClass.getContainingClass();
416        assert containingClass != null;
417        FqName containerFqName = getFqName(containingClass);
418        ClassDescriptor parentClass = resolveClass(containerFqName, DescriptorSearchRule.INCLUDE_KOTLIN);
419        if (parentClass == null) {
420            throw new IllegalStateException(
421                    "PsiClass not found by name " + containerFqName + ", required to be container declaration of " + getFqName(psiClass));
422        }
423        return parentClass;
424    }
425
426    @NotNull
427    private ClassOrNamespaceDescriptor resolveParentNamespace(@NotNull PsiClass psiClass) {
428        FqName namespaceFqName = getFqName(psiClass).parent();
429        NamespaceDescriptor parentNamespace = namespaceResolver.resolveNamespace(namespaceFqName, DescriptorSearchRule.INCLUDE_KOTLIN);
430        if (parentNamespace == null) {
431            throw new IllegalStateException("cannot resolve namespace " + namespaceFqName +
432                                            ", required to be container for " + getFqName(psiClass));
433        }
434        return parentNamespace;
435    }
436
437    @NotNull
438    private static ClassKind getClassKind(@NotNull PsiClass psiClass, @NotNull JetClassAnnotation jetClassAnnotation) {
439        if (psiClass.isInterface()) {
440            return (psiClass.isAnnotationType() ? ClassKind.ANNOTATION_CLASS : ClassKind.TRAIT);
441        }
442        if (psiClass.isEnum()) {
443            return ClassKind.ENUM_CLASS;
444        }
445        else {
446            return jetClassAnnotation.kind() == JvmStdlibNames.FLAG_CLASS_KIND_OBJECT ? ClassKind.OBJECT : ClassKind.CLASS;
447        }
448    }
449
450    @Nullable
451    public ClassDescriptor resolveClass(FqName name) {
452        return resolveClass(name, DescriptorSearchRule.ERROR_IF_FOUND_IN_KOTLIN);
453    }
454}