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