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