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.JetJavaMirrorMarker;
047    import org.jetbrains.jet.lang.resolve.java.JvmClassName;
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            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    }