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.cli.jvm.compiler;
018    
019    import com.google.common.base.Predicate;
020    import com.google.common.collect.Collections2;
021    import com.intellij.openapi.components.ServiceManager;
022    import com.intellij.openapi.project.Project;
023    import com.intellij.psi.PsiClass;
024    import com.intellij.psi.PsiElement;
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.jet.asJava.KotlinLightClassForExplicitDeclaration;
034    import org.jetbrains.jet.asJava.LightClassConstructionContext;
035    import org.jetbrains.jet.asJava.LightClassGenerationSupport;
036    import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
037    import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
038    import org.jetbrains.jet.lang.descriptors.PackageViewDescriptor;
039    import org.jetbrains.jet.lang.descriptors.impl.ModuleDescriptorImpl;
040    import org.jetbrains.jet.lang.psi.*;
041    import org.jetbrains.jet.lang.resolve.*;
042    import org.jetbrains.jet.lang.resolve.java.TopDownAnalyzerFacadeForJVM;
043    import org.jetbrains.jet.lang.resolve.java.JvmAbi;
044    import org.jetbrains.jet.lang.resolve.name.FqName;
045    import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
046    import org.jetbrains.jet.util.slicedmap.WritableSlice;
047    
048    import java.util.*;
049    
050    /**
051     * This class solves the problem of interdependency between analyzing Kotlin code and generating JetLightClasses
052     *
053     * Consider the following example:
054     *
055     * KClass.kt refers to JClass.java and vice versa
056     *
057     * To analyze KClass.kt we need to load descriptors from JClass.java, and to do that we need a JetLightClass instance for KClass,
058     * which can only be constructed when the structure of KClass is known.
059     *
060     * To mitigate this, CliLightClassGenerationSupport hold a trace that is shared between the analyzer and JetLightClasses
061     */
062    public class CliLightClassGenerationSupport extends LightClassGenerationSupport {
063    
064        public static CliLightClassGenerationSupport getInstanceForCli(@NotNull Project project) {
065            return ServiceManager.getService(project, CliLightClassGenerationSupport.class);
066        }
067    
068        private BindingTrace trace;
069        private ModuleDescriptorImpl module;
070    
071        public CliLightClassGenerationSupport() {
072        }
073    
074        @NotNull
075        public BindingTrace getTrace() {
076            if (trace == null) {
077                trace = new BindingTraceContextWithoutScopeRecording();
078            }
079            return trace;
080        }
081    
082        @NotNull
083        public ModuleDescriptorImpl newModule() {
084            assert this.module == null : "module already configured: " + module;
085            module = TopDownAnalyzerFacadeForJVM.createJavaModule("<shared-module-for-cli-light-classes>");
086            module.addDependencyOnModule(module);
087            module.addDependencyOnModule(KotlinBuiltIns.getInstance().getBuiltInsModule());
088            module.seal();
089            return module;
090        }
091    
092        @NotNull
093        private ModuleDescriptorImpl getModule() {
094            if (module == null) {
095               return newModule();
096            }
097            return module;
098        }
099    
100        @TestOnly
101        @Nullable
102        public ModuleDescriptorImpl getLightClassModule() {
103            return module;
104        }
105    
106        @TestOnly
107        public void setModule(@NotNull ModuleDescriptorImpl module) {
108            assert this.module == null : "module already configured: " + module;
109            this.module = module;
110        }
111    
112        @TestOnly
113        public void newBindingTrace() {
114            trace = null;
115            module = null;
116        }
117    
118        @NotNull
119        @Override
120        public LightClassConstructionContext getContextForPackage(@NotNull Collection<JetFile> files) {
121            return getContext();
122        }
123    
124        @NotNull
125        @Override
126        public LightClassConstructionContext getContextForClassOrObject(@NotNull JetClassOrObject classOrObject) {
127            return getContext();
128        }
129    
130        @NotNull
131        private LightClassConstructionContext getContext() {
132            return new LightClassConstructionContext(getTrace().getBindingContext(), getModule());
133        }
134    
135        @NotNull
136        @Override
137        public Collection<JetClassOrObject> findClassOrObjectDeclarations(@NotNull FqName fqName, @NotNull GlobalSearchScope searchScope) {
138            ClassDescriptor classDescriptor = getTrace().get(BindingContext.FQNAME_TO_CLASS_DESCRIPTOR, fqName.toUnsafe());
139            if (classDescriptor != null) {
140                PsiElement element = DescriptorToSourceUtils.classDescriptorToDeclaration(classDescriptor);
141                if (element != null && PsiSearchScopeUtil.isInScope(searchScope, element)) {
142                    return Collections.singletonList((JetClassOrObject) element);
143                }
144            }
145    
146            if (JvmAbi.isClassObjectFqName(fqName)) {
147                Collection<JetClassOrObject> parentClasses = findClassOrObjectDeclarations(fqName.parent(), searchScope);
148                return ContainerUtil.mapNotNull(parentClasses,
149                                                new Function<JetClassOrObject, JetClassOrObject>() {
150                                                    @Override
151                                                    public JetClassOrObject fun(JetClassOrObject classOrObject) {
152                                                        if (classOrObject instanceof JetClass) {
153                                                            JetClass jetClass = (JetClass) classOrObject;
154                                                            JetClassObject classObject = jetClass.getClassObject();
155                                                            if (classObject != null) {
156                                                                return classObject.getObjectDeclaration();
157                                                            }
158                                                        }
159                                                        return null;
160                                                    }
161                                                });
162            }
163    
164            return Collections.emptyList();
165        }
166    
167        @NotNull
168        @Override
169        public Collection<JetFile> findFilesForPackage(@NotNull FqName fqName, @NotNull final GlobalSearchScope searchScope) {
170            Collection<JetFile> files = getTrace().get(BindingContext.PACKAGE_TO_FILES, fqName);
171            if (files != null) {
172                return Collections2.filter(files, new Predicate<JetFile>() {
173                    @Override
174                    public boolean apply(JetFile input) {
175                        return PsiSearchScopeUtil.isInScope(searchScope, input);
176                    }
177                });
178            }
179            return Collections.emptyList();
180        }
181    
182        @NotNull
183        @Override
184        public Collection<JetClassOrObject> findClassOrObjectDeclarationsInPackage(
185                @NotNull FqName packageFqName, @NotNull GlobalSearchScope searchScope
186        ) {
187            Collection<JetFile> files = findFilesForPackage(packageFqName, searchScope);
188            List<JetClassOrObject> result = new SmartList<JetClassOrObject>();
189            for (JetFile file : files) {
190                for (JetDeclaration declaration : file.getDeclarations()) {
191                    if (declaration instanceof JetClassOrObject) {
192                        result.add((JetClassOrObject) declaration);
193                    }
194                }
195            }
196            return result;
197        }
198    
199        @NotNull
200        @Override
201        public List<KotlinLightPackageClassInfo> findPackageClassesInfos(
202                @NotNull FqName fqName, @NotNull GlobalSearchScope wholeScope
203        ) {
204            return Collections.singletonList(new KotlinLightPackageClassInfo(findFilesForPackage(fqName, wholeScope), wholeScope));
205        }
206    
207        @Override
208        public boolean packageExists(@NotNull FqName fqName, @NotNull GlobalSearchScope scope) {
209            return getModule().getPackage(fqName) != null;
210        }
211    
212        @NotNull
213        @Override
214        public Collection<FqName> getSubPackages(@NotNull FqName fqn, @NotNull GlobalSearchScope scope) {
215            PackageViewDescriptor packageView = getModule().getPackage(fqn);
216            if (packageView == null) return Collections.emptyList();
217    
218            Collection<DeclarationDescriptor> members = packageView.getMemberScope().getAllDescriptors();
219            return ContainerUtil.mapNotNull(members, new Function<DeclarationDescriptor, FqName>() {
220                @Override
221                public FqName fun(DeclarationDescriptor member) {
222                    if (member instanceof PackageViewDescriptor) {
223                        return ((PackageViewDescriptor) member).getFqName();
224                    }
225                    return null;
226                }
227            });
228        }
229    
230        @Nullable
231        @Override
232        public PsiClass getPsiClass(@NotNull JetClassOrObject classOrObject) {
233            return KotlinLightClassForExplicitDeclaration.create(classOrObject.getManager(), classOrObject);
234        }
235    
236        public static class BindingTraceContextWithoutScopeRecording extends BindingTraceContext {
237            @Override
238            public <K, V> void record(WritableSlice<K, V> slice, K key, V value) {
239                if (slice == BindingContext.RESOLUTION_SCOPE || slice == BindingContext.TYPE_RESOLUTION_SCOPE) {
240                    // In the compiler there's no need to keep scopes
241                    return;
242                }
243                super.record(slice, key, value);
244            }
245    
246            @Override
247            public String toString() {
248                return "Filtering trace for the CLI compiler: does not save scopes";
249            }
250        }
251    }