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