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                        /*disable not-null assertions*/false, false,
298                        /*generateClassFilter=*/stubGenerationStrategy.getGenerateClassFilter(),
299                        /*disableInline=*/false,
300                        /*disableOptimization=*/false,
301                        null,
302                        null,
303                        forExtraDiagnostics,
304                        null
305                );
306                KotlinCodegenFacade.prepareForCompilation(state);
307    
308                bindingContext = state.getBindingContext();
309    
310                stubGenerationStrategy.generate(state, files);
311    
312                StubElement pop = stubStack.pop();
313                if (pop != javaFileStub) {
314                    LOG.error("Unbalanced stack operations: " + pop);
315                }
316            }
317            catch (ProcessCanceledException e) {
318                throw e;
319            }
320            catch (RuntimeException e) {
321                logErrorWithOSInfo(e, packageFqName, null);
322                throw e;
323            }
324    
325            Diagnostics extraDiagnostics = forExtraDiagnostics.getBindingContext().getDiagnostics();
326            return Result.create(
327                    stubGenerationStrategy.createLightClassData(javaFileStub, bindingContext, extraDiagnostics),
328                    local ? PsiModificationTracker.MODIFICATION_COUNT : PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT
329            );
330        }
331    
332        @NotNull
333        private PsiJavaFileStub createJavaFileStub(@NotNull final FqName packageFqName, @NotNull VirtualFile virtualFile) {
334            PsiManager manager = PsiManager.getInstance(project);
335    
336            final PsiJavaFileStubImpl javaFileStub = new PsiJavaFileStubImpl(packageFqName.asString(), true);
337            javaFileStub.setPsiFactory(new ClsWrapperStubPsiFactory());
338    
339            ClsFileImpl fakeFile =
340                    new ClsFileImpl((PsiManagerImpl) manager, new ClassFileViewProvider(manager, virtualFile)) {
341                        @NotNull
342                        @Override
343                        public PsiClassHolderFileStub getStub() {
344                            return javaFileStub;
345                        }
346    
347                        @NotNull
348                        @Override
349                        public String getPackageName() {
350                            return packageFqName.asString();
351                        }
352                    };
353    
354            fakeFile.setPhysical(false);
355            javaFileStub.setPsi(fakeFile);
356            return javaFileStub;
357        }
358    
359        @NotNull
360        private static VirtualFile getRepresentativeVirtualFile(@NotNull Collection<JetFile> files) {
361            JetFile firstFile = files.iterator().next();
362            VirtualFile virtualFile = files.size() == 1 ? firstFile.getVirtualFile() : new LightVirtualFile();
363            assert virtualFile != null : "No virtual file for " + firstFile;
364            return virtualFile;
365        }
366    
367        private static void checkForBuiltIns(@NotNull FqName fqName, @NotNull Collection<JetFile> files) {
368            for (JetFile file : files) {
369                if (LightClassUtil.belongsToKotlinBuiltIns(file)) {
370                    // We may not fail later due to some luck, but generating JetLightClasses for built-ins is a bad idea anyways
371                    // If it fails later, there will be an exception logged
372                    logErrorWithOSInfo(null, fqName, file.getVirtualFile());
373                }
374            }
375        }
376    
377        private static void logErrorWithOSInfo(@Nullable Throwable cause, @NotNull FqName fqName, @Nullable VirtualFile virtualFile) {
378            String path = virtualFile == null ? "<null>" : virtualFile.getPath();
379            LOG.error(
380                    "Could not generate LightClass for " + fqName + " declared in " + path + "\n" +
381                    "built-ins dir URL is " + LightClassUtil.getBuiltInsDirUrl() + "\n" +
382                    "System: " + SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION + " Java Runtime: " + SystemInfo.JAVA_RUNTIME_VERSION,
383                    cause);
384        }
385    
386        private interface StubGenerationStrategy<T extends WithFileStubAndExtraDiagnostics> {
387            @NotNull Collection<JetFile> getFiles();
388            @NotNull FqName getPackageFqName();
389    
390            @NotNull LightClassConstructionContext getContext(@NotNull Collection<JetFile> files);
391            @NotNull T createLightClassData(PsiJavaFileStub javaFileStub, BindingContext bindingContext, Diagnostics extraDiagnostics);
392    
393            GenerationState.GenerateClassFilter getGenerateClassFilter();
394            void generate(@NotNull GenerationState state, @NotNull Collection<JetFile> files);
395        }
396    }