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