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