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