001 /*
002 * Copyright 2010-2014 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.PsiElement;
027 import com.intellij.psi.PsiFile;
028 import com.intellij.psi.PsiManager;
029 import com.intellij.psi.impl.PsiManagerImpl;
030 import com.intellij.psi.impl.compiled.ClsFileImpl;
031 import com.intellij.psi.impl.java.stubs.PsiJavaFileStub;
032 import com.intellij.psi.impl.java.stubs.impl.PsiJavaFileStubImpl;
033 import com.intellij.psi.search.GlobalSearchScope;
034 import com.intellij.psi.stubs.PsiClassHolderFileStub;
035 import com.intellij.psi.stubs.StubElement;
036 import com.intellij.psi.util.CachedValueProvider;
037 import com.intellij.psi.util.PsiModificationTracker;
038 import com.intellij.psi.util.PsiTreeUtil;
039 import com.intellij.testFramework.LightVirtualFile;
040 import com.intellij.util.containers.ContainerUtil;
041 import com.intellij.util.containers.Stack;
042 import org.jetbrains.annotations.NotNull;
043 import org.jetbrains.annotations.Nullable;
044 import org.jetbrains.jet.codegen.CompilationErrorHandler;
045 import org.jetbrains.jet.codegen.KotlinCodegenFacade;
046 import org.jetbrains.jet.codegen.PackageCodegen;
047 import org.jetbrains.jet.codegen.binding.CodegenBinding;
048 import org.jetbrains.jet.codegen.state.GenerationState;
049 import org.jetbrains.jet.codegen.state.Progress;
050 import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
051 import org.jetbrains.jet.lang.psi.JetClassOrObject;
052 import org.jetbrains.jet.lang.psi.JetFile;
053 import org.jetbrains.jet.lang.psi.JetPsiUtil;
054 import org.jetbrains.jet.lang.resolve.BindingContext;
055 import org.jetbrains.jet.lang.resolve.BindingTraceContext;
056 import org.jetbrains.jet.lang.resolve.Diagnostics;
057 import org.jetbrains.jet.lang.resolve.name.FqName;
058
059 import java.util.Collection;
060 import java.util.Collections;
061 import java.util.Map;
062
063 import static org.jetbrains.jet.lang.resolve.DescriptorToSourceUtils.descriptorToDeclaration;
064
065 public class KotlinJavaFileStubProvider<T extends WithFileStubAndExtraDiagnostics> implements CachedValueProvider<T> {
066
067 @NotNull
068 public static KotlinJavaFileStubProvider<KotlinPackageLightClassData> createForPackageClass(
069 @NotNull final Project project,
070 @NotNull final FqName packageFqName,
071 @NotNull final GlobalSearchScope searchScope
072 ) {
073 return new KotlinJavaFileStubProvider<KotlinPackageLightClassData>(
074 project,
075 false,
076 new StubGenerationStrategy<KotlinPackageLightClassData>() {
077 @NotNull
078 @Override
079 public LightClassConstructionContext getContext(@NotNull Collection<JetFile> files) {
080 return LightClassGenerationSupport.getInstance(project).getContextForPackage(files);
081 }
082
083 @NotNull
084 @Override
085 public Collection<JetFile> getFiles() {
086 // Don't memoize this, it can be called again after an out-of-code-block modification occurs,
087 // and the set of files changes
088 return LightClassGenerationSupport.getInstance(project).findFilesForPackage(packageFqName, searchScope);
089 }
090
091 @NotNull
092 @Override
093 public KotlinPackageLightClassData createLightClassData(
094 PsiJavaFileStub javaFileStub,
095 BindingContext bindingContext,
096 Diagnostics extraDiagnostics
097 ) {
098 return new KotlinPackageLightClassData(javaFileStub, extraDiagnostics);
099 }
100
101 @NotNull
102 @Override
103 public FqName getPackageFqName() {
104 return packageFqName;
105 }
106
107 @Override
108 public GenerationState.GenerateClassFilter getGenerateClassFilter() {
109 return new GenerationState.GenerateClassFilter() {
110 @Override
111 public boolean shouldProcess(JetClassOrObject classOrObject) {
112 // Top-level classes and such should not be generated for performance reasons.
113 // Local classes in top-level functions must still be generated
114 return JetPsiUtil.isLocal(classOrObject);
115 }
116 };
117 }
118
119 @Override
120 public void generate(@NotNull GenerationState state, @NotNull Collection<JetFile> files) {
121 PackageCodegen codegen = state.getFactory().forPackage(packageFqName, files);
122 codegen.generate(CompilationErrorHandler.THROW_EXCEPTION);
123 state.getFactory().asList();
124 }
125
126 @Override
127 public String toString() {
128 return StubGenerationStrategy.class.getName() + " for package class";
129 }
130 }
131 );
132 }
133
134 @NotNull
135 public static KotlinJavaFileStubProvider<OutermostKotlinClassLightClassData> createForDeclaredClass(@NotNull final JetClassOrObject classOrObject) {
136 return new KotlinJavaFileStubProvider<OutermostKotlinClassLightClassData>(
137 classOrObject.getProject(),
138 classOrObject.isLocal(),
139 new StubGenerationStrategy<OutermostKotlinClassLightClassData>() {
140 private JetFile getFile() {
141 return classOrObject.getContainingJetFile();
142 }
143
144 @NotNull
145 @Override
146 public LightClassConstructionContext getContext(@NotNull Collection<JetFile> files) {
147 return LightClassGenerationSupport.getInstance(classOrObject.getProject()).getContextForClassOrObject(classOrObject);
148 }
149
150 @NotNull
151 @Override
152 public OutermostKotlinClassLightClassData createLightClassData(
153 PsiJavaFileStub javaFileStub,
154 BindingContext bindingContext,
155 Diagnostics extraDiagnostics
156 ) {
157 ClassDescriptor classDescriptor = bindingContext.get(BindingContext.CLASS, classOrObject);
158 if (classDescriptor == null) {
159 return new OutermostKotlinClassLightClassData(
160 javaFileStub, extraDiagnostics,
161 "", classOrObject, null, Collections.<JetClassOrObject, InnerKotlinClassLightClassData>emptyMap()
162 );
163 }
164
165 String jvmInternalName = CodegenBinding.getJvmInternalName(bindingContext, classDescriptor);
166 Collection<ClassDescriptor> allInnerClasses = CodegenBinding.getAllInnerClasses(bindingContext, classDescriptor);
167
168 Map<JetClassOrObject, InnerKotlinClassLightClassData> innerClassesMap = ContainerUtil.newHashMap();
169 for (ClassDescriptor innerClassDescriptor : allInnerClasses) {
170 JetClassOrObject innerClass = (JetClassOrObject) descriptorToDeclaration(innerClassDescriptor);
171 if (innerClass == null) continue;
172
173 InnerKotlinClassLightClassData innerLightClassData = new InnerKotlinClassLightClassData(
174 CodegenBinding.getJvmInternalName(bindingContext, innerClassDescriptor),
175 innerClass,
176 innerClassDescriptor
177 );
178
179 innerClassesMap.put(innerClass, innerLightClassData);
180 }
181
182 return new OutermostKotlinClassLightClassData(
183 javaFileStub,
184 extraDiagnostics,
185 jvmInternalName,
186 classOrObject,
187 classDescriptor,
188 innerClassesMap
189 );
190 }
191
192 @NotNull
193 @Override
194 public Collection<JetFile> getFiles() {
195 return Collections.singletonList(getFile());
196 }
197
198 @NotNull
199 @Override
200 public FqName getPackageFqName() {
201 return getFile().getPackageFqName();
202 }
203
204 @Override
205 public GenerationState.GenerateClassFilter getGenerateClassFilter() {
206 return new GenerationState.GenerateClassFilter() {
207 @Override
208 public boolean shouldProcess(JetClassOrObject generatedClassOrObject) {
209 // Trivial: generate and analyze class we are interested in.
210 if (generatedClassOrObject == classOrObject) return true;
211
212 // Process all parent classes as they are context for current class
213 // Process child classes because they probably affect members (heuristic)
214 if (PsiTreeUtil.isAncestor(generatedClassOrObject, classOrObject, true) ||
215 PsiTreeUtil.isAncestor(classOrObject, generatedClassOrObject, true)) {
216 return true;
217 }
218
219 if (generatedClassOrObject.isLocal() && classOrObject.isLocal()) {
220 // Local classes should be process by CodegenAnnotatingVisitor to
221 // decide what class they should be placed in.
222 //
223 // Example:
224 // class A
225 // fun foo() {
226 // trait Z: A {}
227 // fun bar() {
228 // class <caret>O2: Z {}
229 // }
230 // }
231
232 // TODO: current method will process local classes in irrelevant declarations, it should be fixed.
233 PsiElement commonParent = PsiTreeUtil.findCommonParent(generatedClassOrObject, classOrObject);
234 return commonParent != null && !(commonParent instanceof PsiFile);
235 }
236
237 return false;
238 }
239 };
240 }
241
242 @Override
243 public void generate(@NotNull GenerationState state, @NotNull Collection<JetFile> files) {
244 PackageCodegen packageCodegen = state.getFactory().forPackage(getPackageFqName(), files);
245 packageCodegen.generateClassOrObject(classOrObject);
246 state.getFactory().asList();
247 }
248
249 @Override
250 public String toString() {
251 return StubGenerationStrategy.class.getName() + " for explicit class " + classOrObject.getName();
252 }
253 }
254 );
255 }
256
257 private static final Logger LOG = Logger.getInstance(KotlinJavaFileStubProvider.class);
258
259 private final Project project;
260 private final StubGenerationStrategy<T> stubGenerationStrategy;
261 private final boolean local;
262
263 private KotlinJavaFileStubProvider(
264 @NotNull Project project,
265 boolean local,
266 @NotNull StubGenerationStrategy<T> stubGenerationStrategy
267 ) {
268 this.project = project;
269 this.stubGenerationStrategy = stubGenerationStrategy;
270 this.local = local;
271 }
272
273 @Nullable
274 @Override
275 public Result<T> compute() {
276 FqName packageFqName = stubGenerationStrategy.getPackageFqName();
277 Collection<JetFile> files = stubGenerationStrategy.getFiles();
278
279 checkForBuiltIns(packageFqName, files);
280
281 LightClassConstructionContext context = stubGenerationStrategy.getContext(files);
282
283 PsiJavaFileStub javaFileStub = createJavaFileStub(packageFqName, getRepresentativeVirtualFile(files));
284 BindingContext bindingContext;
285 BindingTraceContext forExtraDiagnostics = new BindingTraceContext();
286 try {
287 Stack<StubElement> stubStack = new Stack<StubElement>();
288 stubStack.push(javaFileStub);
289
290 GenerationState state = new GenerationState(
291 project,
292 new KotlinLightClassBuilderFactory(stubStack),
293 Progress.DEAF,
294 context.getModule(),
295 context.getBindingContext(),
296 Lists.newArrayList(files),
297 /*not-null assertions*/false, false,
298 /*generateClassFilter=*/stubGenerationStrategy.getGenerateClassFilter(),
299 /*to generate inline flag on methods*/true,
300 /*optimize*/true,
301 null,
302 null,
303 forExtraDiagnostics,
304 null);
305 KotlinCodegenFacade.prepareForCompilation(state);
306
307 bindingContext = state.getBindingContext();
308
309 stubGenerationStrategy.generate(state, files);
310
311 StubElement pop = stubStack.pop();
312 if (pop != javaFileStub) {
313 LOG.error("Unbalanced stack operations: " + pop);
314 }
315 }
316 catch (ProcessCanceledException e) {
317 throw e;
318 }
319 catch (RuntimeException e) {
320 logErrorWithOSInfo(e, packageFqName, null);
321 throw e;
322 }
323
324 Diagnostics extraDiagnostics = forExtraDiagnostics.getBindingContext().getDiagnostics();
325 return Result.create(
326 stubGenerationStrategy.createLightClassData(javaFileStub, bindingContext, extraDiagnostics),
327 local ? PsiModificationTracker.MODIFICATION_COUNT : PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT
328 );
329 }
330
331 @NotNull
332 private PsiJavaFileStub createJavaFileStub(@NotNull final FqName packageFqName, @NotNull VirtualFile virtualFile) {
333 PsiManager manager = PsiManager.getInstance(project);
334
335 final PsiJavaFileStubImpl javaFileStub = new PsiJavaFileStubImpl(packageFqName.asString(), true);
336 javaFileStub.setPsiFactory(new ClsWrapperStubPsiFactory());
337
338 ClsFileImpl fakeFile =
339 new ClsFileImpl((PsiManagerImpl) manager, new ClassFileViewProvider(manager, virtualFile)) {
340 @NotNull
341 @Override
342 public PsiClassHolderFileStub getStub() {
343 return javaFileStub;
344 }
345
346 @NotNull
347 @Override
348 public String getPackageName() {
349 return packageFqName.asString();
350 }
351 };
352
353 fakeFile.setPhysical(false);
354 javaFileStub.setPsi(fakeFile);
355 return javaFileStub;
356 }
357
358 @NotNull
359 private static VirtualFile getRepresentativeVirtualFile(@NotNull Collection<JetFile> files) {
360 JetFile firstFile = files.iterator().next();
361 VirtualFile virtualFile = files.size() == 1 ? firstFile.getVirtualFile() : new LightVirtualFile();
362 assert virtualFile != null : "No virtual file for " + firstFile;
363 return virtualFile;
364 }
365
366 private static void checkForBuiltIns(@NotNull FqName fqName, @NotNull Collection<JetFile> files) {
367 for (JetFile file : files) {
368 if (LightClassUtil.belongsToKotlinBuiltIns(file)) {
369 // We may not fail later due to some luck, but generating JetLightClasses for built-ins is a bad idea anyways
370 // If it fails later, there will be an exception logged
371 logErrorWithOSInfo(null, fqName, file.getVirtualFile());
372 }
373 }
374 }
375
376 private static void logErrorWithOSInfo(@Nullable Throwable cause, @NotNull FqName fqName, @Nullable VirtualFile virtualFile) {
377 String path = virtualFile == null ? "<null>" : virtualFile.getPath();
378 LOG.error(
379 "Could not generate LightClass for " + fqName + " declared in " + path + "\n" +
380 "built-ins dir URL is " + LightClassUtil.getBuiltInsDirUrl() + "\n" +
381 "System: " + SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION + " Java Runtime: " + SystemInfo.JAVA_RUNTIME_VERSION,
382 cause);
383 }
384
385 private interface StubGenerationStrategy<T extends WithFileStubAndExtraDiagnostics> {
386 @NotNull Collection<JetFile> getFiles();
387 @NotNull FqName getPackageFqName();
388
389 @NotNull LightClassConstructionContext getContext(@NotNull Collection<JetFile> files);
390 @NotNull T createLightClassData(PsiJavaFileStub javaFileStub, BindingContext bindingContext, Diagnostics extraDiagnostics);
391
392 GenerationState.GenerateClassFilter getGenerateClassFilter();
393 void generate(@NotNull GenerationState state, @NotNull Collection<JetFile> files);
394 }
395 }