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