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