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