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