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