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