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
017package org.jetbrains.jet.asJava;
018
019import com.google.common.collect.Lists;
020import com.google.common.collect.Sets;
021import com.intellij.navigation.ItemPresentation;
022import com.intellij.navigation.ItemPresentationProviders;
023import com.intellij.openapi.util.Comparing;
024import com.intellij.openapi.util.Key;
025import com.intellij.openapi.util.NullableLazyValue;
026import com.intellij.openapi.util.Pair;
027import com.intellij.openapi.vfs.VirtualFile;
028import com.intellij.psi.*;
029import com.intellij.psi.impl.PsiManagerImpl;
030import com.intellij.psi.impl.compiled.ClsFileImpl;
031import com.intellij.psi.impl.java.stubs.PsiJavaFileStub;
032import com.intellij.psi.impl.light.AbstractLightClass;
033import com.intellij.psi.impl.light.LightModifierList;
034import com.intellij.psi.impl.light.LightTypeParameterListBuilder;
035import com.intellij.psi.stubs.PsiClassHolderFileStub;
036import com.intellij.psi.util.CachedValue;
037import com.intellij.psi.util.CachedValuesManager;
038import com.intellij.util.IncorrectOperationException;
039import org.jetbrains.annotations.NonNls;
040import org.jetbrains.annotations.NotNull;
041import org.jetbrains.annotations.Nullable;
042import org.jetbrains.jet.codegen.binding.PsiCodegenPredictor;
043import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
044import org.jetbrains.jet.lang.psi.*;
045import org.jetbrains.jet.lang.resolve.DescriptorUtils;
046import org.jetbrains.jet.lang.resolve.java.JetJavaMirrorMarker;
047import org.jetbrains.jet.lang.resolve.java.JvmClassName;
048import org.jetbrains.jet.lang.resolve.name.FqName;
049import org.jetbrains.jet.lang.resolve.name.FqNameUnsafe;
050import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
051import org.jetbrains.jet.lexer.JetKeywordToken;
052import org.jetbrains.jet.plugin.JetLanguage;
053
054import javax.swing.*;
055import java.util.Collection;
056import java.util.List;
057
058import static org.jetbrains.jet.lexer.JetTokens.*;
059
060public class KotlinLightClassForExplicitDeclaration extends AbstractLightClass implements KotlinLightClass, JetJavaMirrorMarker {
061    private final static Key<CachedValue<PsiJavaFileStub>> JAVA_API_STUB = Key.create("JAVA_API_STUB");
062
063    @Nullable
064    public static KotlinLightClassForExplicitDeclaration create(@NotNull PsiManager manager, @NotNull JetClassOrObject classOrObject) {
065        if (LightClassUtil.belongsToKotlinBuiltIns((JetFile) classOrObject.getContainingFile())) {
066            return null;
067        }
068
069        JvmClassName jvmClassName = PsiCodegenPredictor.getPredefinedJvmClassName(classOrObject);
070        if (jvmClassName == null) return null;
071
072        return new KotlinLightClassForExplicitDeclaration(manager, jvmClassName.getFqName(), classOrObject);
073    }
074
075    private final FqName classFqName; // FqName of (possibly inner) class
076    private final JetClassOrObject classOrObject;
077    private PsiClass delegate;
078
079    @Nullable
080    private PsiModifierList modifierList;
081
082    private NullableLazyValue<PsiTypeParameterList> typeParameterList = new NullableLazyValue<PsiTypeParameterList>() {
083        @Nullable
084        @Override
085        protected PsiTypeParameterList compute() {
086            LightTypeParameterListBuilder builder = new LightTypeParameterListBuilder(getManager(), getLanguage());
087            if (classOrObject instanceof JetTypeParameterListOwner) {
088                JetTypeParameterListOwner typeParameterListOwner = (JetTypeParameterListOwner) classOrObject;
089                List<JetTypeParameter> parameters = typeParameterListOwner.getTypeParameters();
090                for (int i = 0; i < parameters.size(); i++) {
091                    JetTypeParameter jetTypeParameter = parameters.get(i);
092                    String name = jetTypeParameter.getName();
093                    String safeName = name == null ? "__no_name__" : name;
094                    builder.addParameter(new KotlinLightTypeParameter(KotlinLightClassForExplicitDeclaration.this, i, safeName));
095                }
096            }
097            return builder;
098        }
099    };
100
101    private KotlinLightClassForExplicitDeclaration(
102            @NotNull PsiManager manager,
103            @NotNull FqName name,
104            @NotNull JetClassOrObject classOrObject
105    ) {
106        super(manager, JetLanguage.INSTANCE);
107        this.classFqName = name;
108        this.classOrObject = classOrObject;
109    }
110
111    @NotNull
112    public JetClassOrObject getJetClassOrObject() {
113        return classOrObject;
114    }
115
116    @NotNull
117    @Override
118    public FqName getFqName() {
119        return classFqName;
120    }
121
122    @NotNull
123    @Override
124    public PsiElement copy() {
125        return new KotlinLightClassForExplicitDeclaration(getManager(), classFqName, classOrObject);
126    }
127
128    @NotNull
129    @Override
130    public PsiClass getDelegate() {
131        if (delegate == null) {
132            PsiJavaFileStub javaFileStub = getJavaFileStub();
133
134            PsiClass psiClass = LightClassUtil.findClass(classFqName, javaFileStub);
135            if (psiClass == null) {
136                JetClassOrObject outermostClassOrObject = getOutermostClassOrObject(classOrObject);
137                throw new IllegalStateException("Class was not found " + classFqName + "\n" +
138                                                "in " + outermostClassOrObject.getContainingFile().getText() + "\n" +
139                                                "stub: \n" + javaFileStub.getPsi().getText());
140            }
141            delegate = psiClass;
142        }
143
144        return delegate;
145    }
146
147    @NotNull
148    private PsiJavaFileStub getJavaFileStub() {
149        JetClassOrObject outermostClassOrObject = getOutermostClassOrObject(classOrObject);
150        return CachedValuesManager.getManager(getProject()).getCachedValue(
151                outermostClassOrObject,
152                JAVA_API_STUB,
153                KotlinJavaFileStubProvider.createForDeclaredTopLevelClass(outermostClassOrObject),
154                        /*trackValue = */false);
155    }
156
157    @NotNull
158    private static JetClassOrObject getOutermostClassOrObject(@NotNull JetClassOrObject classOrObject) {
159        JetClassOrObject outermostClass = JetPsiUtil.getOutermostClassOrObject(classOrObject);
160        if (outermostClass == null) {
161            throw new IllegalStateException("Attempt to build a light class for a local class: " + classOrObject.getText());
162        }
163        else {
164            return outermostClass;
165        }
166    }
167
168    private final NullableLazyValue<PsiFile> _containingFile = new NullableLazyValue<PsiFile>() {
169        @Nullable
170        @Override
171        protected PsiFile compute() {
172            VirtualFile virtualFile = classOrObject.getContainingFile().getVirtualFile();
173            assert virtualFile != null : "No virtual file for " + classOrObject.getText();
174            return new ClsFileImpl((PsiManagerImpl) getManager(), new ClassFileViewProvider(getManager(), virtualFile)) {
175                @NotNull
176                @Override
177                public String getPackageName() {
178                    return JetPsiUtil.getFQName((JetFile) classOrObject.getContainingFile()).asString();
179                }
180
181                @NotNull
182                @Override
183                public PsiClassHolderFileStub getStub() {
184                    return getJavaFileStub();
185                }
186            };
187        }
188    };
189
190    @Override
191    public PsiFile getContainingFile() {
192        return _containingFile.getValue();
193    }
194
195    @NotNull
196    @Override
197    public PsiElement getNavigationElement() {
198        return classOrObject;
199    }
200
201    @Override
202    public boolean isEquivalentTo(PsiElement another) {
203        return another instanceof PsiClass && Comparing.equal(((PsiClass) another).getQualifiedName(), getQualifiedName());
204    }
205
206    @Override
207    public ItemPresentation getPresentation() {
208        return ItemPresentationProviders.getItemPresentation(this);
209    }
210
211    @Override
212    public Icon getElementIcon(int flags) {
213        throw new UnsupportedOperationException("This should be done byt JetIconProvider");
214    }
215
216    @Override
217    public boolean equals(Object o) {
218        if (this == o) return true;
219        if (o == null || getClass() != o.getClass()) return false;
220
221        KotlinLightClassForExplicitDeclaration aClass = (KotlinLightClassForExplicitDeclaration) o;
222
223        if (!classFqName.equals(aClass.classFqName)) return false;
224
225        return true;
226    }
227
228    @Override
229    public int hashCode() {
230        return classFqName.hashCode();
231    }
232
233    @Nullable
234    @Override
235    public PsiClass getContainingClass() {
236        if (classOrObject.getParent() == classOrObject.getContainingFile()) return null;
237        return super.getContainingClass();
238    }
239
240    @Nullable
241    @Override
242    public PsiElement getParent() {
243        if (classOrObject.getParent() == classOrObject.getContainingFile()) return getContainingFile();
244        return getContainingClass();
245    }
246
247    @Nullable
248    @Override
249    public PsiTypeParameterList getTypeParameterList() {
250        return typeParameterList.getValue();
251    }
252
253    @NotNull
254    @Override
255    public PsiTypeParameter[] getTypeParameters() {
256        PsiTypeParameterList typeParameterList = getTypeParameterList();
257        return typeParameterList == null ? PsiTypeParameter.EMPTY_ARRAY : typeParameterList.getTypeParameters();
258    }
259
260    @Nullable
261    @Override
262    public String getName() {
263        return classFqName.shortName().asString();
264    }
265
266    @Nullable
267    @Override
268    public String getQualifiedName() {
269        return classFqName.asString();
270    }
271
272    @NotNull
273    @Override
274    public PsiModifierList getModifierList() {
275        if (modifierList == null) {
276            modifierList = new LightModifierList(getManager(), JetLanguage.INSTANCE, computeModifiers());
277        }
278        return modifierList;
279    }
280
281    @NotNull
282    private String[] computeModifiers() {
283        boolean nestedClass = classOrObject.getParent() != classOrObject.getContainingFile();
284        Collection<String> psiModifiers = Sets.newHashSet();
285
286        // PUBLIC, PROTECTED, PRIVATE, ABSTRACT, FINAL
287        List<Pair<JetKeywordToken, String>> jetTokenToPsiModifier = Lists.newArrayList(
288                Pair.create(PUBLIC_KEYWORD, PsiModifier.PUBLIC),
289                Pair.create(INTERNAL_KEYWORD, PsiModifier.PUBLIC),
290                Pair.create(PROTECTED_KEYWORD, PsiModifier.PROTECTED),
291                Pair.create(FINAL_KEYWORD, PsiModifier.FINAL));
292
293        for (Pair<JetKeywordToken, String> tokenAndModifier : jetTokenToPsiModifier) {
294            if (classOrObject.hasModifier(tokenAndModifier.first)) {
295                psiModifiers.add(tokenAndModifier.second);
296            }
297        }
298
299        if (classOrObject.hasModifier(PRIVATE_KEYWORD)) {
300            // Top-level private class has PUBLIC visibility in Java
301            // Nested private class has PRIVATE visibility
302            psiModifiers.add(nestedClass ? PsiModifier.PRIVATE : PsiModifier.PUBLIC);
303        }
304
305        if (!psiModifiers.contains(PsiModifier.PRIVATE) && !psiModifiers.contains(PsiModifier.PROTECTED)) {
306            psiModifiers.add(PsiModifier.PUBLIC); // For internal (default) visibility
307        }
308
309
310        // FINAL
311        if (isAbstract(classOrObject)) {
312            psiModifiers.add(PsiModifier.ABSTRACT);
313        }
314        else if (!classOrObject.hasModifier(OPEN_KEYWORD)) {
315            psiModifiers.add(PsiModifier.FINAL);
316        }
317
318        if (nestedClass && !classOrObject.hasModifier(INNER_KEYWORD)) {
319            psiModifiers.add(PsiModifier.STATIC);
320        }
321
322        return psiModifiers.toArray(new String[psiModifiers.size()]);
323    }
324
325    private boolean isAbstract(@NotNull JetClassOrObject object) {
326        return object.hasModifier(ABSTRACT_KEYWORD) || isInterface();
327    }
328
329    @Override
330    public boolean hasModifierProperty(@NonNls @NotNull String name) {
331        return getModifierList().hasModifierProperty(name);
332    }
333
334    @Override
335    public boolean isDeprecated() {
336        JetModifierList jetModifierList = classOrObject.getModifierList();
337        if (jetModifierList == null) {
338            return false;
339        }
340
341        ClassDescriptor deprecatedAnnotation = KotlinBuiltIns.getInstance().getDeprecatedAnnotation();
342        String deprecatedName = deprecatedAnnotation.getName().asString();
343        FqNameUnsafe deprecatedFqName = DescriptorUtils.getFQName(deprecatedAnnotation);
344
345        for (JetAnnotationEntry annotationEntry : jetModifierList.getAnnotationEntries()) {
346            JetTypeReference typeReference = annotationEntry.getTypeReference();
347            if (typeReference == null) continue;
348
349            JetTypeElement typeElement = typeReference.getTypeElement();
350            if (!(typeElement instanceof JetUserType)) continue; // If it's not a user type, it's definitely not a ref to deprecated
351
352            FqName fqName = JetPsiUtil.toQualifiedName((JetUserType) typeElement);
353            if (fqName == null) continue;
354
355            if (deprecatedFqName.equals(fqName.toUnsafe())) return true;
356            if (deprecatedName.equals(fqName.asString())) return true;
357        }
358        return false;
359    }
360
361    @Override
362    public boolean isInterface() {
363        if (!(classOrObject instanceof JetClass)) return false;
364        JetClass jetClass = (JetClass) classOrObject;
365        return jetClass.isTrait() || jetClass.isAnnotation();
366    }
367
368    @Override
369    public boolean isAnnotationType() {
370        return classOrObject instanceof JetClass && ((JetClass) classOrObject).isAnnotation();
371    }
372
373    @Override
374    public boolean isEnum() {
375        return classOrObject instanceof JetClass && ((JetClass) classOrObject).isEnum();
376    }
377
378    @Override
379    public boolean hasTypeParameters() {
380        return classOrObject instanceof JetClass && !((JetClass) classOrObject).getTypeParameters().isEmpty();
381    }
382
383    @Override
384    public boolean isValid() {
385        return classOrObject.isValid();
386    }
387
388    @Override
389    public PsiElement setName(@NonNls @NotNull String name) throws IncorrectOperationException {
390        return super.setName(name); // TODO
391    }
392
393    @Override
394    public String toString() {
395        try {
396            return KotlinLightClass.class.getSimpleName() + ":" + getQualifiedName();
397        }
398        catch (Throwable e) {
399            return KotlinLightClass.class.getSimpleName() + ":" + e.toString();
400        }
401    }
402}