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().files();
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().files();
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 }