001    /*
002     * Copyright 2010-2014 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.Sets;
020    import com.intellij.navigation.ItemPresentation;
021    import com.intellij.navigation.ItemPresentationProviders;
022    import com.intellij.openapi.components.ServiceManager;
023    import com.intellij.openapi.project.Project;
024    import com.intellij.openapi.util.Comparing;
025    import com.intellij.psi.*;
026    import com.intellij.psi.impl.light.LightEmptyImplementsList;
027    import com.intellij.psi.impl.light.LightModifierList;
028    import com.intellij.psi.javadoc.PsiDocComment;
029    import com.intellij.psi.search.GlobalSearchScope;
030    import com.intellij.psi.util.CachedValue;
031    import com.intellij.psi.util.CachedValueProvider;
032    import com.intellij.psi.util.CachedValuesManager;
033    import com.intellij.psi.util.PsiModificationTracker;
034    import com.intellij.util.containers.SLRUCache;
035    import org.jetbrains.annotations.NonNls;
036    import org.jetbrains.annotations.NotNull;
037    import org.jetbrains.annotations.Nullable;
038    import org.jetbrains.jet.lang.psi.JetClassOrObject;
039    import org.jetbrains.jet.lang.psi.JetFile;
040    import org.jetbrains.jet.lang.resolve.java.PackageClassUtils;
041    import org.jetbrains.jet.lang.resolve.java.jetAsJava.JetJavaMirrorMarker;
042    import org.jetbrains.jet.lang.resolve.name.FqName;
043    import org.jetbrains.jet.plugin.JetLanguage;
044    
045    import javax.swing.*;
046    import java.util.Collection;
047    import java.util.Collections;
048    import java.util.List;
049    
050    public class KotlinLightClassForPackage extends KotlinWrappingLightClass implements JetJavaMirrorMarker {
051    
052        public static class FileStubCache {
053    
054            @NotNull
055            public static FileStubCache getInstance(@NotNull Project project) {
056                return ServiceManager.getService(project, FileStubCache.class);
057            }
058    
059            private static final class Key {
060                private final FqName fqName;
061                private final GlobalSearchScope searchScope;
062    
063                private Key(
064                        @NotNull FqName fqName,
065                        @NotNull GlobalSearchScope searchScope
066                ) {
067                    this.fqName = fqName;
068                    this.searchScope = searchScope;
069                }
070    
071                @Override
072                public boolean equals(Object o) {
073                    if (this == o) return true;
074                    if (o == null || getClass() != o.getClass()) return false;
075    
076                    Key key = (Key) o;
077    
078                    if (!fqName.equals(key.fqName)) return false;
079                    if (!searchScope.equals(key.searchScope)) return false;
080    
081                    return true;
082                }
083    
084                @Override
085                public int hashCode() {
086                    int result = fqName.hashCode();
087                    result = 31 * result + searchScope.hashCode();
088                    return result;
089                }
090            }
091    
092            private final class CacheData {
093    
094                private final SLRUCache<Key, CachedValue<KotlinPackageLightClassData>> cache = new SLRUCache<Key, CachedValue<KotlinPackageLightClassData>>(20, 30) {
095                    @NotNull
096                    @Override
097                    public CachedValue<KotlinPackageLightClassData> createValue(Key key) {
098                        KotlinJavaFileStubProvider<KotlinPackageLightClassData> stubProvider =
099                                KotlinJavaFileStubProvider.createForPackageClass(project, key.fqName, key.searchScope);
100                        return CachedValuesManager.getManager(project).createCachedValue(stubProvider, /*trackValue = */false);
101                    }
102                };
103            }
104    
105            private final Project project;
106            private final CachedValue<CacheData> cachedValue;
107    
108            public FileStubCache(@NotNull Project project) {
109                this.project = project;
110                this.cachedValue = CachedValuesManager.getManager(project).createCachedValue(
111                        new CachedValueProvider<CacheData>() {
112                            @Nullable
113                            @Override
114                            public Result<CacheData> compute() {
115                                return Result.create(new CacheData(), PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT);
116                            }
117                        },
118                        /*trackValue = */ false
119                );
120            }
121    
122            @NotNull
123            public CachedValue<KotlinPackageLightClassData> get(
124                    @NotNull FqName qualifiedName,
125                    @NotNull GlobalSearchScope searchScope
126            ) {
127                synchronized (cachedValue) {
128                    return cachedValue.getValue().cache.get(new Key(qualifiedName, searchScope));
129                }
130            }
131    
132        }
133    
134        private final FqName packageFqName;
135        private final FqName packageClassFqName; // derived from packageFqName
136        private final GlobalSearchScope searchScope;
137        private final Collection<JetFile> files;
138        private final int hashCode;
139        private final CachedValue<KotlinPackageLightClassData> lightClassDataCache;
140        private final PsiModifierList modifierList;
141        private final LightEmptyImplementsList implementsList;
142    
143        private KotlinLightClassForPackage(
144                @NotNull PsiManager manager,
145                @NotNull FqName packageFqName,
146                @NotNull GlobalSearchScope searchScope,
147                @NotNull Collection<JetFile> files
148        ) {
149            super(manager, JetLanguage.INSTANCE);
150            this.modifierList = new LightModifierList(manager, JetLanguage.INSTANCE, PsiModifier.PUBLIC, PsiModifier.FINAL);
151            this.implementsList = new LightEmptyImplementsList(manager);
152            this.packageFqName = packageFqName;
153            this.packageClassFqName = PackageClassUtils.getPackageClassFqName(packageFqName);
154            this.searchScope = searchScope;
155            assert !files.isEmpty() : "No files for package " + packageFqName;
156            this.files = Sets.newHashSet(files); // needed for hashCode
157            this.hashCode = computeHashCode();
158            this.lightClassDataCache = FileStubCache.getInstance(getProject()).get(packageFqName, searchScope);
159        }
160    
161        @Nullable
162        public static KotlinLightClassForPackage create(
163                @NotNull PsiManager manager,
164                @NotNull FqName qualifiedName,
165                @NotNull GlobalSearchScope searchScope,
166                @NotNull Collection<JetFile> files // this is redundant, but computing it multiple times is costly
167        ) {
168            for (JetFile file : files) {
169                if (LightClassUtil.belongsToKotlinBuiltIns(file)) return null;
170            }
171            return new KotlinLightClassForPackage(manager, qualifiedName, searchScope, files);
172        }
173    
174        private static boolean allValid(Collection<JetFile> files) {
175            for (JetFile file : files) {
176                if (!file.isValid()) return false;
177            }
178            return true;
179        }
180    
181        @Nullable
182        @Override
183        public JetClassOrObject getOrigin() {
184            return null;
185        }
186    
187        @Nullable
188        @Override
189        public PsiModifierList getModifierList() {
190            return modifierList;
191        }
192    
193        @Override
194        public boolean hasModifierProperty(@NonNls @NotNull String name) {
195            return modifierList.hasModifierProperty(name);
196        }
197    
198        @Override
199        public boolean isDeprecated() {
200            return false;
201        }
202    
203        @Override
204        public boolean isInterface() {
205            return false;
206        }
207    
208        @Override
209        public boolean isAnnotationType() {
210            return false;
211        }
212    
213        @Override
214        public boolean isEnum() {
215            return false;
216        }
217    
218        @Nullable
219        @Override
220        public PsiClass getContainingClass() {
221            return null;
222        }
223    
224        @Override
225        public boolean hasTypeParameters() {
226            return false;
227        }
228    
229        @NotNull
230        @Override
231        public PsiTypeParameter[] getTypeParameters() {
232            return PsiTypeParameter.EMPTY_ARRAY;
233        }
234    
235        @Nullable
236        @Override
237        public PsiTypeParameterList getTypeParameterList() {
238            return null;
239        }
240    
241        @Nullable
242        @Override
243        public PsiDocComment getDocComment() {
244            return null;
245        }
246    
247        @Nullable
248        @Override
249        public PsiReferenceList getImplementsList() {
250            return implementsList;
251        }
252    
253        @NotNull
254        @Override
255        public PsiClassType[] getImplementsListTypes() {
256            return PsiClassType.EMPTY_ARRAY;
257        }
258    
259        @Nullable
260        @Override
261        public PsiReferenceList getExtendsList() {
262            // TODO: Find a way to return just Object
263            return super.getExtendsList();
264        }
265    
266        @NotNull
267        @Override
268        public PsiClassType[] getExtendsListTypes() {
269            // TODO see getExtendsList()
270            return super.getExtendsListTypes();
271        }
272    
273        @Nullable
274        @Override
275        public PsiClass getSuperClass() {
276            // TODO see getExtendsList()
277            return super.getSuperClass();
278        }
279    
280        @NotNull
281        @Override
282        public PsiClass[] getSupers() {
283            // TODO see getExtendsList()
284            return super.getSupers();
285        }
286    
287        @NotNull
288        @Override
289        public PsiClassType[] getSuperTypes() {
290            // TODO see getExtendsList()
291            return super.getSuperTypes();
292        }
293    
294        @Override
295        public PsiClass[] getInterfaces() {
296            return PsiClass.EMPTY_ARRAY;
297        }
298    
299        @NotNull
300        @Override
301        public PsiClass[] getInnerClasses() {
302            return PsiClass.EMPTY_ARRAY;
303        }
304    
305        @NotNull
306        @Override
307        public List<PsiClass> getOwnInnerClasses() {
308            return Collections.emptyList();
309        }
310    
311        @NotNull
312        @Override
313        public PsiClass[] getAllInnerClasses() {
314            return PsiClass.EMPTY_ARRAY;
315        }
316    
317        @NotNull
318        @Override
319        public PsiClassInitializer[] getInitializers() {
320            return PsiClassInitializer.EMPTY_ARRAY;
321        }
322    
323        @Nullable
324        @Override
325        public PsiClass findInnerClassByName(@NonNls String name, boolean checkBases) {
326            return null;
327        }
328    
329        @NotNull
330        @Override
331        public FqName getFqName() {
332            return packageClassFqName;
333        }
334    
335        @Nullable
336        @Override
337        public String getName() {
338            return packageClassFqName.shortName().asString();
339        }
340    
341        @Nullable
342        @Override
343        public String getQualifiedName() {
344            return packageClassFqName.asString();
345        }
346    
347        @Override
348        public boolean isValid() {
349            return allValid(files);
350        }
351    
352        @NotNull
353        @Override
354        public PsiElement copy() {
355            return new KotlinLightClassForPackage(getManager(), packageFqName, searchScope, files);
356        }
357    
358        @NotNull
359        @Override
360        public PsiClass getDelegate() {
361            PsiClass psiClass = LightClassUtil.findClass(packageClassFqName, lightClassDataCache.getValue().getJavaFileStub());
362            if (psiClass == null) {
363                throw new IllegalStateException("Package class was not found " + packageFqName);
364            }
365            return psiClass;
366        }
367    
368        @NotNull
369        @Override
370        public PsiElement getNavigationElement() {
371            return files.iterator().next();
372        }
373    
374        @Override
375        public boolean isEquivalentTo(PsiElement another) {
376            return another instanceof PsiClass && Comparing.equal(((PsiClass) another).getQualifiedName(), getQualifiedName());
377        }
378    
379        @Override
380        public ItemPresentation getPresentation() {
381            return ItemPresentationProviders.getItemPresentation(this);
382        }
383    
384        @Override
385        public Icon getElementIcon(int flags) {
386            throw new UnsupportedOperationException("This should be done byt JetIconProvider");
387        }
388    
389        @Override
390        public int hashCode() {
391            return hashCode;
392        }
393    
394        private int computeHashCode() {
395            int result = getManager().hashCode();
396            result = 31 * result + files.hashCode();
397            result = 31 * result + packageFqName.hashCode();
398            return result;
399        }
400    
401        @Override
402        public boolean equals(Object obj) {
403            if (this == obj) return true;
404            if (obj == null || getClass() != obj.getClass()) {
405                return false;
406            }
407    
408            KotlinLightClassForPackage lightClass = (KotlinLightClassForPackage) obj;
409    
410            if (this.hashCode != lightClass.hashCode) return false;
411            if (getManager() != lightClass.getManager()) return false;
412            if (!files.equals(lightClass.files)) return false;
413            if (!packageFqName.equals(lightClass.packageFqName)) return false;
414    
415            return true;
416        }
417    
418        @Override
419        public String toString() {
420            try {
421                return KotlinLightClassForPackage.class.getSimpleName() + ":" + getQualifiedName();
422            }
423            catch (Throwable e) {
424                return KotlinLightClassForPackage.class.getSimpleName() + ":" + e.toString();
425            }
426        }
427    }