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.Stack;
038    import org.jetbrains.annotations.NotNull;
039    import org.jetbrains.annotations.Nullable;
040    import org.jetbrains.jet.codegen.BuiltinToJavaTypesMapping;
041    import org.jetbrains.jet.codegen.CompilationErrorHandler;
042    import org.jetbrains.jet.codegen.NamespaceCodegen;
043    import org.jetbrains.jet.codegen.state.GenerationState;
044    import org.jetbrains.jet.codegen.state.Progress;
045    import org.jetbrains.jet.lang.psi.JetClassOrObject;
046    import org.jetbrains.jet.lang.psi.JetFile;
047    import org.jetbrains.jet.lang.psi.JetPsiUtil;
048    import org.jetbrains.jet.lang.resolve.name.FqName;
049    
050    import java.util.Collection;
051    import java.util.Collections;
052    
053    public class KotlinJavaFileStubProvider implements CachedValueProvider<PsiJavaFileStub> {
054    
055        @NotNull
056        public static KotlinJavaFileStubProvider createForPackageClass(
057                @NotNull final Project project,
058                @NotNull final FqName packageFqName,
059                @NotNull final GlobalSearchScope searchScope
060        ) {
061            return new KotlinJavaFileStubProvider(project, new StubGenerationStrategy.NoDeclaredClasses() {
062    
063                @NotNull
064                @Override
065                public Collection<JetFile> getFiles() {
066                    // Don't memoize this, it can be called again after an out-of-code-block modification occurs,
067                    // and the set of files changes
068                    return LightClassGenerationSupport.getInstance(project).findFilesForPackage(packageFqName, searchScope);
069                }
070    
071                @NotNull
072                @Override
073                public FqName getPackageFqName() {
074                    return packageFqName;
075                }
076    
077                @Override
078                public void generate(@NotNull GenerationState state, @NotNull Collection<JetFile> files) {
079                    NamespaceCodegen codegen = state.getFactory().forNamespace(packageFqName, files);
080                    codegen.generate(CompilationErrorHandler.THROW_EXCEPTION);
081                    state.getFactory().files();
082                }
083            });
084        }
085    
086        @NotNull
087        public static KotlinJavaFileStubProvider createForDeclaredTopLevelClass(@NotNull final JetClassOrObject classOrObject) {
088            return new KotlinJavaFileStubProvider(classOrObject.getProject(), new StubGenerationStrategy.WithDeclaredClasses() {
089                private JetFile getFile() {
090                    JetFile file = (JetFile) classOrObject.getContainingFile();
091                    assert classOrObject.getParent() == file : "Not a top-level class: " + classOrObject.getText();
092                    return file;
093                }
094    
095                @NotNull
096                @Override
097                public Collection<JetFile> getFiles() {
098                    return Collections.singletonList(getFile());
099                }
100    
101                @NotNull
102                @Override
103                public FqName getPackageFqName() {
104                    return JetPsiUtil.getFQName(getFile());
105                }
106    
107                @Override
108                public void generate(@NotNull GenerationState state, @NotNull Collection<JetFile> files) {
109                    NamespaceCodegen namespaceCodegen = state.getFactory().forNamespace(getPackageFqName(), files);
110                    namespaceCodegen.generateClassOrObject(classOrObject);
111                    state.getFactory().files();
112                }
113            });
114        }
115    
116        private static final Logger LOG = Logger.getInstance(KotlinJavaFileStubProvider.class);
117    
118        private final Project project;
119        private final StubGenerationStrategy stubGenerationStrategy;
120    
121        private KotlinJavaFileStubProvider(
122                @NotNull Project project,
123                @NotNull StubGenerationStrategy stubGenerationStrategy
124        ) {
125            this.project = project;
126            this.stubGenerationStrategy = stubGenerationStrategy;
127        }
128    
129        @Nullable
130        @Override
131        public Result<PsiJavaFileStub> compute() {
132            FqName packageFqName = stubGenerationStrategy.getPackageFqName();
133            Collection<JetFile> files = stubGenerationStrategy.getFiles();
134    
135            checkForBuiltIns(packageFqName, files);
136    
137            LightClassConstructionContext context = LightClassGenerationSupport.getInstance(project).analyzeRelevantCode(files);
138    
139            Throwable error = context.getError();
140            if (error != null) {
141                throw new IllegalStateException("failed to analyze: " + error, error);
142            }
143    
144            PsiJavaFileStub javaFileStub = createJavaFileStub(packageFqName, getRepresentativeVirtualFile(files));
145            try {
146                Stack<StubElement> stubStack = new Stack<StubElement>();
147                stubStack.push(javaFileStub);
148    
149                GenerationState state = new GenerationState(
150                        project,
151                        new KotlinLightClassBuilderFactory(stubStack),
152                        Progress.DEAF,
153                        context.getBindingContext(),
154                        Lists.newArrayList(files),
155                        BuiltinToJavaTypesMapping.ENABLED,
156                        /*not-null assertions*/false, false,
157                        /*generateDeclaredClasses=*/stubGenerationStrategy.generateDeclaredClasses());
158                state.beforeCompile();
159    
160                stubGenerationStrategy.generate(state, files);
161    
162                StubElement pop = stubStack.pop();
163                if (pop != javaFileStub) {
164                    LOG.error("Unbalanced stack operations: " + pop);
165                }
166    
167            }
168            catch (ProcessCanceledException e) {
169                throw e;
170            }
171            catch (RuntimeException e) {
172                logErrorWithOSInfo(e, packageFqName, null);
173                throw e;
174            }
175    
176            return Result.create(javaFileStub, PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT);
177        }
178    
179        @NotNull
180        private PsiJavaFileStub createJavaFileStub(@NotNull final FqName packageFqName, @NotNull VirtualFile virtualFile) {
181            PsiManager manager = PsiManager.getInstance(project);
182    
183            final PsiJavaFileStubImpl javaFileStub = new PsiJavaFileStubImpl(packageFqName.asString(), true);
184            javaFileStub.setPsiFactory(new ClsWrapperStubPsiFactory());
185    
186            ClsFileImpl fakeFile =
187                    new ClsFileImpl((PsiManagerImpl) manager, new ClassFileViewProvider(manager, virtualFile)) {
188                        @NotNull
189                        @Override
190                        public PsiClassHolderFileStub getStub() {
191                            return javaFileStub;
192                        }
193    
194                        @NotNull
195                        @Override
196                        public String getPackageName() {
197                            return packageFqName.asString();
198                        }
199                    };
200    
201            fakeFile.setPhysical(false);
202            javaFileStub.setPsi(fakeFile);
203            return javaFileStub;
204        }
205    
206        @NotNull
207        private static VirtualFile getRepresentativeVirtualFile(@NotNull Collection<JetFile> files) {
208            JetFile firstFile = files.iterator().next();
209            VirtualFile virtualFile = files.size() == 1 ? firstFile.getVirtualFile() : new LightVirtualFile();
210            assert virtualFile != null : "No virtual file for " + firstFile;
211            return virtualFile;
212        }
213    
214        private static void checkForBuiltIns(@NotNull FqName fqName, @NotNull Collection<JetFile> files) {
215            for (JetFile file : files) {
216                if (LightClassUtil.belongsToKotlinBuiltIns(file)) {
217                    // We may not fail later due to some luck, but generating JetLightClasses for built-ins is a bad idea anyways
218                    // If it fails later, there will be an exception logged
219                    logErrorWithOSInfo(null, fqName, file.getVirtualFile());
220                }
221            }
222        }
223    
224        private static void logErrorWithOSInfo(@Nullable Throwable cause, @NotNull FqName fqName, @Nullable VirtualFile virtualFile) {
225            String path = virtualFile == null ? "<null>" : virtualFile.getPath();
226            LOG.error(
227                    "Could not generate LightClass for " + fqName + " declared in " + path + "\n" +
228                    "built-ins dir URL is " + LightClassUtil.getBuiltInsDirUrl() + "\n" +
229                    "System: " + SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION + " Java Runtime: " + SystemInfo.JAVA_RUNTIME_VERSION,
230                    cause);
231        }
232    
233        private interface StubGenerationStrategy {
234            @NotNull Collection<JetFile> getFiles();
235            @NotNull FqName getPackageFqName();
236            boolean generateDeclaredClasses();
237            void generate(@NotNull GenerationState state, @NotNull Collection<JetFile> files);
238    
239            abstract class NoDeclaredClasses implements StubGenerationStrategy {
240                @Override
241                public boolean generateDeclaredClasses() {
242                    return false;
243                }
244    
245                @Override
246                public String toString() {
247                    // For subclasses to be identifiable in the debugger
248                    return NoDeclaredClasses.class.getSimpleName();
249                }
250            }
251    
252            abstract class WithDeclaredClasses implements StubGenerationStrategy {
253                @Override
254                public boolean generateDeclaredClasses() {
255                    return true;
256                }
257    
258                @Override
259                public String toString() {
260                    // For subclasses to be identifiable in the debugger
261                    return WithDeclaredClasses.class.getSimpleName();
262                }
263            }
264        }
265    }