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