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.cli.jvm.compiler;
018    
019    import com.google.common.base.Predicate;
020    import com.google.common.collect.Collections2;
021    import com.intellij.openapi.project.Project;
022    import com.intellij.psi.PsiClass;
023    import com.intellij.psi.PsiElement;
024    import com.intellij.psi.PsiManager;
025    import com.intellij.psi.search.GlobalSearchScope;
026    import com.intellij.psi.search.PsiSearchScopeUtil;
027    import com.intellij.util.Function;
028    import com.intellij.util.SmartList;
029    import com.intellij.util.containers.ContainerUtil;
030    import org.jetbrains.annotations.NotNull;
031    import org.jetbrains.annotations.Nullable;
032    import org.jetbrains.annotations.TestOnly;
033    import org.jetbrains.kotlin.asJava.KotlinLightClassForExplicitDeclaration;
034    import org.jetbrains.kotlin.asJava.KotlinLightClassForPackage;
035    import org.jetbrains.kotlin.asJava.LightClassConstructionContext;
036    import org.jetbrains.kotlin.asJava.LightClassGenerationSupport;
037    import org.jetbrains.kotlin.descriptors.ClassDescriptor;
038    import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
039    import org.jetbrains.kotlin.descriptors.ModuleDescriptor;
040    import org.jetbrains.kotlin.descriptors.PackageViewDescriptor;
041    import org.jetbrains.kotlin.load.kotlin.PackagePartClassUtils;
042    import org.jetbrains.kotlin.name.FqName;
043    import org.jetbrains.kotlin.psi.*;
044    import org.jetbrains.kotlin.resolve.*;
045    import org.jetbrains.kotlin.resolve.lazy.KotlinCodeAnalyzer;
046    import org.jetbrains.kotlin.resolve.lazy.ResolveSessionUtils;
047    import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter;
048    import org.jetbrains.kotlin.resolve.scopes.JetScope;
049    import org.jetbrains.kotlin.util.slicedMap.ReadOnlySlice;
050    import org.jetbrains.kotlin.util.slicedMap.WritableSlice;
051    import org.jetbrains.kotlin.utils.UtilsPackage;
052    
053    import java.util.Collection;
054    import java.util.Collections;
055    import java.util.List;
056    
057    /**
058     * This class solves the problem of interdependency between analyzing Kotlin code and generating JetLightClasses
059     *
060     * Consider the following example:
061     *
062     * KClass.kt refers to JClass.java and vice versa
063     *
064     * To analyze KClass.kt we need to load descriptors from JClass.java, and to do that we need a JetLightClass instance for KClass,
065     * which can only be constructed when the structure of KClass is known.
066     *
067     * To mitigate this, CliLightClassGenerationSupport hold a trace that is shared between the analyzer and JetLightClasses
068     */
069    public class CliLightClassGenerationSupport extends LightClassGenerationSupport implements CodeAnalyzerInitializer {
070        private final PsiManager psiManager;
071        private BindingContext bindingContext = null;
072        private ModuleDescriptor module = null;
073    
074        public CliLightClassGenerationSupport(@NotNull Project project) {
075            this.psiManager = PsiManager.getInstance(project);
076        }
077    
078        @Override
079        public void initialize(@NotNull BindingTrace trace, @NotNull ModuleDescriptor module, @Nullable KotlinCodeAnalyzer analyzer) {
080            this.bindingContext = trace.getBindingContext();
081            this.module = module;
082    
083            if (!(trace instanceof CliBindingTrace)) {
084                throw new IllegalArgumentException("Shared trace is expected to be subclass of " + CliBindingTrace.class.getSimpleName() + " class");
085            }
086    
087            ((CliBindingTrace) trace).setKotlinCodeAnalyzer(analyzer);
088        }
089    
090        @NotNull
091        private BindingContext getBindingContext() {
092            assert bindingContext != null : "Call initialize() first";
093            return bindingContext;
094        }
095    
096        @NotNull
097        private ModuleDescriptor getModule() {
098            assert module != null : "Call initialize() first";
099            return module;
100        }
101    
102        @NotNull
103        @Override
104        public LightClassConstructionContext getContextForPackage(@NotNull Collection<JetFile> files) {
105            return getContext();
106        }
107    
108        @NotNull
109        @Override
110        public LightClassConstructionContext getContextForClassOrObject(@NotNull JetClassOrObject classOrObject) {
111            return getContext();
112        }
113    
114        @NotNull
115        private LightClassConstructionContext getContext() {
116            return new LightClassConstructionContext(bindingContext, getModule());
117        }
118    
119        @NotNull
120        @Override
121        public Collection<JetClassOrObject> findClassOrObjectDeclarations(@NotNull FqName fqName, @NotNull final GlobalSearchScope searchScope) {
122            Collection<ClassDescriptor> classDescriptors = ResolveSessionUtils.getClassDescriptorsByFqName(getModule(), fqName);
123    
124            return ContainerUtil.mapNotNull(classDescriptors, new Function<ClassDescriptor, JetClassOrObject>() {
125                @Override
126                public JetClassOrObject fun(ClassDescriptor descriptor) {
127                    PsiElement element = DescriptorToSourceUtils.getSourceFromDescriptor(descriptor);
128                    if (element instanceof JetClassOrObject && PsiSearchScopeUtil.isInScope(searchScope, element)) {
129                        return (JetClassOrObject) element;
130                    }
131                    return null;
132                }
133            });
134        }
135    
136        @NotNull
137        @Override
138        public Collection<JetFile> findFilesForPackage(@NotNull FqName fqName, @NotNull final GlobalSearchScope searchScope) {
139            Collection<JetFile> files = getBindingContext().get(BindingContext.PACKAGE_TO_FILES, fqName);
140            if (files != null) {
141                return Collections2.filter(files, new Predicate<JetFile>() {
142                    @Override
143                    public boolean apply(JetFile input) {
144                        return PsiSearchScopeUtil.isInScope(searchScope, input);
145                    }
146                });
147            }
148            return Collections.emptyList();
149        }
150    
151        @NotNull
152        @Override
153        public Collection<JetClassOrObject> findClassOrObjectDeclarationsInPackage(
154                @NotNull FqName packageFqName, @NotNull GlobalSearchScope searchScope
155        ) {
156            Collection<JetFile> files = findFilesForPackage(packageFqName, searchScope);
157            List<JetClassOrObject> result = new SmartList<JetClassOrObject>();
158            for (JetFile file : files) {
159                for (JetDeclaration declaration : file.getDeclarations()) {
160                    if (declaration instanceof JetClassOrObject) {
161                        result.add((JetClassOrObject) declaration);
162                    }
163                }
164            }
165            return result;
166        }
167    
168        @Override
169        public boolean packageExists(@NotNull FqName fqName, @NotNull GlobalSearchScope scope) {
170            return getModule().getPackage(fqName) != null;
171        }
172    
173        @NotNull
174        @Override
175        public Collection<FqName> getSubPackages(@NotNull FqName fqn, @NotNull GlobalSearchScope scope) {
176            PackageViewDescriptor packageView = getModule().getPackage(fqn);
177            if (packageView == null) return Collections.emptyList();
178    
179            Collection<DeclarationDescriptor> members = packageView.getMemberScope().getDescriptors(DescriptorKindFilter.PACKAGES, JetScope.ALL_NAME_FILTER);
180            return ContainerUtil.mapNotNull(members, new Function<DeclarationDescriptor, FqName>() {
181                @Override
182                public FqName fun(DeclarationDescriptor member) {
183                    if (member instanceof PackageViewDescriptor) {
184                        return ((PackageViewDescriptor) member).getFqName();
185                    }
186                    return null;
187                }
188            });
189        }
190    
191        @Nullable
192        @Override
193        public PsiClass getPsiClass(@NotNull JetClassOrObject classOrObject) {
194            return KotlinLightClassForExplicitDeclaration.create(psiManager, classOrObject);
195        }
196    
197        @NotNull
198        @Override
199        public Collection<PsiClass> getPackageClasses(@NotNull FqName packageFqName, @NotNull GlobalSearchScope scope) {
200            Collection<JetFile> filesInPackage = findFilesForPackage(packageFqName, scope);
201    
202            List<JetFile> filesWithCallables = PackagePartClassUtils.getPackageFilesWithCallables(filesInPackage);
203            if (filesWithCallables.isEmpty()) return Collections.emptyList();
204    
205            //noinspection RedundantTypeArguments
206            return UtilsPackage.<PsiClass>emptyOrSingletonList(KotlinLightClassForPackage.Factory.create(psiManager, packageFqName, scope, filesWithCallables));
207        }
208    
209        @NotNull
210        @Override
211        public BindingTraceContext createTrace() {
212            return new NoScopeRecordCliBindingTrace();
213        }
214    
215        public static class NoScopeRecordCliBindingTrace extends CliBindingTrace {
216            @Override
217            public <K, V> void record(WritableSlice<K, V> slice, K key, V value) {
218                if (slice == BindingContext.RESOLUTION_SCOPE || slice == BindingContext.TYPE_RESOLUTION_SCOPE) {
219                    // In the compiler there's no need to keep scopes
220                    return;
221                }
222                super.record(slice, key, value);
223            }
224    
225            @Override
226            public String toString() {
227                return NoScopeRecordCliBindingTrace.class.getName();
228            }
229        }
230    
231        public static class CliBindingTrace extends BindingTraceContext {
232            private KotlinCodeAnalyzer kotlinCodeAnalyzer;
233    
234            @TestOnly
235            public CliBindingTrace() {
236            }
237    
238            @Override
239            public String toString() {
240                return CliBindingTrace.class.getName();
241            }
242    
243            public void setKotlinCodeAnalyzer(KotlinCodeAnalyzer kotlinCodeAnalyzer) {
244                this.kotlinCodeAnalyzer = kotlinCodeAnalyzer;
245            }
246    
247            @Override
248            public <K, V> V get(ReadOnlySlice<K, V> slice, K key) {
249                V value = super.get(slice, key);
250    
251                if (value == null) {
252                    if (BindingContext.FUNCTION == slice || BindingContext.VARIABLE == slice) {
253                        if (key instanceof JetDeclaration) {
254                            JetDeclaration jetDeclaration = (JetDeclaration) key;
255                            if (!JetPsiUtil.isLocal(jetDeclaration)) {
256                                kotlinCodeAnalyzer.resolveToDescriptor(jetDeclaration);
257                            }
258                        }
259                    }
260    
261                    return super.get(slice, key);
262                }
263    
264                return value;
265            }
266        }
267    }