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.Lists;
020    import com.intellij.openapi.diagnostic.Logger;
021    import com.intellij.openapi.progress.ProcessCanceledException;
022    import com.intellij.openapi.project.Project;
023    import com.intellij.openapi.util.SystemInfo;
024    import com.intellij.openapi.vfs.VirtualFile;
025    import com.intellij.psi.ClassFileViewProvider;
026    import com.intellij.psi.PsiElement;
027    import com.intellij.psi.PsiFile;
028    import com.intellij.psi.PsiManager;
029    import com.intellij.psi.impl.PsiManagerImpl;
030    import com.intellij.psi.impl.compiled.ClsFileImpl;
031    import com.intellij.psi.impl.java.stubs.PsiJavaFileStub;
032    import com.intellij.psi.impl.java.stubs.impl.PsiJavaFileStubImpl;
033    import com.intellij.psi.search.GlobalSearchScope;
034    import com.intellij.psi.stubs.PsiClassHolderFileStub;
035    import com.intellij.psi.stubs.StubElement;
036    import com.intellij.psi.util.CachedValueProvider;
037    import com.intellij.psi.util.PsiModificationTracker;
038    import com.intellij.psi.util.PsiTreeUtil;
039    import com.intellij.testFramework.LightVirtualFile;
040    import com.intellij.util.containers.ContainerUtil;
041    import com.intellij.util.containers.Stack;
042    import org.jetbrains.annotations.NotNull;
043    import org.jetbrains.annotations.Nullable;
044    import org.jetbrains.kotlin.codegen.CompilationErrorHandler;
045    import org.jetbrains.kotlin.codegen.KotlinCodegenFacade;
046    import org.jetbrains.kotlin.codegen.PackageCodegen;
047    import org.jetbrains.kotlin.codegen.binding.CodegenBinding;
048    import org.jetbrains.kotlin.codegen.state.GenerationState;
049    import org.jetbrains.kotlin.codegen.state.Progress;
050    import org.jetbrains.kotlin.descriptors.ClassDescriptor;
051    import org.jetbrains.kotlin.name.FqName;
052    import org.jetbrains.kotlin.psi.JetClassOrObject;
053    import org.jetbrains.kotlin.psi.JetFile;
054    import org.jetbrains.kotlin.psi.JetPsiUtil;
055    import org.jetbrains.kotlin.psi.JetScript;
056    import org.jetbrains.kotlin.resolve.BindingContext;
057    import org.jetbrains.kotlin.resolve.BindingTraceContext;
058    import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics;
059    import org.jetbrains.kotlin.resolve.jvm.JvmClassName;
060    import org.jetbrains.org.objectweb.asm.Type;
061    
062    import java.util.Collection;
063    import java.util.Collections;
064    import java.util.Map;
065    
066    import static org.jetbrains.kotlin.resolve.DescriptorToSourceUtils.descriptorToDeclaration;
067    
068    public class KotlinJavaFileStubProvider<T extends WithFileStubAndExtraDiagnostics> implements CachedValueProvider<T> {
069    
070        @NotNull
071        public static KotlinJavaFileStubProvider<KotlinPackageLightClassData> createForPackageClass(
072                @NotNull final Project project,
073                @NotNull final FqName packageFqName,
074                @NotNull final GlobalSearchScope searchScope
075        ) {
076            return new KotlinJavaFileStubProvider<KotlinPackageLightClassData>(
077                    project,
078                    false,
079                    new StubGenerationStrategy<KotlinPackageLightClassData>() {
080                        @NotNull
081                        @Override
082                        public LightClassConstructionContext getContext(@NotNull Collection<JetFile> files) {
083                            return LightClassGenerationSupport.getInstance(project).getContextForPackage(files);
084                        }
085    
086                        @NotNull
087                        @Override
088                        public Collection<JetFile> getFiles() {
089                            // Don't memoize this, it can be called again after an out-of-code-block modification occurs,
090                            // and the set of files changes
091                            return LightClassGenerationSupport.getInstance(project).findFilesForPackage(packageFqName, searchScope);
092                        }
093    
094                        @NotNull
095                        @Override
096                        public KotlinPackageLightClassData createLightClassData(
097                                PsiJavaFileStub javaFileStub,
098                                BindingContext bindingContext,
099                                Diagnostics extraDiagnostics
100                        ) {
101                            return new KotlinPackageLightClassData(javaFileStub, extraDiagnostics);
102                        }
103    
104                        @NotNull
105                        @Override
106                        public FqName getPackageFqName() {
107                            return packageFqName;
108                        }
109    
110                        @Override
111                        public GenerationState.GenerateClassFilter getGenerateClassFilter() {
112                            return new GenerationState.GenerateClassFilter() {
113                                @Override
114                                public boolean shouldProcessClass(JetClassOrObject classOrObject) {
115                                    // Top-level classes and such should not be generated for performance reasons.
116                                    // Local classes in top-level functions must still be generated
117                                    return JetPsiUtil.isLocal(classOrObject);
118                                }
119    
120                                @Override
121                                public boolean shouldProcessScript(JetScript script) {
122                                    // Scripts yield top-level classes, and should not be generated
123                                    return false;
124                                }
125                            };
126                        }
127    
128                        @Override
129                        public void generate(@NotNull GenerationState state, @NotNull Collection<JetFile> files) {
130                            PackageCodegen codegen = state.getFactory().forPackage(packageFqName, files);
131                            codegen.generate(CompilationErrorHandler.THROW_EXCEPTION);
132                            state.getFactory().asList();
133                        }
134    
135                        @Override
136                        public String toString() {
137                            return StubGenerationStrategy.class.getName() + " for package class";
138                        }
139                    }
140            );
141        }
142    
143        @NotNull
144        public static KotlinJavaFileStubProvider<OutermostKotlinClassLightClassData> createForDeclaredClass(@NotNull final JetClassOrObject classOrObject) {
145            return new KotlinJavaFileStubProvider<OutermostKotlinClassLightClassData>(
146                    classOrObject.getProject(),
147                    classOrObject.isLocal(),
148                    new StubGenerationStrategy<OutermostKotlinClassLightClassData>() {
149                        private JetFile getFile() {
150                            return classOrObject.getContainingJetFile();
151                        }
152    
153                        @NotNull
154                        @Override
155                        public LightClassConstructionContext getContext(@NotNull Collection<JetFile> files) {
156                            return LightClassGenerationSupport.getInstance(classOrObject.getProject()).getContextForClassOrObject(classOrObject);
157                        }
158    
159                        @NotNull
160                        @Override
161                        public OutermostKotlinClassLightClassData createLightClassData(
162                                PsiJavaFileStub javaFileStub,
163                                BindingContext bindingContext,
164                                Diagnostics extraDiagnostics
165                        ) {
166                            ClassDescriptor classDescriptor = bindingContext.get(BindingContext.CLASS, classOrObject);
167                            if (classDescriptor == null) {
168                                return new OutermostKotlinClassLightClassData(
169                                        javaFileStub, extraDiagnostics, FqName.ROOT, classOrObject,
170                                        null, Collections.<JetClassOrObject, InnerKotlinClassLightClassData>emptyMap()
171                                );
172                            }
173    
174                            FqName fqName = predictClassFqName(bindingContext, classDescriptor);
175                            Collection<ClassDescriptor> allInnerClasses = CodegenBinding.getAllInnerClasses(bindingContext, classDescriptor);
176    
177                            Map<JetClassOrObject, InnerKotlinClassLightClassData> innerClassesMap = ContainerUtil.newHashMap();
178                            for (ClassDescriptor innerClassDescriptor : allInnerClasses) {
179                                PsiElement declaration = descriptorToDeclaration(innerClassDescriptor);
180                                if (!(declaration instanceof JetClassOrObject)) continue;
181                                JetClassOrObject innerClass = (JetClassOrObject) declaration;
182    
183                                InnerKotlinClassLightClassData innerLightClassData = new InnerKotlinClassLightClassData(
184                                        predictClassFqName(bindingContext, innerClassDescriptor),
185                                        innerClass,
186                                        innerClassDescriptor
187                                );
188    
189                                innerClassesMap.put(innerClass, innerLightClassData);
190                            }
191    
192                            return new OutermostKotlinClassLightClassData(
193                                    javaFileStub,
194                                    extraDiagnostics,
195                                    fqName,
196                                    classOrObject,
197                                    classDescriptor,
198                                    innerClassesMap
199                            );
200                        }
201    
202                        @NotNull
203                        private FqName predictClassFqName(BindingContext bindingContext, ClassDescriptor classDescriptor) {
204                            Type asmType = CodegenBinding.getAsmType(bindingContext, classDescriptor);
205                            //noinspection ConstantConditions
206                            return JvmClassName.byInternalName(asmType.getClassName().replace('.', '/')).getFqNameForClassNameWithoutDollars();
207                        }
208    
209                        @NotNull
210                        @Override
211                        public Collection<JetFile> getFiles() {
212                            return Collections.singletonList(getFile());
213                        }
214    
215                        @NotNull
216                        @Override
217                        public FqName getPackageFqName() {
218                            return getFile().getPackageFqName();
219                        }
220    
221                        @Override
222                        public GenerationState.GenerateClassFilter getGenerateClassFilter() {
223                            return new GenerationState.GenerateClassFilter() {
224                                @Override
225                                public boolean shouldProcessClass(JetClassOrObject generatedClassOrObject) {
226                                    // Trivial: generate and analyze class we are interested in.
227                                    if (generatedClassOrObject == classOrObject) return true;
228    
229                                    // Process all parent classes as they are context for current class
230                                    // Process child classes because they probably affect members (heuristic)
231                                    if (PsiTreeUtil.isAncestor(generatedClassOrObject, classOrObject, true) ||
232                                        PsiTreeUtil.isAncestor(classOrObject, generatedClassOrObject, true)) {
233                                        return true;
234                                    }
235    
236                                    if (generatedClassOrObject.isLocal() && classOrObject.isLocal()) {
237                                        // Local classes should be process by CodegenAnnotatingVisitor to
238                                        // decide what class they should be placed in.
239                                        //
240                                        // Example:
241                                        // class A
242                                        // fun foo() {
243                                        //     trait Z: A {}
244                                        //     fun bar() {
245                                        //         class <caret>O2: Z {}
246                                        //     }
247                                        // }
248    
249                                        // TODO: current method will process local classes in irrelevant declarations, it should be fixed.
250                                        PsiElement commonParent = PsiTreeUtil.findCommonParent(generatedClassOrObject, classOrObject);
251                                        return commonParent != null && !(commonParent instanceof PsiFile);
252                                    }
253    
254                                    return false;
255                                }
256    
257                                @Override
258                                public boolean shouldProcessScript(JetScript script) {
259                                    // We generate all enclosing classes
260                                    return PsiTreeUtil.isAncestor(script, classOrObject, false);
261                                }
262                            };
263                        }
264    
265                        @Override
266                        public void generate(@NotNull GenerationState state, @NotNull Collection<JetFile> files) {
267                            PackageCodegen packageCodegen = state.getFactory().forPackage(getPackageFqName(), files);
268                            packageCodegen.generateClassOrObject(classOrObject);
269                            state.getFactory().asList();
270                        }
271    
272                        @Override
273                        public String toString() {
274                            return StubGenerationStrategy.class.getName() + " for explicit class " + classOrObject.getName();
275                        }
276                    }
277            );
278        }
279    
280        private static final Logger LOG = Logger.getInstance(KotlinJavaFileStubProvider.class);
281    
282        private final Project project;
283        private final StubGenerationStrategy<T> stubGenerationStrategy;
284        private final boolean local;
285    
286        private KotlinJavaFileStubProvider(
287                @NotNull Project project,
288                boolean local,
289                @NotNull StubGenerationStrategy<T> stubGenerationStrategy
290        ) {
291            this.project = project;
292            this.stubGenerationStrategy = stubGenerationStrategy;
293            this.local = local;
294        }
295    
296        @Nullable
297        @Override
298        public Result<T> compute() {
299            FqName packageFqName = stubGenerationStrategy.getPackageFqName();
300            Collection<JetFile> files = stubGenerationStrategy.getFiles();
301    
302            checkForBuiltIns(packageFqName, files);
303    
304            LightClassConstructionContext context = stubGenerationStrategy.getContext(files);
305    
306            PsiJavaFileStub javaFileStub = createJavaFileStub(packageFqName, getRepresentativeVirtualFile(files));
307            BindingContext bindingContext;
308            BindingTraceContext forExtraDiagnostics = new BindingTraceContext();
309            try {
310                Stack<StubElement> stubStack = new Stack<StubElement>();
311                stubStack.push(javaFileStub);
312    
313                GenerationState state = new GenerationState(
314                        project,
315                        new KotlinLightClassBuilderFactory(stubStack),
316                        Progress.DEAF,
317                        context.getModule(),
318                        context.getBindingContext(),
319                        Lists.newArrayList(files),
320                        /*disable not-null assertions*/false, false,
321                        /*generateClassFilter=*/stubGenerationStrategy.getGenerateClassFilter(),
322                        /*disableInline=*/false,
323                        /*disableOptimization=*/false,
324                        null,
325                        null,
326                        forExtraDiagnostics,
327                        null
328                );
329                KotlinCodegenFacade.prepareForCompilation(state);
330    
331                bindingContext = state.getBindingContext();
332    
333                stubGenerationStrategy.generate(state, files);
334    
335                StubElement pop = stubStack.pop();
336                if (pop != javaFileStub) {
337                    LOG.error("Unbalanced stack operations: " + pop);
338                }
339            }
340            catch (ProcessCanceledException e) {
341                throw e;
342            }
343            catch (RuntimeException e) {
344                logErrorWithOSInfo(e, packageFqName, null);
345                throw e;
346            }
347    
348            Diagnostics extraDiagnostics = forExtraDiagnostics.getBindingContext().getDiagnostics();
349            return Result.create(
350                    stubGenerationStrategy.createLightClassData(javaFileStub, bindingContext, extraDiagnostics),
351                    local ? PsiModificationTracker.MODIFICATION_COUNT : PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT
352            );
353        }
354    
355        @NotNull
356        private PsiJavaFileStub createJavaFileStub(@NotNull final FqName packageFqName, @NotNull VirtualFile virtualFile) {
357            PsiManager manager = PsiManager.getInstance(project);
358    
359            final PsiJavaFileStubImpl javaFileStub = new PsiJavaFileStubImpl(packageFqName.asString(), true);
360            javaFileStub.setPsiFactory(new ClsWrapperStubPsiFactory());
361    
362            ClsFileImpl fakeFile =
363                    new ClsFileImpl((PsiManagerImpl) manager, new ClassFileViewProvider(manager, virtualFile)) {
364                        @NotNull
365                        @Override
366                        public PsiClassHolderFileStub getStub() {
367                            return javaFileStub;
368                        }
369    
370                        @NotNull
371                        @Override
372                        public String getPackageName() {
373                            return packageFqName.asString();
374                        }
375                    };
376    
377            fakeFile.setPhysical(false);
378            javaFileStub.setPsi(fakeFile);
379            return javaFileStub;
380        }
381    
382        @NotNull
383        private static VirtualFile getRepresentativeVirtualFile(@NotNull Collection<JetFile> files) {
384            JetFile firstFile = files.iterator().next();
385            VirtualFile virtualFile = files.size() == 1 ? firstFile.getVirtualFile() : new LightVirtualFile();
386            assert virtualFile != null : "No virtual file for " + firstFile;
387            return virtualFile;
388        }
389    
390        private static void checkForBuiltIns(@NotNull FqName fqName, @NotNull Collection<JetFile> files) {
391            for (JetFile file : files) {
392                if (LightClassUtil.belongsToKotlinBuiltIns(file)) {
393                    // We may not fail later due to some luck, but generating JetLightClasses for built-ins is a bad idea anyways
394                    // If it fails later, there will be an exception logged
395                    logErrorWithOSInfo(null, fqName, file.getVirtualFile());
396                }
397            }
398        }
399    
400        private static void logErrorWithOSInfo(@Nullable Throwable cause, @NotNull FqName fqName, @Nullable VirtualFile virtualFile) {
401            String path = virtualFile == null ? "<null>" : virtualFile.getPath();
402            LOG.error(
403                    "Could not generate LightClass for " + fqName + " declared in " + path + "\n" +
404                    "built-ins dir URL is " + LightClassUtil.getBuiltInsDirUrl() + "\n" +
405                    "System: " + SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION + " Java Runtime: " + SystemInfo.JAVA_RUNTIME_VERSION,
406                    cause);
407        }
408    
409        private interface StubGenerationStrategy<T extends WithFileStubAndExtraDiagnostics> {
410            @NotNull Collection<JetFile> getFiles();
411            @NotNull FqName getPackageFqName();
412    
413            @NotNull LightClassConstructionContext getContext(@NotNull Collection<JetFile> files);
414            @NotNull T createLightClassData(PsiJavaFileStub javaFileStub, BindingContext bindingContext, Diagnostics extraDiagnostics);
415    
416            GenerationState.GenerateClassFilter getGenerateClassFilter();
417            void generate(@NotNull GenerationState state, @NotNull Collection<JetFile> files);
418        }
419    }