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
017package org.jetbrains.jet.asJava;
018
019import com.google.common.collect.Lists;
020import com.intellij.openapi.diagnostic.Logger;
021import com.intellij.openapi.progress.ProcessCanceledException;
022import com.intellij.openapi.project.Project;
023import com.intellij.openapi.util.SystemInfo;
024import com.intellij.openapi.vfs.VirtualFile;
025import com.intellij.psi.ClassFileViewProvider;
026import com.intellij.psi.PsiManager;
027import com.intellij.psi.impl.PsiManagerImpl;
028import com.intellij.psi.impl.compiled.ClsFileImpl;
029import com.intellij.psi.impl.java.stubs.PsiJavaFileStub;
030import com.intellij.psi.impl.java.stubs.impl.PsiJavaFileStubImpl;
031import com.intellij.psi.search.GlobalSearchScope;
032import com.intellij.psi.stubs.PsiClassHolderFileStub;
033import com.intellij.psi.stubs.StubElement;
034import com.intellij.psi.util.CachedValueProvider;
035import com.intellij.psi.util.PsiModificationTracker;
036import com.intellij.testFramework.LightVirtualFile;
037import com.intellij.util.containers.Stack;
038import org.jetbrains.annotations.NotNull;
039import org.jetbrains.annotations.Nullable;
040import org.jetbrains.jet.codegen.BuiltinToJavaTypesMapping;
041import org.jetbrains.jet.codegen.CompilationErrorHandler;
042import org.jetbrains.jet.codegen.NamespaceCodegen;
043import org.jetbrains.jet.codegen.state.GenerationState;
044import org.jetbrains.jet.codegen.state.Progress;
045import org.jetbrains.jet.lang.descriptors.NamespaceDescriptor;
046import org.jetbrains.jet.lang.psi.JetClassOrObject;
047import org.jetbrains.jet.lang.psi.JetFile;
048import org.jetbrains.jet.lang.psi.JetPsiUtil;
049import org.jetbrains.jet.lang.resolve.BindingContext;
050import org.jetbrains.jet.lang.resolve.name.FqName;
051
052import java.util.Collection;
053import java.util.Collections;
054
055public 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}