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, 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                    @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 ItemPresentation getPresentation() {
345            return ItemPresentationProviders.getItemPresentation(this);
346        }
347    
348        @Override
349        public Icon getElementIcon(int flags) {
350            throw new UnsupportedOperationException("This should be done byt JetIconProvider");
351        }
352    
353        @Override
354        public boolean equals(Object o) {
355            if (this == o) return true;
356            if (o == null || getClass() != o.getClass()) return false;
357    
358            KotlinLightClassForExplicitDeclaration aClass = (KotlinLightClassForExplicitDeclaration) o;
359    
360            if (!classFqName.equals(aClass.classFqName)) return false;
361    
362            return true;
363        }
364    
365        @Override
366        public int hashCode() {
367            return classFqName.hashCode();
368        }
369    
370        @Nullable
371        @Override
372        public PsiClass getContainingClass() {
373            if (classOrObject.getParent() == classOrObject.getContainingFile()) return null;
374            return super.getContainingClass();
375        }
376    
377        @Nullable
378        @Override
379        public PsiElement getParent() {
380            return parent.getValue();
381        }
382    
383        @Override
384        public PsiElement getContext() {
385            return getParent();
386        }
387    
388        @Nullable
389        @Override
390        public PsiTypeParameterList getTypeParameterList() {
391            return typeParameterList.getValue();
392        }
393    
394        @NotNull
395        @Override
396        public PsiTypeParameter[] getTypeParameters() {
397            PsiTypeParameterList typeParameterList = getTypeParameterList();
398            return typeParameterList == null ? PsiTypeParameter.EMPTY_ARRAY : typeParameterList.getTypeParameters();
399        }
400    
401        @Nullable
402        @Override
403        public String getName() {
404            return classFqName.shortName().asString();
405        }
406    
407        @Nullable
408        @Override
409        public String getQualifiedName() {
410            return classFqName.asString();
411        }
412    
413        @NotNull
414        @Override
415        public PsiModifierList getModifierList() {
416            if (modifierList == null) {
417                modifierList = new KotlinLightModifierList(this.getManager(), computeModifiers()) {
418                    @Override
419                    public PsiAnnotationOwner getDelegate() {
420                        return KotlinLightClassForExplicitDeclaration.this.getDelegate().getModifierList();
421                    }
422                };
423            }
424            return modifierList;
425        }
426    
427        @NotNull
428        private String[] computeModifiers() {
429            Collection<String> psiModifiers = Sets.newHashSet();
430    
431            // PUBLIC, PROTECTED, PRIVATE, ABSTRACT, FINAL
432            //noinspection unchecked
433            List<Pair<JetModifierKeywordToken, String>> jetTokenToPsiModifier = Lists.newArrayList(
434                    Pair.create(PUBLIC_KEYWORD, PsiModifier.PUBLIC),
435                    Pair.create(INTERNAL_KEYWORD, PsiModifier.PUBLIC),
436                    Pair.create(PROTECTED_KEYWORD, PsiModifier.PROTECTED),
437                    Pair.create(FINAL_KEYWORD, PsiModifier.FINAL));
438    
439            for (Pair<JetModifierKeywordToken, String> tokenAndModifier : jetTokenToPsiModifier) {
440                if (classOrObject.hasModifier(tokenAndModifier.first)) {
441                    psiModifiers.add(tokenAndModifier.second);
442                }
443            }
444    
445            if (classOrObject.hasModifier(PRIVATE_KEYWORD)) {
446                // Top-level private class has PUBLIC visibility in Java
447                // Nested private class has PRIVATE visibility
448                psiModifiers.add(classOrObject.isTopLevel() ? PsiModifier.PUBLIC : PsiModifier.PRIVATE);
449            }
450    
451            if (!psiModifiers.contains(PsiModifier.PRIVATE) && !psiModifiers.contains(PsiModifier.PROTECTED)) {
452                psiModifiers.add(PsiModifier.PUBLIC); // For internal (default) visibility
453            }
454    
455    
456            // FINAL
457            if (isAbstract(classOrObject)) {
458                psiModifiers.add(PsiModifier.ABSTRACT);
459            }
460            else if (!(classOrObject.hasModifier(OPEN_KEYWORD) || (classOrObject instanceof JetClass && ((JetClass) classOrObject).isEnum()))) {
461                psiModifiers.add(PsiModifier.FINAL);
462            }
463    
464            if (!classOrObject.isTopLevel() && !classOrObject.hasModifier(INNER_KEYWORD)) {
465                psiModifiers.add(PsiModifier.STATIC);
466            }
467    
468            return ArrayUtil.toStringArray(psiModifiers);
469        }
470    
471        private boolean isAbstract(@NotNull JetClassOrObject object) {
472            return object.hasModifier(ABSTRACT_KEYWORD) || isInterface();
473        }
474    
475        @Override
476        public boolean hasModifierProperty(@NonNls @NotNull String name) {
477            return getModifierList().hasModifierProperty(name);
478        }
479    
480        @Override
481        public boolean isDeprecated() {
482            JetModifierList jetModifierList = classOrObject.getModifierList();
483            if (jetModifierList == null) {
484                return false;
485            }
486    
487            ClassDescriptor deprecatedAnnotation = KotlinBuiltIns.getInstance().getDeprecatedAnnotation();
488            String deprecatedName = deprecatedAnnotation.getName().asString();
489            FqNameUnsafe deprecatedFqName = DescriptorUtils.getFqName(deprecatedAnnotation);
490    
491            for (JetAnnotationEntry annotationEntry : jetModifierList.getAnnotationEntries()) {
492                JetTypeReference typeReference = annotationEntry.getTypeReference();
493                if (typeReference == null) continue;
494    
495                JetTypeElement typeElement = typeReference.getTypeElement();
496                if (!(typeElement instanceof JetUserType)) continue; // If it's not a user type, it's definitely not a ref to deprecated
497    
498                FqName fqName = JetPsiUtil.toQualifiedName((JetUserType) typeElement);
499                if (fqName == null) continue;
500    
501                if (deprecatedFqName.equals(fqName.toUnsafe())) return true;
502                if (deprecatedName.equals(fqName.asString())) return true;
503            }
504            return false;
505        }
506    
507        @Override
508        public boolean isInterface() {
509            if (!(classOrObject instanceof JetClass)) return false;
510            JetClass jetClass = (JetClass) classOrObject;
511            return jetClass.isTrait() || jetClass.isAnnotation();
512        }
513    
514        @Override
515        public boolean isAnnotationType() {
516            return classOrObject instanceof JetClass && ((JetClass) classOrObject).isAnnotation();
517        }
518    
519        @Override
520        public boolean isEnum() {
521            return classOrObject instanceof JetClass && ((JetClass) classOrObject).isEnum();
522        }
523    
524        @Override
525        public boolean hasTypeParameters() {
526            return classOrObject instanceof JetClass && !((JetClass) classOrObject).getTypeParameters().isEmpty();
527        }
528    
529        @Override
530        public boolean isValid() {
531            return classOrObject.isValid();
532        }
533    
534        @Override
535        public boolean isInheritor(@NotNull PsiClass baseClass, boolean checkDeep) {
536            // Java inheritor check doesn't work when trait (interface in Java) subclasses Java class and for Kotlin local classes
537            if (baseClass instanceof KotlinLightClassForExplicitDeclaration || (isInterface() && !baseClass.isInterface())) {
538                String qualifiedName;
539                if (baseClass instanceof KotlinLightClassForExplicitDeclaration) {
540                    ClassDescriptor baseDescriptor = ((KotlinLightClassForExplicitDeclaration) baseClass).getDescriptor();
541                    qualifiedName = baseDescriptor != null ? DescriptorUtils.getFqName(baseDescriptor).asString() : null;
542                }
543                else {
544                    qualifiedName = baseClass.getQualifiedName();
545                }
546    
547                ClassDescriptor thisDescriptor = getDescriptor();
548                return qualifiedName != null
549                       && thisDescriptor != null
550                       && checkSuperTypeByFQName(thisDescriptor, qualifiedName, checkDeep);
551            }
552    
553            return super.isInheritor(baseClass, checkDeep);
554        }
555    
556        @Override
557        public PsiElement setName(@NonNls @NotNull String name) throws IncorrectOperationException {
558            throw new IncorrectOperationException("Cannot modify compiled kotlin element");
559        }
560    
561        @Override
562        public String toString() {
563            try {
564                return KotlinLightClass.class.getSimpleName() + ":" + getQualifiedName();
565            }
566            catch (Throwable e) {
567                return KotlinLightClass.class.getSimpleName() + ":" + e.toString();
568            }
569        }
570    
571        @NotNull
572        @Override
573        public List<PsiClass> getOwnInnerClasses() {
574            return KotlinPackage.filterNotNull(
575                    KotlinPackage.map(
576                            getDelegate().getInnerClasses(),
577                            new Function1<PsiClass, PsiClass>() {
578                                @Override
579                                public PsiClass invoke(PsiClass aClass) {
580                                    JetClassOrObject declaration = (JetClassOrObject) ClsWrapperStubPsiFactory.getOriginalDeclaration(aClass);
581                                    return declaration != null ? KotlinLightClassForExplicitDeclaration.create(myManager, declaration) : null;
582                                }
583                            }
584                    )
585            );
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    }