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