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 CachedValueProvider<KotlinFacadeLightClassData> createForFacadeClass(
075                @NotNull final Project project,
076                @NotNull final FqName facadeFqName,
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 Collection<KtFile> getFiles() {
086                            return LightClassGenerationSupport.getInstance(project).findFilesForFacade(facadeFqName, searchScope);
087                        }
088    
089                        @NotNull
090                        @Override
091                        public FqName getPackageFqName() {
092                            return facadeFqName.parent();
093                        }
094    
095                        @NotNull
096                        @Override
097                        public LightClassConstructionContext getContext(@NotNull Collection<KtFile> files) {
098                            return LightClassGenerationSupport.getInstance(project).getContextForFacade(files);
099                        }
100    
101                        @NotNull
102                        @Override
103                        public KotlinFacadeLightClassData createLightClassData(
104                                PsiJavaFileStub javaFileStub,
105                                BindingContext bindingContext,
106                                Diagnostics extraDiagnostics
107                        ) {
108                            return new KotlinFacadeLightClassData(javaFileStub, extraDiagnostics);
109                        }
110    
111                        @Override
112                        public GenerationState.GenerateClassFilter getGenerateClassFilter() {
113                            return new GenerationState.GenerateClassFilter() {
114                                @Override
115                                public boolean shouldAnnotateClass(KtClassOrObject classOrObject) {
116                                    return shouldGenerateClass(classOrObject);
117                                }
118    
119                                @Override
120                                public boolean shouldGenerateClass(KtClassOrObject classOrObject) {
121                                    return KtPsiUtil.isLocal(classOrObject);
122                                }
123    
124                                @Override
125                                public boolean shouldGeneratePackagePart(KtFile jetFile) {
126                                    return true;
127                                }
128    
129                                @Override
130                                public boolean shouldGenerateScript(KtScript script) {
131                                    return false;
132                                }
133                            };
134                        }
135    
136                        @Override
137                        public void generate(@NotNull GenerationState state, @NotNull Collection<KtFile> files) {
138                            if (!files.isEmpty()) {
139                                KtFile representativeFile = files.iterator().next();
140                                JvmFileClassInfo fileClassInfo = NoResolveFileClassesProvider.INSTANCE.getFileClassInfo(representativeFile);
141                                if (!fileClassInfo.getWithJvmMultifileClass()) {
142                                    PackageCodegen codegen = state.getFactory().forPackage(representativeFile.getPackageFqName(), files);
143                                    codegen.generate(CompilationErrorHandler.THROW_EXCEPTION);
144                                    state.getFactory().asList();
145                                    return;
146                                }
147                            }
148    
149                            MultifileClassCodegen codegen = state.getFactory().forMultifileClass(facadeFqName, files);
150                            codegen.generate(CompilationErrorHandler.THROW_EXCEPTION);
151                            state.getFactory().asList();
152                        }
153    
154                        @Override
155                        public String toString() {
156                            return StubGenerationStrategy.class.getName() + " for facade class";
157                        }
158                    });
159        }
160    
161        @NotNull
162        public static KotlinJavaFileStubProvider<OutermostKotlinClassLightClassData> createForDeclaredClass(@NotNull final KtClassOrObject classOrObject) {
163            return new KotlinJavaFileStubProvider<OutermostKotlinClassLightClassData>(
164                    classOrObject.getProject(),
165                    classOrObject.isLocal(),
166                    new StubGenerationStrategy<OutermostKotlinClassLightClassData>() {
167                        private KtFile getFile() {
168                            return classOrObject.getContainingJetFile();
169                        }
170    
171                        @NotNull
172                        @Override
173                        public LightClassConstructionContext getContext(@NotNull Collection<KtFile> files) {
174                            return LightClassGenerationSupport.getInstance(classOrObject.getProject()).getContextForClassOrObject(classOrObject);
175                        }
176    
177                        @NotNull
178                        @Override
179                        public OutermostKotlinClassLightClassData createLightClassData(
180                                PsiJavaFileStub javaFileStub,
181                                BindingContext bindingContext,
182                                Diagnostics extraDiagnostics
183                        ) {
184                            ClassDescriptor classDescriptor = bindingContext.get(BindingContext.CLASS, classOrObject);
185                            if (classDescriptor == null) {
186                                return new OutermostKotlinClassLightClassData(
187                                        javaFileStub, extraDiagnostics, FqName.ROOT, classOrObject,
188                                        Collections.<KtClassOrObject, InnerKotlinClassLightClassData>emptyMap()
189                                );
190                            }
191    
192                            FqName fqName = predictClassFqName(bindingContext, classDescriptor);
193                            Collection<ClassDescriptor> allInnerClasses = CodegenBinding.getAllInnerClasses(bindingContext, classDescriptor);
194    
195                            Map<KtClassOrObject, InnerKotlinClassLightClassData> innerClassesMap = ContainerUtil.newHashMap();
196                            for (ClassDescriptor innerClassDescriptor : allInnerClasses) {
197                                PsiElement declaration = descriptorToDeclaration(innerClassDescriptor);
198                                if (!(declaration instanceof KtClassOrObject)) continue;
199                                KtClassOrObject innerClass = (KtClassOrObject) declaration;
200    
201                                InnerKotlinClassLightClassData innerLightClassData = new InnerKotlinClassLightClassData(
202                                        predictClassFqName(bindingContext, innerClassDescriptor),
203                                        innerClass
204                                );
205    
206                                innerClassesMap.put(innerClass, innerLightClassData);
207                            }
208    
209                            return new OutermostKotlinClassLightClassData(
210                                    javaFileStub,
211                                    extraDiagnostics,
212                                    fqName,
213                                    classOrObject,
214                                    innerClassesMap
215                            );
216                        }
217    
218                        @NotNull
219                        private FqName predictClassFqName(BindingContext bindingContext, ClassDescriptor classDescriptor) {
220                            Type asmType = CodegenBinding.getAsmType(bindingContext, classDescriptor);
221                            //noinspection ConstantConditions
222                            return JvmClassName.byInternalName(asmType.getClassName().replace('.', '/')).getFqNameForClassNameWithoutDollars();
223                        }
224    
225                        @NotNull
226                        @Override
227                        public Collection<KtFile> getFiles() {
228                            return Collections.singletonList(getFile());
229                        }
230    
231                        @NotNull
232                        @Override
233                        public FqName getPackageFqName() {
234                            return getFile().getPackageFqName();
235                        }
236    
237                        @Override
238                        public GenerationState.GenerateClassFilter getGenerateClassFilter() {
239                            return new GenerationState.GenerateClassFilter() {
240    
241                                @Override
242                                public boolean shouldGeneratePackagePart(KtFile jetFile) {
243                                    return true;
244                                }
245    
246                                @Override
247                                public boolean shouldAnnotateClass(KtClassOrObject classOrObject) {
248                                    return shouldGenerateClass(classOrObject);
249                                }
250    
251                                @Override
252                                public boolean shouldGenerateClass(KtClassOrObject generatedClassOrObject) {
253                                    // Trivial: generate and analyze class we are interested in.
254                                    if (generatedClassOrObject == classOrObject) return true;
255    
256                                    // Process all parent classes as they are context for current class
257                                    // Process child classes because they probably affect members (heuristic)
258                                    if (PsiTreeUtil.isAncestor(generatedClassOrObject, classOrObject, true) ||
259                                        PsiTreeUtil.isAncestor(classOrObject, generatedClassOrObject, true)) {
260                                        return true;
261                                    }
262    
263                                    if (generatedClassOrObject.isLocal() && classOrObject.isLocal()) {
264                                        // Local classes should be process by CodegenAnnotatingVisitor to
265                                        // decide what class they should be placed in.
266                                        //
267                                        // Example:
268                                        // class A
269                                        // fun foo() {
270                                        //     trait Z: A {}
271                                        //     fun bar() {
272                                        //         class <caret>O2: Z {}
273                                        //     }
274                                        // }
275    
276                                        // TODO: current method will process local classes in irrelevant declarations, it should be fixed.
277                                        PsiElement commonParent = PsiTreeUtil.findCommonParent(generatedClassOrObject, classOrObject);
278                                        return commonParent != null && !(commonParent instanceof PsiFile);
279                                    }
280    
281                                    return false;
282                                }
283    
284                                @Override
285                                public boolean shouldGenerateScript(KtScript script) {
286                                    // We generate all enclosing classes
287                                    return PsiTreeUtil.isAncestor(script, classOrObject, false);
288                                }
289                            };
290                        }
291    
292                        @Override
293                        public void generate(@NotNull GenerationState state, @NotNull Collection<KtFile> files) {
294                            PackageCodegen packageCodegen = state.getFactory().forPackage(getPackageFqName(), files);
295                            KtFile file = classOrObject.getContainingJetFile();
296                            Type packagePartType = FileClasses.getFileClassType(state.getFileClassesProvider(), file);
297                            PackageContext context = state.getRootContext().intoPackagePart(packageCodegen.getPackageFragment(), packagePartType, file);
298                            packageCodegen.generateClassOrObject(classOrObject, context);
299                            state.getFactory().asList();
300                        }
301    
302                        @Override
303                        public String toString() {
304                            return StubGenerationStrategy.class.getName() + " for explicit class " + classOrObject.getName();
305                        }
306                    }
307            );
308        }
309    
310        private static final Logger LOG = Logger.getInstance(KotlinJavaFileStubProvider.class);
311    
312        private final Project project;
313        private final StubGenerationStrategy<T> stubGenerationStrategy;
314        private final boolean local;
315    
316        private KotlinJavaFileStubProvider(
317                @NotNull Project project,
318                boolean local,
319                @NotNull StubGenerationStrategy<T> stubGenerationStrategy
320        ) {
321            this.project = project;
322            this.stubGenerationStrategy = stubGenerationStrategy;
323            this.local = local;
324        }
325    
326        @Nullable
327        @Override
328        public Result<T> compute() {
329            FqName packageFqName = stubGenerationStrategy.getPackageFqName();
330            Collection<KtFile> files = stubGenerationStrategy.getFiles();
331    
332            checkForBuiltIns(packageFqName, files);
333    
334            LightClassConstructionContext context = stubGenerationStrategy.getContext(files);
335    
336            PsiJavaFileStub javaFileStub = createJavaFileStub(packageFqName, files);
337            BindingContext bindingContext;
338            BindingTraceContext forExtraDiagnostics = new BindingTraceContext();
339            try {
340                Stack<StubElement> stubStack = new Stack<StubElement>();
341                stubStack.push(javaFileStub);
342    
343                GenerationState state = new GenerationState(
344                        project,
345                        new KotlinLightClassBuilderFactory(stubStack),
346                        context.getModule(),
347                        context.getBindingContext(),
348                        Lists.newArrayList(files),
349                        /*disable not-null assertions*/false, false,
350                        /*generateClassFilter=*/stubGenerationStrategy.getGenerateClassFilter(),
351                        /*disableInline=*/false,
352                        /*disableOptimization=*/false,
353                        /*useTypeTableInSerializer=*/false,
354                        forExtraDiagnostics
355                );
356                KotlinCodegenFacade.prepareForCompilation(state);
357    
358                bindingContext = state.getBindingContext();
359    
360                stubGenerationStrategy.generate(state, files);
361    
362                StubElement pop = stubStack.pop();
363                if (pop != javaFileStub) {
364                    LOG.error("Unbalanced stack operations: " + pop);
365                }
366            }
367            catch (ProcessCanceledException e) {
368                throw e;
369            }
370            catch (RuntimeException e) {
371                logErrorWithOSInfo(e, packageFqName, null);
372                throw e;
373            }
374    
375            Diagnostics extraDiagnostics = forExtraDiagnostics.getBindingContext().getDiagnostics();
376            return Result.create(
377                    stubGenerationStrategy.createLightClassData(javaFileStub, bindingContext, extraDiagnostics),
378                    local ? PsiModificationTracker.MODIFICATION_COUNT : PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT
379            );
380        }
381    
382        @NotNull
383        private static ClsFileImpl createFakeClsFile(
384                @NotNull Project project,
385                @NotNull final FqName packageFqName,
386                @NotNull Collection<KtFile> files,
387                @NotNull final Function0<? extends PsiClassHolderFileStub> fileStubProvider
388        ) {
389            PsiManager manager = PsiManager.getInstance(project);
390    
391            VirtualFile virtualFile = getRepresentativeVirtualFile(files);
392            ClsFileImpl fakeFile = new ClsFileImpl(new ClassFileViewProvider(manager, virtualFile)) {
393                @NotNull
394                @Override
395                public PsiClassHolderFileStub getStub() {
396                    return fileStubProvider.invoke();
397                }
398    
399                @NotNull
400                @Override
401                public String getPackageName() {
402                    return packageFqName.asString();
403                }
404            };
405    
406            fakeFile.setPhysical(false);
407            return fakeFile;
408        }
409    
410        @NotNull
411        private PsiJavaFileStub createJavaFileStub(@NotNull FqName packageFqName, @NotNull Collection<KtFile> files) {
412            final PsiJavaFileStubImpl javaFileStub = new PsiJavaFileStubImpl(packageFqName.asString(), true);
413            javaFileStub.setPsiFactory(new ClsWrapperStubPsiFactory());
414    
415            ClsFileImpl fakeFile = createFakeClsFile(project, packageFqName, files, new Function0<PsiClassHolderFileStub>() {
416                @Override
417                public PsiClassHolderFileStub invoke() {
418                    return javaFileStub;
419                }
420            });
421    
422            javaFileStub.setPsi(fakeFile);
423            return javaFileStub;
424        }
425    
426        @NotNull
427        private static VirtualFile getRepresentativeVirtualFile(@NotNull Collection<KtFile> files) {
428            KtFile firstFile = files.iterator().next();
429            VirtualFile virtualFile = firstFile.getVirtualFile();
430            assert virtualFile != null : "No virtual file for " + firstFile;
431            return virtualFile;
432        }
433    
434        private static void checkForBuiltIns(@NotNull FqName fqName, @NotNull Collection<KtFile> files) {
435            for (KtFile file : files) {
436                if (LightClassUtil.INSTANCE$.belongsToKotlinBuiltIns(file)) {
437                    // We may not fail later due to some luck, but generating JetLightClasses for built-ins is a bad idea anyways
438                    // If it fails later, there will be an exception logged
439                    logErrorWithOSInfo(null, fqName, file.getVirtualFile());
440                }
441            }
442        }
443    
444        private static void logErrorWithOSInfo(@Nullable Throwable cause, @NotNull FqName fqName, @Nullable VirtualFile virtualFile) {
445            String path = virtualFile == null ? "<null>" : virtualFile.getPath();
446            LOG.error(
447                    "Could not generate LightClass for " + fqName + " declared in " + path + "\n" +
448                    "built-ins dir URL is " + LightClassUtil.INSTANCE$.getBuiltInsDirUrl() + "\n" +
449                    "System: " + SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION + " Java Runtime: " + SystemInfo.JAVA_RUNTIME_VERSION,
450                    cause);
451        }
452    
453        private interface StubGenerationStrategy<T extends WithFileStubAndExtraDiagnostics> {
454            @NotNull Collection<KtFile> getFiles();
455            @NotNull FqName getPackageFqName();
456    
457            @NotNull LightClassConstructionContext getContext(@NotNull Collection<KtFile> files);
458            @NotNull T createLightClassData(PsiJavaFileStub javaFileStub, BindingContext bindingContext, Diagnostics extraDiagnostics);
459    
460            GenerationState.GenerateClassFilter getGenerateClassFilter();
461            void generate(@NotNull GenerationState state, @NotNull Collection<KtFile> files);
462        }
463    }