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.PsiPackage;
030    import com.intellij.psi.impl.PsiManagerImpl;
031    import com.intellij.psi.impl.compiled.ClsFileImpl;
032    import com.intellij.psi.impl.java.stubs.PsiJavaFileStub;
033    import com.intellij.psi.impl.light.LightClass;
034    import com.intellij.psi.impl.light.LightMethod;
035    import com.intellij.psi.scope.PsiScopeProcessor;
036    import com.intellij.psi.stubs.PsiClassHolderFileStub;
037    import com.intellij.psi.util.CachedValue;
038    import com.intellij.psi.util.CachedValuesManager;
039    import com.intellij.psi.util.PsiTreeUtil;
040    import com.intellij.util.ArrayUtil;
041    import com.intellij.util.IncorrectOperationException;
042    import kotlin.Function1;
043    import kotlin.KotlinPackage;
044    import org.jetbrains.annotations.NonNls;
045    import org.jetbrains.annotations.NotNull;
046    import org.jetbrains.annotations.Nullable;
047    import org.jetbrains.jet.codegen.binding.PsiCodegenPredictor;
048    import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
049    import org.jetbrains.jet.lang.descriptors.ClassifierDescriptor;
050    import org.jetbrains.jet.lang.psi.*;
051    import org.jetbrains.jet.lang.resolve.DescriptorUtils;
052    import org.jetbrains.jet.lang.resolve.java.JvmClassName;
053    import org.jetbrains.jet.lang.resolve.java.jetAsJava.JetJavaMirrorMarker;
054    import org.jetbrains.jet.lang.resolve.name.FqName;
055    import org.jetbrains.jet.lang.resolve.name.FqNameUnsafe;
056    import org.jetbrains.jet.lang.types.JetType;
057    import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
058    import org.jetbrains.jet.lexer.JetModifierKeywordToken;
059    import org.jetbrains.jet.plugin.JetLanguage;
060    
061    import javax.swing.*;
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);
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                    @SuppressWarnings("Contract")
304                    @Override
305                    public boolean processDeclarations(
306                            @NotNull PsiScopeProcessor processor,
307                            @NotNull ResolveState state,
308                            PsiElement lastParent,
309                            @NotNull PsiElement place
310                    ) {
311                        if (!super.processDeclarations(processor, state, lastParent, place)) return false;
312    
313                        // We have to explicitly process package declarations if current file belongs to default package
314                        // so that Java resolve can find classes located in that package
315                        String packageName = getPackageName();
316                        if (!packageName.isEmpty()) return true;
317    
318                        PsiPackage aPackage = JavaPsiFacade.getInstance(myManager.getProject()).findPackage(packageName);
319                        if (aPackage != null && !aPackage.processDeclarations(processor, state, null, place)) return false;
320    
321                        return true;
322                    }
323                };
324            }
325        };
326    
327        @Override
328        public PsiFile getContainingFile() {
329            return _containingFile.getValue();
330        }
331    
332        @NotNull
333        @Override
334        public PsiElement getNavigationElement() {
335            return classOrObject;
336        }
337    
338        @Override
339        public boolean isEquivalentTo(PsiElement another) {
340            return another instanceof PsiClass && Comparing.equal(((PsiClass) another).getQualifiedName(), getQualifiedName());
341        }
342    
343        @Override
344        public Icon getElementIcon(int flags) {
345            throw new UnsupportedOperationException("This should be done byt JetIconProvider");
346        }
347    
348        @Override
349        public boolean equals(Object o) {
350            if (this == o) return true;
351            if (o == null || getClass() != o.getClass()) return false;
352    
353            KotlinLightClassForExplicitDeclaration aClass = (KotlinLightClassForExplicitDeclaration) o;
354    
355            if (!classFqName.equals(aClass.classFqName)) return false;
356    
357            return true;
358        }
359    
360        @Override
361        public int hashCode() {
362            return classFqName.hashCode();
363        }
364    
365        @Nullable
366        @Override
367        public PsiClass getContainingClass() {
368            if (classOrObject.getParent() == classOrObject.getContainingFile()) return null;
369            return super.getContainingClass();
370        }
371    
372        @Nullable
373        @Override
374        public PsiElement getParent() {
375            return parent.getValue();
376        }
377    
378        @Override
379        public PsiElement getContext() {
380            return getParent();
381        }
382    
383        @Nullable
384        @Override
385        public PsiTypeParameterList getTypeParameterList() {
386            return typeParameterList.getValue();
387        }
388    
389        @NotNull
390        @Override
391        public PsiTypeParameter[] getTypeParameters() {
392            PsiTypeParameterList typeParameterList = getTypeParameterList();
393            return typeParameterList == null ? PsiTypeParameter.EMPTY_ARRAY : typeParameterList.getTypeParameters();
394        }
395    
396        @Nullable
397        @Override
398        public String getName() {
399            return classFqName.shortName().asString();
400        }
401    
402        @Nullable
403        @Override
404        public String getQualifiedName() {
405            return classFqName.asString();
406        }
407    
408        @NotNull
409        @Override
410        public PsiModifierList getModifierList() {
411            if (modifierList == null) {
412                modifierList = new KotlinLightModifierList(this.getManager(), computeModifiers()) {
413                    @Override
414                    public PsiAnnotationOwner getDelegate() {
415                        return KotlinLightClassForExplicitDeclaration.this.getDelegate().getModifierList();
416                    }
417                };
418            }
419            return modifierList;
420        }
421    
422        @NotNull
423        private String[] computeModifiers() {
424            Collection<String> psiModifiers = Sets.newHashSet();
425    
426            // PUBLIC, PROTECTED, PRIVATE, ABSTRACT, FINAL
427            //noinspection unchecked
428            List<Pair<JetModifierKeywordToken, String>> jetTokenToPsiModifier = Lists.newArrayList(
429                    Pair.create(PUBLIC_KEYWORD, PsiModifier.PUBLIC),
430                    Pair.create(INTERNAL_KEYWORD, PsiModifier.PUBLIC),
431                    Pair.create(PROTECTED_KEYWORD, PsiModifier.PROTECTED),
432                    Pair.create(FINAL_KEYWORD, PsiModifier.FINAL));
433    
434            for (Pair<JetModifierKeywordToken, String> tokenAndModifier : jetTokenToPsiModifier) {
435                if (classOrObject.hasModifier(tokenAndModifier.first)) {
436                    psiModifiers.add(tokenAndModifier.second);
437                }
438            }
439    
440            if (classOrObject.hasModifier(PRIVATE_KEYWORD)) {
441                // Top-level private class has PUBLIC visibility in Java
442                // Nested private class has PRIVATE visibility
443                psiModifiers.add(classOrObject.isTopLevel() ? PsiModifier.PUBLIC : PsiModifier.PRIVATE);
444            }
445    
446            if (!psiModifiers.contains(PsiModifier.PRIVATE) && !psiModifiers.contains(PsiModifier.PROTECTED)) {
447                psiModifiers.add(PsiModifier.PUBLIC); // For internal (default) visibility
448            }
449    
450    
451            // FINAL
452            if (isAbstract(classOrObject)) {
453                psiModifiers.add(PsiModifier.ABSTRACT);
454            }
455            else if (!(classOrObject.hasModifier(OPEN_KEYWORD) || (classOrObject instanceof JetClass && ((JetClass) classOrObject).isEnum()))) {
456                psiModifiers.add(PsiModifier.FINAL);
457            }
458    
459            if (!classOrObject.isTopLevel() && !classOrObject.hasModifier(INNER_KEYWORD)) {
460                psiModifiers.add(PsiModifier.STATIC);
461            }
462    
463            return ArrayUtil.toStringArray(psiModifiers);
464        }
465    
466        private boolean isAbstract(@NotNull JetClassOrObject object) {
467            return object.hasModifier(ABSTRACT_KEYWORD) || isInterface();
468        }
469    
470        @Override
471        public boolean hasModifierProperty(@NonNls @NotNull String name) {
472            return getModifierList().hasModifierProperty(name);
473        }
474    
475        @Override
476        public boolean isDeprecated() {
477            JetModifierList jetModifierList = classOrObject.getModifierList();
478            if (jetModifierList == null) {
479                return false;
480            }
481    
482            ClassDescriptor deprecatedAnnotation = KotlinBuiltIns.getInstance().getDeprecatedAnnotation();
483            String deprecatedName = deprecatedAnnotation.getName().asString();
484            FqNameUnsafe deprecatedFqName = DescriptorUtils.getFqName(deprecatedAnnotation);
485    
486            for (JetAnnotationEntry annotationEntry : jetModifierList.getAnnotationEntries()) {
487                JetTypeReference typeReference = annotationEntry.getTypeReference();
488                if (typeReference == null) continue;
489    
490                JetTypeElement typeElement = typeReference.getTypeElement();
491                if (!(typeElement instanceof JetUserType)) continue; // If it's not a user type, it's definitely not a ref to deprecated
492    
493                FqName fqName = JetPsiUtil.toQualifiedName((JetUserType) typeElement);
494                if (fqName == null) continue;
495    
496                if (deprecatedFqName.equals(fqName.toUnsafe())) return true;
497                if (deprecatedName.equals(fqName.asString())) return true;
498            }
499            return false;
500        }
501    
502        @Override
503        public boolean isInterface() {
504            if (!(classOrObject instanceof JetClass)) return false;
505            JetClass jetClass = (JetClass) classOrObject;
506            return jetClass.isTrait() || jetClass.isAnnotation();
507        }
508    
509        @Override
510        public boolean isAnnotationType() {
511            return classOrObject instanceof JetClass && ((JetClass) classOrObject).isAnnotation();
512        }
513    
514        @Override
515        public boolean isEnum() {
516            return classOrObject instanceof JetClass && ((JetClass) classOrObject).isEnum();
517        }
518    
519        @Override
520        public boolean hasTypeParameters() {
521            return classOrObject instanceof JetClass && !((JetClass) classOrObject).getTypeParameters().isEmpty();
522        }
523    
524        @Override
525        public boolean isValid() {
526            return classOrObject.isValid();
527        }
528    
529        @Override
530        public boolean isInheritor(@NotNull PsiClass baseClass, boolean checkDeep) {
531            // Java inheritor check doesn't work when trait (interface in Java) subclasses Java class and for Kotlin local classes
532            if (baseClass instanceof KotlinLightClassForExplicitDeclaration || (isInterface() && !baseClass.isInterface())) {
533                String qualifiedName;
534                if (baseClass instanceof KotlinLightClassForExplicitDeclaration) {
535                    ClassDescriptor baseDescriptor = ((KotlinLightClassForExplicitDeclaration) baseClass).getDescriptor();
536                    qualifiedName = baseDescriptor != null ? DescriptorUtils.getFqName(baseDescriptor).asString() : null;
537                }
538                else {
539                    qualifiedName = baseClass.getQualifiedName();
540                }
541    
542                ClassDescriptor thisDescriptor = getDescriptor();
543                return qualifiedName != null
544                       && thisDescriptor != null
545                       && checkSuperTypeByFQName(thisDescriptor, qualifiedName, checkDeep);
546            }
547    
548            return super.isInheritor(baseClass, checkDeep);
549        }
550    
551        @Override
552        public PsiElement setName(@NonNls @NotNull String name) throws IncorrectOperationException {
553            throw new IncorrectOperationException("Cannot modify compiled kotlin element");
554        }
555    
556        @Override
557        public String toString() {
558            try {
559                return KotlinLightClass.class.getSimpleName() + ":" + getQualifiedName();
560            }
561            catch (Throwable e) {
562                return KotlinLightClass.class.getSimpleName() + ":" + e.toString();
563            }
564        }
565    
566        @NotNull
567        @Override
568        public List<PsiClass> getOwnInnerClasses() {
569            return KotlinPackage.filterNotNull(
570                    KotlinPackage.map(
571                            getDelegate().getInnerClasses(),
572                            new Function1<PsiClass, PsiClass>() {
573                                @Override
574                                public PsiClass invoke(PsiClass aClass) {
575                                    JetClassOrObject declaration = (JetClassOrObject) ClsWrapperStubPsiFactory.getOriginalDeclaration(aClass);
576                                    return declaration != null ? KotlinLightClassForExplicitDeclaration.create(myManager, declaration) : null;
577                                }
578                            }
579                    )
580            );
581        }
582    
583        private static boolean checkSuperTypeByFQName(@NotNull ClassDescriptor classDescriptor, @NotNull String qualifiedName, Boolean deep) {
584            if (CommonClassNames.JAVA_LANG_OBJECT.equals(qualifiedName)) return true;
585    
586            if (qualifiedName.equals(DescriptorUtils.getFqName(classDescriptor).asString())) return true;
587    
588            for (JetType superType : classDescriptor.getTypeConstructor().getSupertypes()) {
589                ClassifierDescriptor superDescriptor = superType.getConstructor().getDeclarationDescriptor();
590    
591                if (superDescriptor instanceof ClassDescriptor) {
592                    if (qualifiedName.equals(DescriptorUtils.getFqName(superDescriptor).asString())) return true;
593    
594                    if (deep) {
595                        if (checkSuperTypeByFQName((ClassDescriptor)superDescriptor, qualifiedName, true)) {
596                            return true;
597                        }
598                    }
599                }
600            }
601    
602            return false;
603        }
604    }