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