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