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.BindingTraceContext;
056    import org.jetbrains.jet.lang.resolve.Diagnostics;
057    import org.jetbrains.jet.lang.resolve.name.FqName;
058    
059    import java.util.Collection;
060    import java.util.Collections;
061    import java.util.Map;
062    
063    import static org.jetbrains.jet.lang.resolve.DescriptorToSourceUtils.descriptorToDeclaration;
064    
065    public class KotlinJavaFileStubProvider<T extends WithFileStubAndExtraDiagnostics> implements CachedValueProvider<T> {
066    
067        @NotNull
068        public static KotlinJavaFileStubProvider<KotlinPackageLightClassData> createForPackageClass(
069                @NotNull final Project project,
070                @NotNull final FqName packageFqName,
071                @NotNull final GlobalSearchScope searchScope
072        ) {
073            return new KotlinJavaFileStubProvider<KotlinPackageLightClassData>(
074                    project,
075                    false,
076                    new StubGenerationStrategy<KotlinPackageLightClassData>() {
077                        @NotNull
078                        @Override
079                        public LightClassConstructionContext getContext(@NotNull Collection<JetFile> files) {
080                            return LightClassGenerationSupport.getInstance(project).getContextForPackage(files);
081                        }
082    
083                        @NotNull
084                        @Override
085                        public Collection<JetFile> getFiles() {
086                            // Don't memoize this, it can be called again after an out-of-code-block modification occurs,
087                            // and the set of files changes
088                            return LightClassGenerationSupport.getInstance(project).findFilesForPackage(packageFqName, searchScope);
089                        }
090    
091                        @NotNull
092                        @Override
093                        public KotlinPackageLightClassData createLightClassData(
094                                PsiJavaFileStub javaFileStub,
095                                BindingContext bindingContext,
096                                Diagnostics extraDiagnostics
097                        ) {
098                            return new KotlinPackageLightClassData(javaFileStub, extraDiagnostics);
099                        }
100    
101                        @NotNull
102                        @Override
103                        public FqName getPackageFqName() {
104                            return packageFqName;
105                        }
106    
107                        @Override
108                        public GenerationState.GenerateClassFilter getGenerateClassFilter() {
109                            return new GenerationState.GenerateClassFilter() {
110                                @Override
111                                public boolean shouldProcess(JetClassOrObject classOrObject) {
112                                    // Top-level classes and such should not be generated for performance reasons.
113                                    // Local classes in top-level functions must still be generated
114                                    return JetPsiUtil.isLocal(classOrObject);
115                                }
116                            };
117                        }
118    
119                        @Override
120                        public void generate(@NotNull GenerationState state, @NotNull Collection<JetFile> files) {
121                            PackageCodegen codegen = state.getFactory().forPackage(packageFqName, files);
122                            codegen.generate(CompilationErrorHandler.THROW_EXCEPTION);
123                            state.getFactory().asList();
124                        }
125    
126                        @Override
127                        public String toString() {
128                            return StubGenerationStrategy.class.getName() + " for package class";
129                        }
130                    }
131            );
132        }
133    
134        @NotNull
135        public static KotlinJavaFileStubProvider<OutermostKotlinClassLightClassData> createForDeclaredClass(@NotNull final JetClassOrObject classOrObject) {
136            return new KotlinJavaFileStubProvider<OutermostKotlinClassLightClassData>(
137                    classOrObject.getProject(),
138                    classOrObject.isLocal(),
139                    new StubGenerationStrategy<OutermostKotlinClassLightClassData>() {
140                        private JetFile getFile() {
141                            return classOrObject.getContainingJetFile();
142                        }
143    
144                        @NotNull
145                        @Override
146                        public LightClassConstructionContext getContext(@NotNull Collection<JetFile> files) {
147                            return LightClassGenerationSupport.getInstance(classOrObject.getProject()).getContextForClassOrObject(classOrObject);
148                        }
149    
150                        @NotNull
151                        @Override
152                        public OutermostKotlinClassLightClassData createLightClassData(
153                                PsiJavaFileStub javaFileStub,
154                                BindingContext bindingContext,
155                                Diagnostics extraDiagnostics
156                        ) {
157                            ClassDescriptor classDescriptor = bindingContext.get(BindingContext.CLASS, classOrObject);
158                            if (classDescriptor == null) {
159                                return new OutermostKotlinClassLightClassData(
160                                        javaFileStub, extraDiagnostics,
161                                        "", classOrObject, null, Collections.<JetClassOrObject, InnerKotlinClassLightClassData>emptyMap()
162                                );
163                            }
164    
165                            String jvmInternalName = CodegenBinding.getJvmInternalName(bindingContext, classDescriptor);
166                            Collection<ClassDescriptor> allInnerClasses = CodegenBinding.getAllInnerClasses(bindingContext, classDescriptor);
167    
168                            Map<JetClassOrObject, InnerKotlinClassLightClassData> innerClassesMap = ContainerUtil.newHashMap();
169                            for (ClassDescriptor innerClassDescriptor : allInnerClasses) {
170                                JetClassOrObject innerClass = (JetClassOrObject) descriptorToDeclaration(innerClassDescriptor);
171                                if (innerClass == null) continue;
172    
173                                InnerKotlinClassLightClassData innerLightClassData = new InnerKotlinClassLightClassData(
174                                        CodegenBinding.getJvmInternalName(bindingContext, innerClassDescriptor),
175                                        innerClass,
176                                        innerClassDescriptor
177                                );
178    
179                                innerClassesMap.put(innerClass, innerLightClassData);
180                            }
181    
182                            return new OutermostKotlinClassLightClassData(
183                                    javaFileStub,
184                                    extraDiagnostics,
185                                    jvmInternalName,
186                                    classOrObject,
187                                    classDescriptor,
188                                    innerClassesMap
189                            );
190                        }
191    
192                        @NotNull
193                        @Override
194                        public Collection<JetFile> getFiles() {
195                            return Collections.singletonList(getFile());
196                        }
197    
198                        @NotNull
199                        @Override
200                        public FqName getPackageFqName() {
201                            return getFile().getPackageFqName();
202                        }
203    
204                        @Override
205                        public GenerationState.GenerateClassFilter getGenerateClassFilter() {
206                            return new GenerationState.GenerateClassFilter() {
207                                @Override
208                                public boolean shouldProcess(JetClassOrObject generatedClassOrObject) {
209                                    // Trivial: generate and analyze class we are interested in.
210                                    if (generatedClassOrObject == classOrObject) return true;
211    
212                                    // Process all parent classes as they are context for current class
213                                    // Process child classes because they probably affect members (heuristic)
214                                    if (PsiTreeUtil.isAncestor(generatedClassOrObject, classOrObject, true) ||
215                                        PsiTreeUtil.isAncestor(classOrObject, generatedClassOrObject, true)) {
216                                        return true;
217                                    }
218    
219                                    if (generatedClassOrObject.isLocal() && classOrObject.isLocal()) {
220                                        // Local classes should be process by CodegenAnnotatingVisitor to
221                                        // decide what class they should be placed in.
222                                        //
223                                        // Example:
224                                        // class A
225                                        // fun foo() {
226                                        //     trait Z: A {}
227                                        //     fun bar() {
228                                        //         class <caret>O2: Z {}
229                                        //     }
230                                        // }
231    
232                                        // TODO: current method will process local classes in irrelevant declarations, it should be fixed.
233                                        PsiElement commonParent = PsiTreeUtil.findCommonParent(generatedClassOrObject, classOrObject);
234                                        return commonParent != null && !(commonParent instanceof PsiFile);
235                                    }
236    
237                                    return false;
238                                }
239                            };
240                        }
241    
242                        @Override
243                        public void generate(@NotNull GenerationState state, @NotNull Collection<JetFile> files) {
244                            PackageCodegen packageCodegen = state.getFactory().forPackage(getPackageFqName(), files);
245                            packageCodegen.generateClassOrObject(classOrObject);
246                            state.getFactory().asList();
247                        }
248    
249                        @Override
250                        public String toString() {
251                            return StubGenerationStrategy.class.getName() + " for explicit class " + classOrObject.getName();
252                        }
253                    }
254            );
255        }
256    
257        private static final Logger LOG = Logger.getInstance(KotlinJavaFileStubProvider.class);
258    
259        private final Project project;
260        private final StubGenerationStrategy<T> stubGenerationStrategy;
261        private final boolean local;
262    
263        private KotlinJavaFileStubProvider(
264                @NotNull Project project,
265                boolean local,
266                @NotNull StubGenerationStrategy<T> stubGenerationStrategy
267        ) {
268            this.project = project;
269            this.stubGenerationStrategy = stubGenerationStrategy;
270            this.local = local;
271        }
272    
273        @Nullable
274        @Override
275        public Result<T> compute() {
276            FqName packageFqName = stubGenerationStrategy.getPackageFqName();
277            Collection<JetFile> files = stubGenerationStrategy.getFiles();
278    
279            checkForBuiltIns(packageFqName, files);
280    
281            LightClassConstructionContext context = stubGenerationStrategy.getContext(files);
282    
283            PsiJavaFileStub javaFileStub = createJavaFileStub(packageFqName, getRepresentativeVirtualFile(files));
284            BindingContext bindingContext;
285            BindingTraceContext forExtraDiagnostics = new BindingTraceContext();
286            try {
287                Stack<StubElement> stubStack = new Stack<StubElement>();
288                stubStack.push(javaFileStub);
289    
290                GenerationState state = new GenerationState(
291                        project,
292                        new KotlinLightClassBuilderFactory(stubStack),
293                        Progress.DEAF,
294                        context.getModule(),
295                        context.getBindingContext(),
296                        Lists.newArrayList(files),
297                        /*not-null assertions*/false, false,
298                        /*generateClassFilter=*/stubGenerationStrategy.getGenerateClassFilter(),
299                        /*to generate inline flag on methods*/true,
300                        /*optimize*/true,
301                        null,
302                        null,
303                        forExtraDiagnostics,
304                        null);
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    }