001    /*
002     * Copyright 2010-2013 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.PsiManager;
027    import com.intellij.psi.impl.PsiManagerImpl;
028    import com.intellij.psi.impl.compiled.ClsFileImpl;
029    import com.intellij.psi.impl.java.stubs.PsiJavaFileStub;
030    import com.intellij.psi.impl.java.stubs.impl.PsiJavaFileStubImpl;
031    import com.intellij.psi.search.GlobalSearchScope;
032    import com.intellij.psi.stubs.PsiClassHolderFileStub;
033    import com.intellij.psi.stubs.StubElement;
034    import com.intellij.psi.util.CachedValueProvider;
035    import com.intellij.psi.util.PsiModificationTracker;
036    import com.intellij.testFramework.LightVirtualFile;
037    import com.intellij.util.containers.ContainerUtil;
038    import com.intellij.util.containers.Stack;
039    import org.jetbrains.annotations.NotNull;
040    import org.jetbrains.annotations.Nullable;
041    import org.jetbrains.jet.codegen.CompilationErrorHandler;
042    import org.jetbrains.jet.codegen.NamespaceCodegen;
043    import org.jetbrains.jet.codegen.binding.CodegenBinding;
044    import org.jetbrains.jet.codegen.state.GenerationState;
045    import org.jetbrains.jet.codegen.state.Progress;
046    import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
047    import org.jetbrains.jet.lang.psi.JetClassOrObject;
048    import org.jetbrains.jet.lang.psi.JetFile;
049    import org.jetbrains.jet.lang.psi.JetPsiUtil;
050    import org.jetbrains.jet.lang.resolve.BindingContext;
051    import org.jetbrains.jet.lang.resolve.BindingContextUtils;
052    import org.jetbrains.jet.lang.resolve.name.FqName;
053    import org.jetbrains.jet.lang.types.lang.InlineUtil;
054    
055    import java.util.Collection;
056    import java.util.Collections;
057    import java.util.Map;
058    
059    public class KotlinJavaFileStubProvider implements CachedValueProvider<LightClassStubWithData> {
060    
061        @NotNull
062        public static KotlinJavaFileStubProvider createForPackageClass(
063                @NotNull final Project project,
064                @NotNull final FqName packageFqName,
065                @NotNull final GlobalSearchScope searchScope
066        ) {
067            return new KotlinJavaFileStubProvider(
068                    project,
069                    false,
070                    new StubGenerationStrategy.NoDeclaredClasses() {
071                        @NotNull
072                        @Override
073                        public LightClassConstructionContext createLightClassConstructionContext(@NotNull Collection<JetFile> files) {
074                            return LightClassGenerationSupport.getInstance(project).analyzeRelevantCode(files);
075                        }
076    
077                        @NotNull
078                        @Override
079                        public Collection<JetFile> getFiles() {
080                            // Don't memoize this, it can be called again after an out-of-code-block modification occurs,
081                            // and the set of files changes
082                            return LightClassGenerationSupport.getInstance(project).findFilesForPackage(packageFqName, searchScope);
083                        }
084    
085                        @NotNull
086                        @Override
087                        public LightClassStubWithData createLightClassStubWithData(PsiJavaFileStub javaFileStub, BindingContext bindingContext) {
088                            return new LightClassStubWithData(javaFileStub, KotlinPackageLightClassData.instance$);
089                        }
090    
091                        @NotNull
092                        @Override
093                        public FqName getPackageFqName() {
094                            return packageFqName;
095                        }
096    
097                        @Override
098                        public void generate(@NotNull GenerationState state, @NotNull Collection<JetFile> files) {
099                            NamespaceCodegen codegen = state.getFactory().forNamespace(packageFqName, files);
100                            codegen.generate(CompilationErrorHandler.THROW_EXCEPTION);
101                            state.getFactory().asList();
102                        }
103                    }
104            );
105        }
106    
107        @NotNull
108        public static KotlinJavaFileStubProvider createForDeclaredClass(@NotNull final JetClassOrObject classOrObject) {
109            return new KotlinJavaFileStubProvider(
110                    classOrObject.getProject(),
111                    JetPsiUtil.isLocal(classOrObject),
112                    new StubGenerationStrategy.WithDeclaredClasses() {
113                        private JetFile getFile() {
114                            return (JetFile) classOrObject.getContainingFile();
115                        }
116    
117                        @NotNull
118                        @Override
119                        public LightClassConstructionContext createLightClassConstructionContext(@NotNull Collection<JetFile> files) {
120                            return LightClassGenerationSupport.getInstance(classOrObject.getProject()).analyzeRelevantCode(classOrObject);
121                        }
122    
123                        @NotNull
124                        @Override
125                        public LightClassStubWithData createLightClassStubWithData(PsiJavaFileStub javaFileStub, BindingContext bindingContext) {
126                            ClassDescriptor classDescriptor = bindingContext.get(BindingContext.CLASS, classOrObject);
127                            if (classDescriptor == null) {
128                                return new LightClassStubWithData(
129                                        javaFileStub,
130                                        new OutermostKotlinClassLightClassData("", classOrObject, null, Collections.<JetClassOrObject, LightClassDataForKotlinClass>emptyMap())
131                                );
132                            }
133    
134                            String jvmInternalName = CodegenBinding.getJvmInternalName(bindingContext, classDescriptor);
135                            Collection<ClassDescriptor> allInnerClasses = CodegenBinding.getAllInnerClasses(bindingContext, classDescriptor);
136    
137                            Map<JetClassOrObject, LightClassDataForKotlinClass> innerClassesMap = ContainerUtil.newHashMap();
138                            for (ClassDescriptor innerClassDescriptor : allInnerClasses) {
139                                JetClassOrObject innerClass = (JetClassOrObject) BindingContextUtils.descriptorToDeclaration(
140                                        bindingContext, innerClassDescriptor
141                                );
142                                if (innerClass == null) continue;
143    
144                                InnerKotlinClassLightClassData innerLightClassData = new InnerKotlinClassLightClassData(
145                                        CodegenBinding.getJvmInternalName(bindingContext, innerClassDescriptor),
146                                        innerClass,
147                                        innerClassDescriptor
148                                );
149                                innerClassesMap.put(innerClass, innerLightClassData);
150                            }
151    
152                            return new LightClassStubWithData(
153                                    javaFileStub,
154                                    new OutermostKotlinClassLightClassData(jvmInternalName, classOrObject, classDescriptor, innerClassesMap)
155                            );
156                        }
157    
158                        @NotNull
159                        @Override
160                        public Collection<JetFile> getFiles() {
161                            return Collections.singletonList(getFile());
162                        }
163    
164                        @NotNull
165                        @Override
166                        public FqName getPackageFqName() {
167                            return JetPsiUtil.getFQName(getFile());
168                        }
169    
170                        @Override
171                        public void generate(@NotNull GenerationState state, @NotNull Collection<JetFile> files) {
172                            NamespaceCodegen namespaceCodegen = state.getFactory().forNamespace(getPackageFqName(), files);
173                            namespaceCodegen.generateClassOrObject(classOrObject);
174                            state.getFactory().asList();
175                        }
176                    }
177            );
178        }
179    
180        private static final Logger LOG = Logger.getInstance(KotlinJavaFileStubProvider.class);
181    
182        private final Project project;
183        private final StubGenerationStrategy stubGenerationStrategy;
184        private final boolean local;
185    
186        private KotlinJavaFileStubProvider(
187                @NotNull Project project,
188                boolean local,
189                @NotNull StubGenerationStrategy stubGenerationStrategy
190        ) {
191            this.project = project;
192            this.stubGenerationStrategy = stubGenerationStrategy;
193            this.local = local;
194        }
195    
196        @Nullable
197        @Override
198        public Result<LightClassStubWithData> compute() {
199            FqName packageFqName = stubGenerationStrategy.getPackageFqName();
200            Collection<JetFile> files = stubGenerationStrategy.getFiles();
201    
202            checkForBuiltIns(packageFqName, files);
203    
204            LightClassConstructionContext context = stubGenerationStrategy.createLightClassConstructionContext(files);
205            Throwable error = context.getError();
206            if (error != null) {
207                throw new IllegalStateException("failed to analyze: " + error, error);
208            }
209    
210            PsiJavaFileStub javaFileStub = createJavaFileStub(packageFqName, getRepresentativeVirtualFile(files));
211            BindingContext bindingContext;
212            try {
213                Stack<StubElement> stubStack = new Stack<StubElement>();
214                stubStack.push(javaFileStub);
215    
216                GenerationState state = new GenerationState(
217                        project,
218                        new KotlinLightClassBuilderFactory(stubStack),
219                        Progress.DEAF,
220                        context.getBindingContext(),
221                        Lists.newArrayList(files),
222                        /*not-null assertions*/false, false,
223                        /*generateDeclaredClasses=*/stubGenerationStrategy.generateDeclaredClasses(),
224                        InlineUtil.DEFAULT_INLINE_FLAG_FOR_STUB);
225                state.beforeCompile();
226    
227                bindingContext = state.getBindingContext();
228    
229                stubGenerationStrategy.generate(state, files);
230    
231                StubElement pop = stubStack.pop();
232                if (pop != javaFileStub) {
233                    LOG.error("Unbalanced stack operations: " + pop);
234                }
235    
236            }
237            catch (ProcessCanceledException e) {
238                throw e;
239            }
240            catch (RuntimeException e) {
241                logErrorWithOSInfo(e, packageFqName, null);
242                throw e;
243            }
244    
245            return Result.create(
246                    stubGenerationStrategy.createLightClassStubWithData(javaFileStub, bindingContext),
247                    local ? PsiModificationTracker.MODIFICATION_COUNT : PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT
248            );
249        }
250    
251        @NotNull
252        private PsiJavaFileStub createJavaFileStub(@NotNull final FqName packageFqName, @NotNull VirtualFile virtualFile) {
253            PsiManager manager = PsiManager.getInstance(project);
254    
255            final PsiJavaFileStubImpl javaFileStub = new PsiJavaFileStubImpl(packageFqName.asString(), true);
256            javaFileStub.setPsiFactory(new ClsWrapperStubPsiFactory());
257    
258            ClsFileImpl fakeFile =
259                    new ClsFileImpl((PsiManagerImpl) manager, new ClassFileViewProvider(manager, virtualFile)) {
260                        @NotNull
261                        @Override
262                        public PsiClassHolderFileStub getStub() {
263                            return javaFileStub;
264                        }
265    
266                        @NotNull
267                        @Override
268                        public String getPackageName() {
269                            return packageFqName.asString();
270                        }
271                    };
272    
273            fakeFile.setPhysical(false);
274            javaFileStub.setPsi(fakeFile);
275            return javaFileStub;
276        }
277    
278        @NotNull
279        private static VirtualFile getRepresentativeVirtualFile(@NotNull Collection<JetFile> files) {
280            JetFile firstFile = files.iterator().next();
281            VirtualFile virtualFile = files.size() == 1 ? firstFile.getVirtualFile() : new LightVirtualFile();
282            assert virtualFile != null : "No virtual file for " + firstFile;
283            return virtualFile;
284        }
285    
286        private static void checkForBuiltIns(@NotNull FqName fqName, @NotNull Collection<JetFile> files) {
287            for (JetFile file : files) {
288                if (LightClassUtil.belongsToKotlinBuiltIns(file)) {
289                    // We may not fail later due to some luck, but generating JetLightClasses for built-ins is a bad idea anyways
290                    // If it fails later, there will be an exception logged
291                    logErrorWithOSInfo(null, fqName, file.getVirtualFile());
292                }
293            }
294        }
295    
296        private static void logErrorWithOSInfo(@Nullable Throwable cause, @NotNull FqName fqName, @Nullable VirtualFile virtualFile) {
297            String path = virtualFile == null ? "<null>" : virtualFile.getPath();
298            LOG.error(
299                    "Could not generate LightClass for " + fqName + " declared in " + path + "\n" +
300                    "built-ins dir URL is " + LightClassUtil.getBuiltInsDirUrl() + "\n" +
301                    "System: " + SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION + " Java Runtime: " + SystemInfo.JAVA_RUNTIME_VERSION,
302                    cause);
303        }
304    
305        private interface StubGenerationStrategy {
306            @NotNull LightClassConstructionContext createLightClassConstructionContext(@NotNull Collection<JetFile> files);
307            @NotNull
308            LightClassStubWithData createLightClassStubWithData(PsiJavaFileStub javaFileStub, BindingContext bindingContext);
309            @NotNull Collection<JetFile> getFiles();
310            @NotNull FqName getPackageFqName();
311            boolean generateDeclaredClasses();
312            void generate(@NotNull GenerationState state, @NotNull Collection<JetFile> files);
313    
314            abstract class NoDeclaredClasses implements StubGenerationStrategy {
315                @Override
316                public boolean generateDeclaredClasses() {
317                    return false;
318                }
319    
320                @Override
321                public String toString() {
322                    // For subclasses to be identifiable in the debugger
323                    return NoDeclaredClasses.class.getSimpleName();
324                }
325            }
326    
327            abstract class WithDeclaredClasses implements StubGenerationStrategy {
328                @Override
329                public boolean generateDeclaredClasses() {
330                    return true;
331                }
332    
333                @Override
334                public String toString() {
335                    // For subclasses to be identifiable in the debugger
336                    return WithDeclaredClasses.class.getSimpleName();
337                }
338            }
339        }
340    }