001    /*
002     * Copyright 2010-2015 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.kotlin.asJava;
018    
019    import com.google.common.collect.Lists;
020    import com.google.common.collect.Sets;
021    import com.intellij.openapi.util.Comparing;
022    import com.intellij.openapi.util.Key;
023    import com.intellij.openapi.util.NullableLazyValue;
024    import com.intellij.openapi.util.Pair;
025    import com.intellij.openapi.vfs.VirtualFile;
026    import com.intellij.psi.*;
027    import com.intellij.psi.PsiPackage;
028    import com.intellij.psi.impl.PsiManagerImpl;
029    import com.intellij.psi.impl.compiled.ClsFileImpl;
030    import com.intellij.psi.impl.java.stubs.PsiJavaFileStub;
031    import com.intellij.psi.impl.light.LightClass;
032    import com.intellij.psi.impl.light.LightMethod;
033    import com.intellij.psi.scope.PsiScopeProcessor;
034    import com.intellij.psi.search.SearchScope;
035    import com.intellij.psi.stubs.PsiClassHolderFileStub;
036    import com.intellij.psi.util.CachedValue;
037    import com.intellij.psi.util.CachedValuesManager;
038    import com.intellij.psi.util.PsiTreeUtil;
039    import com.intellij.util.ArrayUtil;
040    import com.intellij.util.IncorrectOperationException;
041    import kotlin.Function1;
042    import kotlin.KotlinPackage;
043    import org.jetbrains.annotations.NonNls;
044    import org.jetbrains.annotations.NotNull;
045    import org.jetbrains.annotations.Nullable;
046    import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
047    import org.jetbrains.kotlin.codegen.binding.PsiCodegenPredictor;
048    import org.jetbrains.kotlin.descriptors.ClassDescriptor;
049    import org.jetbrains.kotlin.descriptors.ClassifierDescriptor;
050    import org.jetbrains.kotlin.idea.JetLanguage;
051    import org.jetbrains.kotlin.lexer.JetModifierKeywordToken;
052    import org.jetbrains.kotlin.name.FqName;
053    import org.jetbrains.kotlin.name.FqNameUnsafe;
054    import org.jetbrains.kotlin.psi.*;
055    import org.jetbrains.kotlin.resolve.DescriptorUtils;
056    import org.jetbrains.kotlin.resolve.jvm.JvmClassName;
057    import org.jetbrains.kotlin.types.JetType;
058    
059    import javax.swing.*;
060    import java.util.Collection;
061    import java.util.List;
062    
063    import static org.jetbrains.kotlin.lexer.JetTokens.*;
064    
065    public class KotlinLightClassForExplicitDeclaration extends KotlinWrappingLightClass implements JetJavaMirrorMarker {
066        private final static Key<CachedValue<OutermostKotlinClassLightClassData>> JAVA_API_STUB = Key.create("JAVA_API_STUB");
067    
068        @Nullable
069        public static KotlinLightClassForExplicitDeclaration create(@NotNull PsiManager manager, @NotNull JetClassOrObject classOrObject) {
070            if (LightClassUtil.belongsToKotlinBuiltIns(classOrObject.getContainingJetFile())) {
071                return null;
072            }
073    
074            FqName fqName = predictFqName(classOrObject);
075            if (fqName == null) return null;
076    
077            if (classOrObject instanceof JetObjectDeclaration && ((JetObjectDeclaration) classOrObject).isObjectLiteral()) {
078                return new KotlinLightClassForAnonymousDeclaration(manager, fqName, classOrObject);
079            }
080            return new KotlinLightClassForExplicitDeclaration(manager, fqName, classOrObject);
081        }
082    
083        @Nullable
084        private static FqName predictFqName(@NotNull JetClassOrObject classOrObject) {
085            if (classOrObject.isLocal()) {
086                LightClassDataForKotlinClass data = getLightClassDataExactly(classOrObject);
087                return data == null ? null : data.getJvmQualifiedName();
088            }
089            String internalName = PsiCodegenPredictor.getPredefinedJvmInternalName(classOrObject);
090            return internalName == null ? null : JvmClassName.byInternalName(internalName).getFqNameForClassNameWithoutDollars();
091        }
092    
093        private final FqName classFqName; // FqName of (possibly inner) class
094        protected final JetClassOrObject classOrObject;
095        private PsiClass delegate;
096    
097        private final NullableLazyValue<PsiElement> parent = new NullableLazyValue<PsiElement>() {
098            @Nullable
099            @Override
100            protected PsiElement compute() {
101                if (classOrObject.isLocal()) {
102                    //noinspection unchecked
103                    PsiElement declaration = JetPsiUtil.getTopmostParentOfTypes(
104                            classOrObject,
105                            JetNamedFunction.class, JetProperty.class, JetClassInitializer.class, JetParameter.class
106                    );
107    
108                    if (declaration instanceof JetParameter) {
109                        declaration = PsiTreeUtil.getParentOfType(declaration, JetNamedDeclaration.class);
110                    }
111    
112                    if (declaration instanceof JetNamedFunction) {
113                        JetNamedFunction function = (JetNamedFunction) declaration;
114                        return getParentByPsiMethod(LightClassUtil.getLightClassMethod(function), function.getName(), false);
115                    }
116    
117                    // Represent the property as a fake method with the same name
118                    if (declaration instanceof JetProperty) {
119                        JetProperty property = (JetProperty) declaration;
120                        return getParentByPsiMethod(LightClassUtil.getLightClassPropertyMethods(property).getGetter(), property.getName(), true);
121                    }
122    
123                    if (declaration instanceof JetClassInitializer) {
124                        PsiElement parent = declaration.getParent();
125                        PsiElement grandparent = parent.getParent();
126    
127                        if (parent instanceof JetClassBody && grandparent instanceof JetClassOrObject) {
128                            return LightClassUtil.getPsiClass((JetClassOrObject) grandparent);
129                        }
130                    }
131    
132                    if (declaration instanceof JetClass) {
133                        return LightClassUtil.getPsiClass((JetClass) declaration);
134                    }
135                }
136    
137                return classOrObject.getParent() == classOrObject.getContainingFile() ? getContainingFile() : getContainingClass();
138            }
139    
140            private PsiElement getParentByPsiMethod(PsiMethod method, final String name, boolean forceMethodWrapping) {
141                if (method == null || name == null) return null;
142    
143                PsiClass containingClass = method.getContainingClass();
144                if (containingClass == null) return null;
145    
146                final String currentFileName = classOrObject.getContainingFile().getName();
147    
148                boolean createWrapper = forceMethodWrapping;
149                // Use PsiClass wrapper instead of package light class to avoid names like "FooPackage" in Type Hierarchy and related views
150                if (containingClass instanceof KotlinLightClassForPackage) {
151                    containingClass = new LightClass(containingClass, JetLanguage.INSTANCE) {
152                        @Nullable
153                        @Override
154                        public String getName() {
155                            return currentFileName;
156                        }
157                    };
158                    createWrapper = true;
159                }
160    
161                if (createWrapper) {
162                    return new LightMethod(myManager, method, containingClass, JetLanguage.INSTANCE) {
163                        @Override
164                        public PsiElement getParent() {
165                            return getContainingClass();
166                        }
167    
168                        @NotNull
169                        @Override
170                        public String getName() {
171                            return name;
172                        }
173                    };
174                }
175    
176                return method;
177            }
178        };
179    
180        @Nullable
181        private PsiModifierList modifierList;
182    
183        private final NullableLazyValue<PsiTypeParameterList> typeParameterList = new NullableLazyValue<PsiTypeParameterList>() {
184            @Nullable
185            @Override
186            protected PsiTypeParameterList compute() {
187                return LightClassUtil.buildLightTypeParameterList(KotlinLightClassForExplicitDeclaration.this, classOrObject);
188            }
189        };
190    
191        KotlinLightClassForExplicitDeclaration(
192                @NotNull PsiManager manager,
193                @NotNull FqName name,
194                @NotNull JetClassOrObject classOrObject
195        ) {
196            super(manager);
197            this.classFqName = name;
198            this.classOrObject = classOrObject;
199        }
200    
201        @Override
202        @NotNull
203        public JetClassOrObject getOrigin() {
204            return classOrObject;
205        }
206    
207        @NotNull
208        @Override
209        public FqName getFqName() {
210            return classFqName;
211        }
212    
213        @NotNull
214        @Override
215        public PsiElement copy() {
216            return new KotlinLightClassForExplicitDeclaration(getManager(), classFqName, (JetClassOrObject) classOrObject.copy());
217        }
218    
219        @NotNull
220        @Override
221        public PsiClass getDelegate() {
222            if (delegate == null) {
223                PsiJavaFileStub javaFileStub = getJavaFileStub();
224    
225                PsiClass psiClass = LightClassUtil.findClass(classFqName, javaFileStub);
226                if (psiClass == null) {
227                    JetClassOrObject outermostClassOrObject = getOutermostClassOrObject(classOrObject);
228                    throw new IllegalStateException("Class was not found " + classFqName + "\n" +
229                                                    "in " + outermostClassOrObject.getContainingFile().getText() + "\n" +
230                                                    "stub: \n" + javaFileStub.getPsi().getText());
231                }
232                delegate = psiClass;
233            }
234    
235            return delegate;
236        }
237    
238        @NotNull
239        private PsiJavaFileStub getJavaFileStub() {
240            return getLightClassData().getJavaFileStub();
241        }
242    
243        @Nullable
244        protected final ClassDescriptor getDescriptor() {
245            LightClassDataForKotlinClass data = getLightClassDataExactly(classOrObject);
246            return data != null ? data.getDescriptor() : null;
247        }
248    
249        @NotNull
250        private OutermostKotlinClassLightClassData getLightClassData() {
251            return getLightClassData(classOrObject);
252        }
253    
254        @NotNull
255        public static OutermostKotlinClassLightClassData getLightClassData(@NotNull JetClassOrObject classOrObject) {
256            JetClassOrObject outermostClassOrObject = getOutermostClassOrObject(classOrObject);
257            return CachedValuesManager.getManager(classOrObject.getProject()).getCachedValue(
258                    outermostClassOrObject,
259                    JAVA_API_STUB,
260                    KotlinJavaFileStubProvider.createForDeclaredClass(outermostClassOrObject),
261                    /*trackValue = */false
262            );
263        }
264    
265        @Nullable
266        private static LightClassDataForKotlinClass getLightClassDataExactly(JetClassOrObject classOrObject) {
267            OutermostKotlinClassLightClassData data = getLightClassData(classOrObject);
268            return data.getClassOrObject().equals(classOrObject) ? data : data.getAllInnerClasses().get(classOrObject);
269        }
270    
271        @NotNull
272        private static JetClassOrObject getOutermostClassOrObject(@NotNull JetClassOrObject classOrObject) {
273            JetClassOrObject outermostClass = JetPsiUtil.getOutermostClassOrObject(classOrObject);
274            if (outermostClass == null) {
275                throw new IllegalStateException("Attempt to build a light class for a local class: " + classOrObject.getText());
276            }
277            else {
278                return outermostClass;
279            }
280        }
281    
282        private final NullableLazyValue<PsiFile> _containingFile = new NullableLazyValue<PsiFile>() {
283            @Nullable
284            @Override
285            protected PsiFile compute() {
286                VirtualFile virtualFile = classOrObject.getContainingFile().getVirtualFile();
287                assert virtualFile != null : "No virtual file for " + classOrObject.getText();
288                return new ClsFileImpl(new ClassFileViewProvider(getManager(), virtualFile)) {
289                    @NotNull
290                    @Override
291                    public String getPackageName() {
292                        return classOrObject.getContainingJetFile().getPackageFqName().asString();
293                    }
294    
295                    @NotNull
296                    @Override
297                    public PsiClassHolderFileStub getStub() {
298                        return getJavaFileStub();
299                    }
300    
301                    @SuppressWarnings("Contract")
302                    @Override
303                    public boolean processDeclarations(
304                            @NotNull PsiScopeProcessor processor,
305                            @NotNull ResolveState state,
306                            PsiElement lastParent,
307                            @NotNull PsiElement place
308                    ) {
309                        if (!super.processDeclarations(processor, state, lastParent, place)) return false;
310    
311                        // We have to explicitly process package declarations if current file belongs to default package
312                        // so that Java resolve can find classes located in that package
313                        String packageName = getPackageName();
314                        if (!packageName.isEmpty()) return true;
315    
316                        PsiPackage aPackage = JavaPsiFacade.getInstance(myManager.getProject()).findPackage(packageName);
317                        if (aPackage != null && !aPackage.processDeclarations(processor, state, null, place)) return false;
318    
319                        return true;
320                    }
321                };
322            }
323        };
324    
325        @Override
326        public PsiFile getContainingFile() {
327            return _containingFile.getValue();
328        }
329    
330        @NotNull
331        @Override
332        public PsiElement getNavigationElement() {
333            return classOrObject;
334        }
335    
336        @Override
337        public boolean isEquivalentTo(PsiElement another) {
338            return another instanceof PsiClass && Comparing.equal(((PsiClass) another).getQualifiedName(), getQualifiedName());
339        }
340    
341        @Override
342        public Icon getElementIcon(int flags) {
343            throw new UnsupportedOperationException("This should be done byt JetIconProvider");
344        }
345    
346        @Override
347        public boolean equals(Object o) {
348            if (this == o) return true;
349            if (o == null || getClass() != o.getClass()) return false;
350    
351            KotlinLightClassForExplicitDeclaration aClass = (KotlinLightClassForExplicitDeclaration) o;
352    
353            if (!classFqName.equals(aClass.classFqName)) return false;
354    
355            return true;
356        }
357    
358        @Override
359        public int hashCode() {
360            return classFqName.hashCode();
361        }
362    
363        @Nullable
364        @Override
365        public PsiClass getContainingClass() {
366            if (classOrObject.getParent() == classOrObject.getContainingFile()) return null;
367            return super.getContainingClass();
368        }
369    
370        @Nullable
371        @Override
372        public PsiElement getParent() {
373            return parent.getValue();
374        }
375    
376        @Override
377        public PsiElement getContext() {
378            return getParent();
379        }
380    
381        @Nullable
382        @Override
383        public PsiTypeParameterList getTypeParameterList() {
384            return typeParameterList.getValue();
385        }
386    
387        @NotNull
388        @Override
389        public PsiTypeParameter[] getTypeParameters() {
390            PsiTypeParameterList typeParameterList = getTypeParameterList();
391            return typeParameterList == null ? PsiTypeParameter.EMPTY_ARRAY : typeParameterList.getTypeParameters();
392        }
393    
394        @Nullable
395        @Override
396        public String getName() {
397            return classFqName.shortName().asString();
398        }
399    
400        @Nullable
401        @Override
402        public String getQualifiedName() {
403            return classFqName.asString();
404        }
405    
406        @NotNull
407        @Override
408        public PsiModifierList getModifierList() {
409            if (modifierList == null) {
410                modifierList = new KotlinLightModifierList(this.getManager(), computeModifiers()) {
411                    @Override
412                    public PsiAnnotationOwner getDelegate() {
413                        return KotlinLightClassForExplicitDeclaration.this.getDelegate().getModifierList();
414                    }
415                };
416            }
417            return modifierList;
418        }
419    
420        @NotNull
421        private String[] computeModifiers() {
422            Collection<String> psiModifiers = Sets.newHashSet();
423    
424            // PUBLIC, PROTECTED, PRIVATE, ABSTRACT, FINAL
425            //noinspection unchecked
426            List<Pair<JetModifierKeywordToken, String>> jetTokenToPsiModifier = Lists.newArrayList(
427                    Pair.create(PUBLIC_KEYWORD, PsiModifier.PUBLIC),
428                    Pair.create(INTERNAL_KEYWORD, PsiModifier.PUBLIC),
429                    Pair.create(PROTECTED_KEYWORD, PsiModifier.PROTECTED),
430                    Pair.create(FINAL_KEYWORD, PsiModifier.FINAL));
431    
432            for (Pair<JetModifierKeywordToken, String> tokenAndModifier : jetTokenToPsiModifier) {
433                if (classOrObject.hasModifier(tokenAndModifier.first)) {
434                    psiModifiers.add(tokenAndModifier.second);
435                }
436            }
437    
438            if (classOrObject.hasModifier(PRIVATE_KEYWORD)) {
439                // Top-level private class has PUBLIC visibility in Java
440                // Nested private class has PRIVATE visibility
441                psiModifiers.add(classOrObject.isTopLevel() ? PsiModifier.PUBLIC : PsiModifier.PRIVATE);
442            }
443    
444            if (!psiModifiers.contains(PsiModifier.PRIVATE) && !psiModifiers.contains(PsiModifier.PROTECTED)) {
445                psiModifiers.add(PsiModifier.PUBLIC); // For internal (default) visibility
446            }
447    
448    
449            // FINAL
450            if (isAbstract(classOrObject)) {
451                psiModifiers.add(PsiModifier.ABSTRACT);
452            }
453            else if (!(classOrObject.hasModifier(OPEN_KEYWORD) || (classOrObject instanceof JetClass && ((JetClass) classOrObject).isEnum()))) {
454                psiModifiers.add(PsiModifier.FINAL);
455            }
456    
457            if (!classOrObject.isTopLevel() && !classOrObject.hasModifier(INNER_KEYWORD)) {
458                psiModifiers.add(PsiModifier.STATIC);
459            }
460    
461            return ArrayUtil.toStringArray(psiModifiers);
462        }
463    
464        private boolean isAbstract(@NotNull JetClassOrObject object) {
465            return object.hasModifier(ABSTRACT_KEYWORD) || isInterface();
466        }
467    
468        @Override
469        public boolean hasModifierProperty(@NonNls @NotNull String name) {
470            return getModifierList().hasModifierProperty(name);
471        }
472    
473        @Override
474        public boolean isDeprecated() {
475            JetModifierList jetModifierList = classOrObject.getModifierList();
476            if (jetModifierList == null) {
477                return false;
478            }
479    
480            ClassDescriptor deprecatedAnnotation = KotlinBuiltIns.getInstance().getDeprecatedAnnotation();
481            String deprecatedName = deprecatedAnnotation.getName().asString();
482            FqNameUnsafe deprecatedFqName = DescriptorUtils.getFqName(deprecatedAnnotation);
483    
484            for (JetAnnotationEntry annotationEntry : jetModifierList.getAnnotationEntries()) {
485                JetTypeReference typeReference = annotationEntry.getTypeReference();
486                if (typeReference == null) continue;
487    
488                JetTypeElement typeElement = typeReference.getTypeElement();
489                if (!(typeElement instanceof JetUserType)) continue; // If it's not a user type, it's definitely not a ref to deprecated
490    
491                FqName fqName = JetPsiUtil.toQualifiedName((JetUserType) typeElement);
492                if (fqName == null) continue;
493    
494                if (deprecatedFqName.equals(fqName.toUnsafe())) return true;
495                if (deprecatedName.equals(fqName.asString())) return true;
496            }
497            return false;
498        }
499    
500        @Override
501        public boolean isInterface() {
502            if (!(classOrObject instanceof JetClass)) return false;
503            JetClass jetClass = (JetClass) classOrObject;
504            return jetClass.isTrait() || jetClass.isAnnotation();
505        }
506    
507        @Override
508        public boolean isAnnotationType() {
509            return classOrObject instanceof JetClass && ((JetClass) classOrObject).isAnnotation();
510        }
511    
512        @Override
513        public boolean isEnum() {
514            return classOrObject instanceof JetClass && ((JetClass) classOrObject).isEnum();
515        }
516    
517        @Override
518        public boolean hasTypeParameters() {
519            return classOrObject instanceof JetClass && !((JetClass) classOrObject).getTypeParameters().isEmpty();
520        }
521    
522        @Override
523        public boolean isValid() {
524            return classOrObject.isValid();
525        }
526    
527        @Override
528        public boolean isInheritor(@NotNull PsiClass baseClass, boolean checkDeep) {
529            // Java inheritor check doesn't work when trait (interface in Java) subclasses Java class and for Kotlin local classes
530            if (baseClass instanceof KotlinLightClassForExplicitDeclaration || (isInterface() && !baseClass.isInterface())) {
531                String qualifiedName;
532                if (baseClass instanceof KotlinLightClassForExplicitDeclaration) {
533                    ClassDescriptor baseDescriptor = ((KotlinLightClassForExplicitDeclaration) baseClass).getDescriptor();
534                    qualifiedName = baseDescriptor != null ? DescriptorUtils.getFqName(baseDescriptor).asString() : null;
535                }
536                else {
537                    qualifiedName = baseClass.getQualifiedName();
538                }
539    
540                ClassDescriptor thisDescriptor = getDescriptor();
541                return qualifiedName != null
542                       && thisDescriptor != null
543                       && checkSuperTypeByFQName(thisDescriptor, qualifiedName, checkDeep);
544            }
545    
546            return super.isInheritor(baseClass, checkDeep);
547        }
548    
549        @Override
550        public PsiElement setName(@NonNls @NotNull String name) throws IncorrectOperationException {
551            getOrigin().setName(name);
552            return this;
553        }
554    
555        @Override
556        public String toString() {
557            try {
558                return KotlinLightClass.class.getSimpleName() + ":" + getQualifiedName();
559            }
560            catch (Throwable e) {
561                return KotlinLightClass.class.getSimpleName() + ":" + e.toString();
562            }
563        }
564    
565        @NotNull
566        @Override
567        public List<PsiClass> getOwnInnerClasses() {
568            return KotlinPackage.filterNotNull(
569                    KotlinPackage.map(
570                            getDelegate().getInnerClasses(),
571                            new Function1<PsiClass, PsiClass>() {
572                                @Override
573                                public PsiClass invoke(PsiClass aClass) {
574                                    JetClassOrObject declaration = (JetClassOrObject) ClsWrapperStubPsiFactory.getOriginalDeclaration(aClass);
575                                    return declaration != null ? create(myManager, declaration) : null;
576                                }
577                            }
578                    )
579            );
580        }
581    
582        @NotNull
583        @Override
584        public SearchScope getUseScope() {
585            return getOrigin().getUseScope();
586        }
587    
588        private static boolean checkSuperTypeByFQName(@NotNull ClassDescriptor classDescriptor, @NotNull String qualifiedName, Boolean deep) {
589            if (CommonClassNames.JAVA_LANG_OBJECT.equals(qualifiedName)) return true;
590    
591            if (qualifiedName.equals(DescriptorUtils.getFqName(classDescriptor).asString())) return true;
592    
593            for (JetType superType : classDescriptor.getTypeConstructor().getSupertypes()) {
594                ClassifierDescriptor superDescriptor = superType.getConstructor().getDeclarationDescriptor();
595    
596                if (superDescriptor instanceof ClassDescriptor) {
597                    if (qualifiedName.equals(DescriptorUtils.getFqName(superDescriptor).asString())) return true;
598    
599                    if (deep) {
600                        if (checkSuperTypeByFQName((ClassDescriptor)superDescriptor, qualifiedName, true)) {
601                            return true;
602                        }
603                    }
604                }
605            }
606    
607            return false;
608        }
609    }