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.compiled.ClsFileImpl;
030    import com.intellij.psi.impl.java.stubs.PsiJavaFileStub;
031    import com.intellij.psi.impl.java.stubs.impl.PsiJavaFileStubImpl;
032    import com.intellij.psi.search.GlobalSearchScope;
033    import com.intellij.psi.stubs.PsiClassHolderFileStub;
034    import com.intellij.psi.stubs.StubElement;
035    import com.intellij.psi.util.CachedValueProvider;
036    import com.intellij.psi.util.PsiModificationTracker;
037    import com.intellij.psi.util.PsiTreeUtil;
038    import com.intellij.util.containers.ContainerUtil;
039    import com.intellij.util.containers.Stack;
040    import kotlin.jvm.functions.Function0;
041    import org.jetbrains.annotations.NotNull;
042    import org.jetbrains.annotations.Nullable;
043    import org.jetbrains.kotlin.codegen.CompilationErrorHandler;
044    import org.jetbrains.kotlin.codegen.KotlinCodegenFacade;
045    import org.jetbrains.kotlin.codegen.MultifileClassCodegen;
046    import org.jetbrains.kotlin.codegen.PackageCodegen;
047    import org.jetbrains.kotlin.codegen.binding.CodegenBinding;
048    import org.jetbrains.kotlin.codegen.context.PackageContext;
049    import org.jetbrains.kotlin.codegen.state.GenerationState;
050    import org.jetbrains.kotlin.descriptors.ClassDescriptor;
051    import org.jetbrains.kotlin.fileClasses.FileClasses;
052    import org.jetbrains.kotlin.fileClasses.JvmFileClassInfo;
053    import org.jetbrains.kotlin.fileClasses.NoResolveFileClassesProvider;
054    import org.jetbrains.kotlin.name.FqName;
055    import org.jetbrains.kotlin.psi.KtClassOrObject;
056    import org.jetbrains.kotlin.psi.KtFile;
057    import org.jetbrains.kotlin.psi.KtPsiUtil;
058    import org.jetbrains.kotlin.psi.KtScript;
059    import org.jetbrains.kotlin.resolve.BindingContext;
060    import org.jetbrains.kotlin.resolve.BindingTraceContext;
061    import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics;
062    import org.jetbrains.kotlin.resolve.jvm.JvmClassName;
063    import org.jetbrains.org.objectweb.asm.Type;
064    
065    import java.util.Collection;
066    import java.util.Collections;
067    import java.util.Map;
068    
069    import static org.jetbrains.kotlin.resolve.DescriptorToSourceUtils.descriptorToDeclaration;
070    
071    public class KotlinJavaFileStubProvider<T extends WithFileStubAndExtraDiagnostics> implements CachedValueProvider<T> {
072    
073        @NotNull
074        public static KotlinJavaFileStubProvider<KotlinFacadeLightClassData> createForPackageClass(
075                @NotNull final Project project,
076                @NotNull final FqName packageFqName,
077                @NotNull final GlobalSearchScope searchScope
078        ) {
079            return new KotlinJavaFileStubProvider<KotlinFacadeLightClassData>(
080                    project,
081                    false,
082                    new StubGenerationStrategy<KotlinFacadeLightClassData>() {
083                        @NotNull
084                        @Override
085                        public LightClassConstructionContext getContext(@NotNull Collection<KtFile> files) {
086                            return LightClassGenerationSupport.getInstance(project).getContextForPackage(files);
087                        }
088    
089                        @NotNull
090                        @Override
091                        public Collection<KtFile> getFiles() {
092                            // Don't memoize this, it can be called again after an out-of-code-block modification occurs,
093                            // and the set of files changes
094                            return LightClassGenerationSupport.getInstance(project).findFilesForPackage(packageFqName, searchScope);
095                        }
096    
097                        @NotNull
098                        @Override
099                        public KotlinFacadeLightClassData createLightClassData(
100                                PsiJavaFileStub javaFileStub,
101                                BindingContext bindingContext,
102                                Diagnostics extraDiagnostics
103                        ) {
104                            return new KotlinFacadeLightClassData(javaFileStub, extraDiagnostics);
105                        }
106    
107                        @NotNull
108                        @Override
109                        public FqName getPackageFqName() {
110                            return packageFqName;
111                        }
112    
113                        @Override
114                        public GenerationState.GenerateClassFilter getGenerateClassFilter() {
115                            return new GenerationState.GenerateClassFilter() {
116    
117                                @Override
118                                public boolean shouldGeneratePackagePart(KtFile jetFile) {
119                                    return true;
120                                }
121    
122                                @Override
123                                public boolean shouldAnnotateClass(KtClassOrObject classOrObject) {
124                                    return shouldGenerateClass(classOrObject);
125                                }
126    
127                                @Override
128                                public boolean shouldGenerateClass(KtClassOrObject classOrObject) {
129                                    // Top-level classes and such should not be generated for performance reasons.
130                                    // Local classes in top-level functions must still be generated
131                                    return KtPsiUtil.isLocal(classOrObject);
132                                }
133    
134                                @Override
135                                public boolean shouldGenerateScript(KtScript script) {
136                                    // Scripts yield top-level classes, and should not be generated
137                                    return false;
138                                }
139                            };
140                        }
141    
142                        @Override
143                        public void generate(@NotNull GenerationState state, @NotNull Collection<KtFile> files) {
144                            KotlinCodegenFacade.doGenerateFiles(files, state, CompilationErrorHandler.THROW_EXCEPTION);
145                        }
146    
147                        @Override
148                        public String toString() {
149                            return StubGenerationStrategy.class.getName() + " for package class";
150                        }
151                    }
152            );
153        }
154    
155        @NotNull
156        public static CachedValueProvider<KotlinFacadeLightClassData> createForFacadeClass(
157                @NotNull final Project project,
158                @NotNull final FqName facadeFqName,
159                @NotNull final GlobalSearchScope searchScope
160        ) {
161            return new KotlinJavaFileStubProvider<KotlinFacadeLightClassData>(
162                    project,
163                    false,
164                    new StubGenerationStrategy<KotlinFacadeLightClassData>() {
165                        @NotNull
166                        @Override
167                        public Collection<KtFile> getFiles() {
168                            return LightClassGenerationSupport.getInstance(project).findFilesForFacade(facadeFqName, searchScope);
169                        }
170    
171                        @NotNull
172                        @Override
173                        public FqName getPackageFqName() {
174                            return facadeFqName.parent();
175                        }
176    
177                        @NotNull
178                        @Override
179                        public LightClassConstructionContext getContext(@NotNull Collection<KtFile> files) {
180                            return LightClassGenerationSupport.getInstance(project).getContextForFacade(files);
181                        }
182    
183                        @NotNull
184                        @Override
185                        public KotlinFacadeLightClassData createLightClassData(
186                                PsiJavaFileStub javaFileStub,
187                                BindingContext bindingContext,
188                                Diagnostics extraDiagnostics
189                        ) {
190                            return new KotlinFacadeLightClassData(javaFileStub, extraDiagnostics);
191                        }
192    
193                        @Override
194                        public GenerationState.GenerateClassFilter getGenerateClassFilter() {
195                            return new GenerationState.GenerateClassFilter() {
196                                @Override
197                                public boolean shouldAnnotateClass(KtClassOrObject classOrObject) {
198                                    return shouldGenerateClass(classOrObject);
199                                }
200    
201                                @Override
202                                public boolean shouldGenerateClass(KtClassOrObject classOrObject) {
203                                    return KtPsiUtil.isLocal(classOrObject);
204                                }
205    
206                                @Override
207                                public boolean shouldGeneratePackagePart(KtFile jetFile) {
208                                    return true;
209                                }
210    
211                                @Override
212                                public boolean shouldGenerateScript(KtScript script) {
213                                    return false;
214                                }
215                            };
216                        }
217    
218                        @Override
219                        public void generate(@NotNull GenerationState state, @NotNull Collection<KtFile> files) {
220                            if (!files.isEmpty()) {
221                                KtFile representativeFile = files.iterator().next();
222                                JvmFileClassInfo fileClassInfo = NoResolveFileClassesProvider.INSTANCE$.getFileClassInfo(representativeFile);
223                                if (!fileClassInfo.getWithJvmMultifileClass()) {
224                                    PackageCodegen codegen = state.getFactory().forPackage(representativeFile.getPackageFqName(), files);
225                                    codegen.generate(CompilationErrorHandler.THROW_EXCEPTION);
226                                    state.getFactory().asList();
227                                    return;
228                                }
229                            }
230    
231                            MultifileClassCodegen codegen = state.getFactory().forMultifileClass(facadeFqName, files);
232                            codegen.generate(CompilationErrorHandler.THROW_EXCEPTION);
233                            state.getFactory().asList();
234                        }
235    
236                        @Override
237                        public String toString() {
238                            return StubGenerationStrategy.class.getName() + " for facade class";
239                        }
240                    });
241        }
242    
243        @NotNull
244        public static KotlinJavaFileStubProvider<OutermostKotlinClassLightClassData> createForDeclaredClass(@NotNull final KtClassOrObject classOrObject) {
245            return new KotlinJavaFileStubProvider<OutermostKotlinClassLightClassData>(
246                    classOrObject.getProject(),
247                    classOrObject.isLocal(),
248                    new StubGenerationStrategy<OutermostKotlinClassLightClassData>() {
249                        private KtFile getFile() {
250                            return classOrObject.getContainingJetFile();
251                        }
252    
253                        @NotNull
254                        @Override
255                        public LightClassConstructionContext getContext(@NotNull Collection<KtFile> files) {
256                            return LightClassGenerationSupport.getInstance(classOrObject.getProject()).getContextForClassOrObject(classOrObject);
257                        }
258    
259                        @NotNull
260                        @Override
261                        public OutermostKotlinClassLightClassData createLightClassData(
262                                PsiJavaFileStub javaFileStub,
263                                BindingContext bindingContext,
264                                Diagnostics extraDiagnostics
265                        ) {
266                            ClassDescriptor classDescriptor = bindingContext.get(BindingContext.CLASS, classOrObject);
267                            if (classDescriptor == null) {
268                                return new OutermostKotlinClassLightClassData(
269                                        javaFileStub, extraDiagnostics, FqName.ROOT, classOrObject,
270                                        Collections.<KtClassOrObject, InnerKotlinClassLightClassData>emptyMap()
271                                );
272                            }
273    
274                            FqName fqName = predictClassFqName(bindingContext, classDescriptor);
275                            Collection<ClassDescriptor> allInnerClasses = CodegenBinding.getAllInnerClasses(bindingContext, classDescriptor);
276    
277                            Map<KtClassOrObject, InnerKotlinClassLightClassData> innerClassesMap = ContainerUtil.newHashMap();
278                            for (ClassDescriptor innerClassDescriptor : allInnerClasses) {
279                                PsiElement declaration = descriptorToDeclaration(innerClassDescriptor);
280                                if (!(declaration instanceof KtClassOrObject)) continue;
281                                KtClassOrObject innerClass = (KtClassOrObject) declaration;
282    
283                                InnerKotlinClassLightClassData innerLightClassData = new InnerKotlinClassLightClassData(
284                                        predictClassFqName(bindingContext, innerClassDescriptor),
285                                        innerClass
286                                );
287    
288                                innerClassesMap.put(innerClass, innerLightClassData);
289                            }
290    
291                            return new OutermostKotlinClassLightClassData(
292                                    javaFileStub,
293                                    extraDiagnostics,
294                                    fqName,
295                                    classOrObject,
296                                    innerClassesMap
297                            );
298                        }
299    
300                        @NotNull
301                        private FqName predictClassFqName(BindingContext bindingContext, ClassDescriptor classDescriptor) {
302                            Type asmType = CodegenBinding.getAsmType(bindingContext, classDescriptor);
303                            //noinspection ConstantConditions
304                            return JvmClassName.byInternalName(asmType.getClassName().replace('.', '/')).getFqNameForClassNameWithoutDollars();
305                        }
306    
307                        @NotNull
308                        @Override
309                        public Collection<KtFile> getFiles() {
310                            return Collections.singletonList(getFile());
311                        }
312    
313                        @NotNull
314                        @Override
315                        public FqName getPackageFqName() {
316                            return getFile().getPackageFqName();
317                        }
318    
319                        @Override
320                        public GenerationState.GenerateClassFilter getGenerateClassFilter() {
321                            return new GenerationState.GenerateClassFilter() {
322    
323                                @Override
324                                public boolean shouldGeneratePackagePart(KtFile jetFile) {
325                                    return true;
326                                }
327    
328                                @Override
329                                public boolean shouldAnnotateClass(KtClassOrObject classOrObject) {
330                                    return shouldGenerateClass(classOrObject);
331                                }
332    
333                                @Override
334                                public boolean shouldGenerateClass(KtClassOrObject generatedClassOrObject) {
335                                    // Trivial: generate and analyze class we are interested in.
336                                    if (generatedClassOrObject == classOrObject) return true;
337    
338                                    // Process all parent classes as they are context for current class
339                                    // Process child classes because they probably affect members (heuristic)
340                                    if (PsiTreeUtil.isAncestor(generatedClassOrObject, classOrObject, true) ||
341                                        PsiTreeUtil.isAncestor(classOrObject, generatedClassOrObject, true)) {
342                                        return true;
343                                    }
344    
345                                    if (generatedClassOrObject.isLocal() && classOrObject.isLocal()) {
346                                        // Local classes should be process by CodegenAnnotatingVisitor to
347                                        // decide what class they should be placed in.
348                                        //
349                                        // Example:
350                                        // class A
351                                        // fun foo() {
352                                        //     trait Z: A {}
353                                        //     fun bar() {
354                                        //         class <caret>O2: Z {}
355                                        //     }
356                                        // }
357    
358                                        // TODO: current method will process local classes in irrelevant declarations, it should be fixed.
359                                        PsiElement commonParent = PsiTreeUtil.findCommonParent(generatedClassOrObject, classOrObject);
360                                        return commonParent != null && !(commonParent instanceof PsiFile);
361                                    }
362    
363                                    return false;
364                                }
365    
366                                @Override
367                                public boolean shouldGenerateScript(KtScript script) {
368                                    // We generate all enclosing classes
369                                    return PsiTreeUtil.isAncestor(script, classOrObject, false);
370                                }
371                            };
372                        }
373    
374                        @Override
375                        public void generate(@NotNull GenerationState state, @NotNull Collection<KtFile> files) {
376                            PackageCodegen packageCodegen = state.getFactory().forPackage(getPackageFqName(), files);
377                            KtFile file = classOrObject.getContainingJetFile();
378                            Type packagePartType = FileClasses.getFileClassType(state.getFileClassesProvider(), file);
379                            PackageContext context = state.getRootContext().intoPackagePart(packageCodegen.getPackageFragment(), packagePartType, file);
380                            packageCodegen.generateClassOrObject(classOrObject, context);
381                            state.getFactory().asList();
382                        }
383    
384                        @Override
385                        public String toString() {
386                            return StubGenerationStrategy.class.getName() + " for explicit class " + classOrObject.getName();
387                        }
388                    }
389            );
390        }
391    
392        private static final Logger LOG = Logger.getInstance(KotlinJavaFileStubProvider.class);
393    
394        private final Project project;
395        private final StubGenerationStrategy<T> stubGenerationStrategy;
396        private final boolean local;
397    
398        private KotlinJavaFileStubProvider(
399                @NotNull Project project,
400                boolean local,
401                @NotNull StubGenerationStrategy<T> stubGenerationStrategy
402        ) {
403            this.project = project;
404            this.stubGenerationStrategy = stubGenerationStrategy;
405            this.local = local;
406        }
407    
408        @Nullable
409        @Override
410        public Result<T> compute() {
411            FqName packageFqName = stubGenerationStrategy.getPackageFqName();
412            Collection<KtFile> files = stubGenerationStrategy.getFiles();
413    
414            checkForBuiltIns(packageFqName, files);
415    
416            LightClassConstructionContext context = stubGenerationStrategy.getContext(files);
417    
418            PsiJavaFileStub javaFileStub = createJavaFileStub(packageFqName, files);
419            BindingContext bindingContext;
420            BindingTraceContext forExtraDiagnostics = new BindingTraceContext();
421            try {
422                Stack<StubElement> stubStack = new Stack<StubElement>();
423                stubStack.push(javaFileStub);
424    
425                GenerationState state = new GenerationState(
426                        project,
427                        new KotlinLightClassBuilderFactory(stubStack),
428                        context.getModule(),
429                        context.getBindingContext(),
430                        Lists.newArrayList(files),
431                        /*disable not-null assertions*/false, false,
432                        /*generateClassFilter=*/stubGenerationStrategy.getGenerateClassFilter(),
433                        /*disableInline=*/false,
434                        /*disableOptimization=*/false,
435                        /*useTypeTableInSerializer=*/false,
436                        forExtraDiagnostics
437                );
438                KotlinCodegenFacade.prepareForCompilation(state);
439    
440                bindingContext = state.getBindingContext();
441    
442                stubGenerationStrategy.generate(state, files);
443    
444                StubElement pop = stubStack.pop();
445                if (pop != javaFileStub) {
446                    LOG.error("Unbalanced stack operations: " + pop);
447                }
448            }
449            catch (ProcessCanceledException e) {
450                throw e;
451            }
452            catch (RuntimeException e) {
453                logErrorWithOSInfo(e, packageFqName, null);
454                throw e;
455            }
456    
457            Diagnostics extraDiagnostics = forExtraDiagnostics.getBindingContext().getDiagnostics();
458            return Result.create(
459                    stubGenerationStrategy.createLightClassData(javaFileStub, bindingContext, extraDiagnostics),
460                    local ? PsiModificationTracker.MODIFICATION_COUNT : PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT
461            );
462        }
463    
464        @NotNull
465        private static ClsFileImpl createFakeClsFile(
466                @NotNull Project project,
467                @NotNull final FqName packageFqName,
468                @NotNull Collection<KtFile> files,
469                @NotNull final Function0<? extends PsiClassHolderFileStub> fileStubProvider
470        ) {
471            PsiManager manager = PsiManager.getInstance(project);
472    
473            VirtualFile virtualFile = getRepresentativeVirtualFile(files);
474            ClsFileImpl fakeFile = new ClsFileImpl(new ClassFileViewProvider(manager, virtualFile)) {
475                @NotNull
476                @Override
477                public PsiClassHolderFileStub getStub() {
478                    return fileStubProvider.invoke();
479                }
480    
481                @NotNull
482                @Override
483                public String getPackageName() {
484                    return packageFqName.asString();
485                }
486            };
487    
488            fakeFile.setPhysical(false);
489            return fakeFile;
490        }
491    
492        @NotNull
493        private PsiJavaFileStub createJavaFileStub(@NotNull FqName packageFqName, @NotNull Collection<KtFile> files) {
494            final PsiJavaFileStubImpl javaFileStub = new PsiJavaFileStubImpl(packageFqName.asString(), true);
495            javaFileStub.setPsiFactory(new ClsWrapperStubPsiFactory());
496    
497            ClsFileImpl fakeFile = createFakeClsFile(project, packageFqName, files, new Function0<PsiClassHolderFileStub>() {
498                @Override
499                public PsiClassHolderFileStub invoke() {
500                    return javaFileStub;
501                }
502            });
503    
504            javaFileStub.setPsi(fakeFile);
505            return javaFileStub;
506        }
507    
508        @NotNull
509        private static VirtualFile getRepresentativeVirtualFile(@NotNull Collection<KtFile> files) {
510            KtFile firstFile = files.iterator().next();
511            VirtualFile virtualFile = firstFile.getVirtualFile();
512            assert virtualFile != null : "No virtual file for " + firstFile;
513            return virtualFile;
514        }
515    
516        private static void checkForBuiltIns(@NotNull FqName fqName, @NotNull Collection<KtFile> files) {
517            for (KtFile file : files) {
518                if (LightClassUtil.INSTANCE$.belongsToKotlinBuiltIns(file)) {
519                    // We may not fail later due to some luck, but generating JetLightClasses for built-ins is a bad idea anyways
520                    // If it fails later, there will be an exception logged
521                    logErrorWithOSInfo(null, fqName, file.getVirtualFile());
522                }
523            }
524        }
525    
526        private static void logErrorWithOSInfo(@Nullable Throwable cause, @NotNull FqName fqName, @Nullable VirtualFile virtualFile) {
527            String path = virtualFile == null ? "<null>" : virtualFile.getPath();
528            LOG.error(
529                    "Could not generate LightClass for " + fqName + " declared in " + path + "\n" +
530                    "built-ins dir URL is " + LightClassUtil.INSTANCE$.getBuiltInsDirUrl() + "\n" +
531                    "System: " + SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION + " Java Runtime: " + SystemInfo.JAVA_RUNTIME_VERSION,
532                    cause);
533        }
534    
535        private interface StubGenerationStrategy<T extends WithFileStubAndExtraDiagnostics> {
536            @NotNull Collection<KtFile> getFiles();
537            @NotNull FqName getPackageFqName();
538    
539            @NotNull LightClassConstructionContext getContext(@NotNull Collection<KtFile> files);
540            @NotNull T createLightClassData(PsiJavaFileStub javaFileStub, BindingContext bindingContext, Diagnostics extraDiagnostics);
541    
542            GenerationState.GenerateClassFilter getGenerateClassFilter();
543            void generate(@NotNull GenerationState state, @NotNull Collection<KtFile> files);
544        }
545    }