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.JetKeywordToken;
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((JetFile) classOrObject.getContainingFile())) {
070                return null;
071            }
072    
073            String jvmInternalName = getJvmInternalName(classOrObject);
074            if (jvmInternalName == null) return null;
075    
076            FqName fqName = JvmClassName.byInternalName(jvmInternalName).getFqNameForClassNameWithoutDollars();
077    
078            if (classOrObject instanceof JetObjectDeclaration && ((JetObjectDeclaration) classOrObject).isObjectLiteral()) {
079                return new KotlinLightClassForAnonymousDeclaration(manager, fqName, classOrObject);
080            }
081            return new KotlinLightClassForExplicitDeclaration(manager, fqName, classOrObject);
082        }
083    
084        private static String getJvmInternalName(JetClassOrObject classOrObject) {
085            if (JetPsiUtil.isLocal(classOrObject)) {
086                LightClassDataForKotlinClass data = getLightClassDataExactly(classOrObject);
087                return data != null ? data.getJvmInternalName() : "";
088            }
089            return PsiCodegenPredictor.getPredefinedJvmInternalName(classOrObject);
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 (JetPsiUtil.isLocal(classOrObject)) {
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        private static OutermostKotlinClassLightClassData getLightClassData(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 JetPsiUtil.getFQName((JetFile) classOrObject.getContainingFile()).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            boolean nestedClass = classOrObject.getParent() != classOrObject.getContainingFile();
401            Collection<String> psiModifiers = Sets.newHashSet();
402    
403            // PUBLIC, PROTECTED, PRIVATE, ABSTRACT, FINAL
404            //noinspection unchecked
405            List<Pair<JetKeywordToken, String>> jetTokenToPsiModifier = Lists.newArrayList(
406                    Pair.create(PUBLIC_KEYWORD, PsiModifier.PUBLIC),
407                    Pair.create(INTERNAL_KEYWORD, PsiModifier.PUBLIC),
408                    Pair.create(PROTECTED_KEYWORD, PsiModifier.PROTECTED),
409                    Pair.create(FINAL_KEYWORD, PsiModifier.FINAL));
410    
411            for (Pair<JetKeywordToken, String> tokenAndModifier : jetTokenToPsiModifier) {
412                if (classOrObject.hasModifier(tokenAndModifier.first)) {
413                    psiModifiers.add(tokenAndModifier.second);
414                }
415            }
416    
417            if (classOrObject.hasModifier(PRIVATE_KEYWORD)) {
418                // Top-level private class has PUBLIC visibility in Java
419                // Nested private class has PRIVATE visibility
420                psiModifiers.add(nestedClass ? PsiModifier.PRIVATE : PsiModifier.PUBLIC);
421            }
422    
423            if (!psiModifiers.contains(PsiModifier.PRIVATE) && !psiModifiers.contains(PsiModifier.PROTECTED)) {
424                psiModifiers.add(PsiModifier.PUBLIC); // For internal (default) visibility
425            }
426    
427    
428            // FINAL
429            if (isAbstract(classOrObject)) {
430                psiModifiers.add(PsiModifier.ABSTRACT);
431            }
432            else if (!classOrObject.hasModifier(OPEN_KEYWORD)) {
433                psiModifiers.add(PsiModifier.FINAL);
434            }
435    
436            if (nestedClass && !classOrObject.hasModifier(INNER_KEYWORD)) {
437                psiModifiers.add(PsiModifier.STATIC);
438            }
439    
440            return ArrayUtil.toStringArray(psiModifiers);
441        }
442    
443        private boolean isAbstract(@NotNull JetClassOrObject object) {
444            return object.hasModifier(ABSTRACT_KEYWORD) || isInterface();
445        }
446    
447        @Override
448        public boolean hasModifierProperty(@NonNls @NotNull String name) {
449            return getModifierList().hasModifierProperty(name);
450        }
451    
452        @Override
453        public boolean isDeprecated() {
454            JetModifierList jetModifierList = classOrObject.getModifierList();
455            if (jetModifierList == null) {
456                return false;
457            }
458    
459            ClassDescriptor deprecatedAnnotation = KotlinBuiltIns.getInstance().getDeprecatedAnnotation();
460            String deprecatedName = deprecatedAnnotation.getName().asString();
461            FqNameUnsafe deprecatedFqName = DescriptorUtils.getFqName(deprecatedAnnotation);
462    
463            for (JetAnnotationEntry annotationEntry : jetModifierList.getAnnotationEntries()) {
464                JetTypeReference typeReference = annotationEntry.getTypeReference();
465                if (typeReference == null) continue;
466    
467                JetTypeElement typeElement = typeReference.getTypeElement();
468                if (!(typeElement instanceof JetUserType)) continue; // If it's not a user type, it's definitely not a ref to deprecated
469    
470                FqName fqName = JetPsiUtil.toQualifiedName((JetUserType) typeElement);
471                if (fqName == null) continue;
472    
473                if (deprecatedFqName.equals(fqName.toUnsafe())) return true;
474                if (deprecatedName.equals(fqName.asString())) return true;
475            }
476            return false;
477        }
478    
479        @Override
480        public boolean isInterface() {
481            if (!(classOrObject instanceof JetClass)) return false;
482            JetClass jetClass = (JetClass) classOrObject;
483            return jetClass.isTrait() || jetClass.isAnnotation();
484        }
485    
486        @Override
487        public boolean isAnnotationType() {
488            return classOrObject instanceof JetClass && ((JetClass) classOrObject).isAnnotation();
489        }
490    
491        @Override
492        public boolean isEnum() {
493            return classOrObject instanceof JetClass && ((JetClass) classOrObject).isEnum();
494        }
495    
496        @Override
497        public boolean hasTypeParameters() {
498            return classOrObject instanceof JetClass && !((JetClass) classOrObject).getTypeParameters().isEmpty();
499        }
500    
501        @Override
502        public boolean isValid() {
503            return classOrObject.isValid();
504        }
505    
506        @Override
507        public boolean isInheritor(@NotNull PsiClass baseClass, boolean checkDeep) {
508            // Java inheritor check doesn't work when trait (interface in Java) subclasses Java class and for Kotlin local classes
509            if (baseClass instanceof KotlinLightClassForExplicitDeclaration || (isInterface() && !baseClass.isInterface())) {
510                String qualifiedName;
511                if (baseClass instanceof KotlinLightClassForExplicitDeclaration) {
512                    ClassDescriptor baseDescriptor = ((KotlinLightClassForExplicitDeclaration) baseClass).getDescriptor();
513                    qualifiedName = baseDescriptor != null ? DescriptorUtils.getFqName(baseDescriptor).asString() : null;
514                }
515                else {
516                    qualifiedName = baseClass.getQualifiedName();
517                }
518    
519                ClassDescriptor thisDescriptor = getDescriptor();
520                return qualifiedName != null
521                       && thisDescriptor != null
522                       && checkSuperTypeByFQName(thisDescriptor, qualifiedName, checkDeep);
523            }
524    
525            return super.isInheritor(baseClass, checkDeep);
526        }
527    
528        @Override
529        public PsiElement setName(@NonNls @NotNull String name) throws IncorrectOperationException {
530            throw new IncorrectOperationException("Cannot modify compiled kotlin element");
531        }
532    
533        @Override
534        public String toString() {
535            try {
536                return KotlinLightClass.class.getSimpleName() + ":" + getQualifiedName();
537            }
538            catch (Throwable e) {
539                return KotlinLightClass.class.getSimpleName() + ":" + e.toString();
540            }
541        }
542    
543        @NotNull
544        @Override
545        public List<PsiClass> getOwnInnerClasses() {
546            // TODO: Should return inner class wrapper
547            return Arrays.asList(getDelegate().getInnerClasses());
548        }
549    
550        private static boolean checkSuperTypeByFQName(@NotNull ClassDescriptor classDescriptor, @NotNull String qualifiedName, Boolean deep) {
551            if (CommonClassNames.JAVA_LANG_OBJECT.equals(qualifiedName)) return true;
552    
553            if (qualifiedName.equals(DescriptorUtils.getFqName(classDescriptor).asString())) return true;
554    
555            for (JetType superType : classDescriptor.getTypeConstructor().getSupertypes()) {
556                ClassifierDescriptor superDescriptor = superType.getConstructor().getDeclarationDescriptor();
557    
558                if (superDescriptor instanceof ClassDescriptor) {
559                    if (qualifiedName.equals(DescriptorUtils.getFqName(superDescriptor).asString())) return true;
560    
561                    if (deep) {
562                        if (checkSuperTypeByFQName((ClassDescriptor)superDescriptor, qualifiedName, true)) {
563                            return true;
564                        }
565                    }
566                }
567            }
568    
569            return false;
570        }
571    }