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