001 /*
002 * Copyright 2010-2015 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.kotlin.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.compiled.ClsFileImpl;
030 import com.intellij.psi.impl.java.stubs.PsiJavaFileStub;
031 import com.intellij.psi.impl.java.stubs.impl.PsiJavaFileStubImpl;
032 import com.intellij.psi.search.GlobalSearchScope;
033 import com.intellij.psi.stubs.PsiClassHolderFileStub;
034 import com.intellij.psi.stubs.StubElement;
035 import com.intellij.psi.util.CachedValueProvider;
036 import com.intellij.psi.util.PsiModificationTracker;
037 import com.intellij.psi.util.PsiTreeUtil;
038 import com.intellij.util.containers.ContainerUtil;
039 import com.intellij.util.containers.Stack;
040 import kotlin.jvm.functions.Function0;
041 import org.jetbrains.annotations.NotNull;
042 import org.jetbrains.annotations.Nullable;
043 import org.jetbrains.kotlin.codegen.CompilationErrorHandler;
044 import org.jetbrains.kotlin.codegen.KotlinCodegenFacade;
045 import org.jetbrains.kotlin.codegen.MultifileClassCodegen;
046 import org.jetbrains.kotlin.codegen.PackageCodegen;
047 import org.jetbrains.kotlin.codegen.binding.CodegenBinding;
048 import org.jetbrains.kotlin.codegen.state.GenerationState;
049 import org.jetbrains.kotlin.descriptors.ClassDescriptor;
050 import org.jetbrains.kotlin.fileClasses.JvmFileClassInfo;
051 import org.jetbrains.kotlin.fileClasses.NoResolveFileClassesProvider;
052 import org.jetbrains.kotlin.name.FqName;
053 import org.jetbrains.kotlin.psi.JetClassOrObject;
054 import org.jetbrains.kotlin.psi.JetFile;
055 import org.jetbrains.kotlin.psi.JetPsiUtil;
056 import org.jetbrains.kotlin.psi.JetScript;
057 import org.jetbrains.kotlin.resolve.BindingContext;
058 import org.jetbrains.kotlin.resolve.BindingTraceContext;
059 import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics;
060 import org.jetbrains.kotlin.resolve.jvm.JvmClassName;
061 import org.jetbrains.org.objectweb.asm.Type;
062
063 import java.util.Collection;
064 import java.util.Collections;
065 import java.util.Map;
066
067 import static org.jetbrains.kotlin.resolve.DescriptorToSourceUtils.descriptorToDeclaration;
068
069 public class KotlinJavaFileStubProvider<T extends WithFileStubAndExtraDiagnostics> implements CachedValueProvider<T> {
070
071 @NotNull
072 public static KotlinJavaFileStubProvider<KotlinFacadeLightClassData> createForPackageClass(
073 @NotNull final Project project,
074 @NotNull final FqName packageFqName,
075 @NotNull final GlobalSearchScope searchScope
076 ) {
077 return new KotlinJavaFileStubProvider<KotlinFacadeLightClassData>(
078 project,
079 false,
080 new StubGenerationStrategy<KotlinFacadeLightClassData>() {
081 @NotNull
082 @Override
083 public LightClassConstructionContext getContext(@NotNull Collection<JetFile> files) {
084 return LightClassGenerationSupport.getInstance(project).getContextForPackage(files);
085 }
086
087 @NotNull
088 @Override
089 public Collection<JetFile> getFiles() {
090 // Don't memoize this, it can be called again after an out-of-code-block modification occurs,
091 // and the set of files changes
092 return LightClassGenerationSupport.getInstance(project).findFilesForPackage(packageFqName, searchScope);
093 }
094
095 @NotNull
096 @Override
097 public KotlinFacadeLightClassData createLightClassData(
098 PsiJavaFileStub javaFileStub,
099 BindingContext bindingContext,
100 Diagnostics extraDiagnostics
101 ) {
102 return new KotlinFacadeLightClassData(javaFileStub, extraDiagnostics);
103 }
104
105 @NotNull
106 @Override
107 public FqName getPackageFqName() {
108 return packageFqName;
109 }
110
111 @Override
112 public GenerationState.GenerateClassFilter getGenerateClassFilter() {
113 return new GenerationState.GenerateClassFilter() {
114
115 @Override
116 public boolean shouldGeneratePackagePart(JetFile jetFile) {
117 return true;
118 }
119
120 @Override
121 public boolean shouldAnnotateClass(JetClassOrObject classOrObject) {
122 return shouldGenerateClass(classOrObject);
123 }
124
125 @Override
126 public boolean shouldGenerateClass(JetClassOrObject classOrObject) {
127 // Top-level classes and such should not be generated for performance reasons.
128 // Local classes in top-level functions must still be generated
129 return JetPsiUtil.isLocal(classOrObject);
130 }
131
132 @Override
133 public boolean shouldGenerateScript(JetScript script) {
134 // Scripts yield top-level classes, and should not be generated
135 return false;
136 }
137 };
138 }
139
140 @Override
141 public void generate(@NotNull GenerationState state, @NotNull Collection<JetFile> files) {
142 PackageCodegen codegen = state.getFactory().forPackage(packageFqName, files);
143 codegen.generate(CompilationErrorHandler.THROW_EXCEPTION);
144 state.getFactory().asList();
145 }
146
147 @Override
148 public String toString() {
149 return StubGenerationStrategy.class.getName() + " for package class";
150 }
151 }
152 );
153 }
154
155 @NotNull
156 public static CachedValueProvider<KotlinFacadeLightClassData> createForFacadeClass(
157 @NotNull final Project project,
158 @NotNull final FqName facadeFqName,
159 @NotNull final GlobalSearchScope searchScope
160 ) {
161 return new KotlinJavaFileStubProvider<KotlinFacadeLightClassData>(
162 project,
163 false,
164 new StubGenerationStrategy<KotlinFacadeLightClassData>() {
165 @NotNull
166 @Override
167 public Collection<JetFile> getFiles() {
168 return LightClassGenerationSupport.getInstance(project).findFilesForFacade(facadeFqName, searchScope);
169 }
170
171 @NotNull
172 @Override
173 public FqName getPackageFqName() {
174 return facadeFqName.parent();
175 }
176
177 @NotNull
178 @Override
179 public LightClassConstructionContext getContext(@NotNull Collection<JetFile> files) {
180 return LightClassGenerationSupport.getInstance(project).getContextForFacade(files);
181 }
182
183 @NotNull
184 @Override
185 public KotlinFacadeLightClassData createLightClassData(
186 PsiJavaFileStub javaFileStub,
187 BindingContext bindingContext,
188 Diagnostics extraDiagnostics
189 ) {
190 return new KotlinFacadeLightClassData(javaFileStub, extraDiagnostics);
191 }
192
193 @Override
194 public GenerationState.GenerateClassFilter getGenerateClassFilter() {
195 return new GenerationState.GenerateClassFilter() {
196 @Override
197 public boolean shouldAnnotateClass(JetClassOrObject classOrObject) {
198 return shouldGenerateClass(classOrObject);
199 }
200
201 @Override
202 public boolean shouldGenerateClass(JetClassOrObject classOrObject) {
203 return JetPsiUtil.isLocal(classOrObject);
204 }
205
206 @Override
207 public boolean shouldGeneratePackagePart(JetFile jetFile) {
208 return true;
209 }
210
211 @Override
212 public boolean shouldGenerateScript(JetScript script) {
213 return false;
214 }
215 };
216 }
217
218 @Override
219 public void generate(@NotNull GenerationState state, @NotNull Collection<JetFile> files) {
220 if (!files.isEmpty()) {
221 JetFile representativeFile = files.iterator().next();
222 JvmFileClassInfo fileClassInfo = NoResolveFileClassesProvider.INSTANCE$.getFileClassInfo(representativeFile);
223 if (!fileClassInfo.getIsMultifileClass()) {
224 PackageCodegen codegen = state.getFactory().forPackage(representativeFile.getPackageFqName(), files);
225 codegen.generate(CompilationErrorHandler.THROW_EXCEPTION);
226 state.getFactory().asList();
227 return;
228 }
229 }
230
231 MultifileClassCodegen codegen = state.getFactory().forMultifileClass(facadeFqName, files);
232 codegen.generate(CompilationErrorHandler.THROW_EXCEPTION);
233 state.getFactory().asList();
234 }
235
236 @Override
237 public String toString() {
238 return StubGenerationStrategy.class.getName() + " for facade class";
239 }
240 });
241 }
242
243 @NotNull
244 public static KotlinJavaFileStubProvider<OutermostKotlinClassLightClassData> createForDeclaredClass(@NotNull final JetClassOrObject classOrObject) {
245 return new KotlinJavaFileStubProvider<OutermostKotlinClassLightClassData>(
246 classOrObject.getProject(),
247 classOrObject.isLocal(),
248 new StubGenerationStrategy<OutermostKotlinClassLightClassData>() {
249 private JetFile getFile() {
250 return classOrObject.getContainingJetFile();
251 }
252
253 @NotNull
254 @Override
255 public LightClassConstructionContext getContext(@NotNull Collection<JetFile> files) {
256 return LightClassGenerationSupport.getInstance(classOrObject.getProject()).getContextForClassOrObject(classOrObject);
257 }
258
259 @NotNull
260 @Override
261 public OutermostKotlinClassLightClassData createLightClassData(
262 PsiJavaFileStub javaFileStub,
263 BindingContext bindingContext,
264 Diagnostics extraDiagnostics
265 ) {
266 ClassDescriptor classDescriptor = bindingContext.get(BindingContext.CLASS, classOrObject);
267 if (classDescriptor == null) {
268 return new OutermostKotlinClassLightClassData(
269 javaFileStub, extraDiagnostics, FqName.ROOT, classOrObject,
270 Collections.<JetClassOrObject, InnerKotlinClassLightClassData>emptyMap()
271 );
272 }
273
274 FqName fqName = predictClassFqName(bindingContext, classDescriptor);
275 Collection<ClassDescriptor> allInnerClasses = CodegenBinding.getAllInnerClasses(bindingContext, classDescriptor);
276
277 Map<JetClassOrObject, InnerKotlinClassLightClassData> innerClassesMap = ContainerUtil.newHashMap();
278 for (ClassDescriptor innerClassDescriptor : allInnerClasses) {
279 PsiElement declaration = descriptorToDeclaration(innerClassDescriptor);
280 if (!(declaration instanceof JetClassOrObject)) continue;
281 JetClassOrObject innerClass = (JetClassOrObject) declaration;
282
283 InnerKotlinClassLightClassData innerLightClassData = new InnerKotlinClassLightClassData(
284 predictClassFqName(bindingContext, innerClassDescriptor),
285 innerClass
286 );
287
288 innerClassesMap.put(innerClass, innerLightClassData);
289 }
290
291 return new OutermostKotlinClassLightClassData(
292 javaFileStub,
293 extraDiagnostics,
294 fqName,
295 classOrObject,
296 innerClassesMap
297 );
298 }
299
300 @NotNull
301 private FqName predictClassFqName(BindingContext bindingContext, ClassDescriptor classDescriptor) {
302 Type asmType = CodegenBinding.getAsmType(bindingContext, classDescriptor);
303 //noinspection ConstantConditions
304 return JvmClassName.byInternalName(asmType.getClassName().replace('.', '/')).getFqNameForClassNameWithoutDollars();
305 }
306
307 @NotNull
308 @Override
309 public Collection<JetFile> getFiles() {
310 return Collections.singletonList(getFile());
311 }
312
313 @NotNull
314 @Override
315 public FqName getPackageFqName() {
316 return getFile().getPackageFqName();
317 }
318
319 @Override
320 public GenerationState.GenerateClassFilter getGenerateClassFilter() {
321 return new GenerationState.GenerateClassFilter() {
322
323 @Override
324 public boolean shouldGeneratePackagePart(JetFile jetFile) {
325 return true;
326 }
327
328 @Override
329 public boolean shouldAnnotateClass(JetClassOrObject classOrObject) {
330 return shouldGenerateClass(classOrObject);
331 }
332
333 @Override
334 public boolean shouldGenerateClass(JetClassOrObject generatedClassOrObject) {
335 // Trivial: generate and analyze class we are interested in.
336 if (generatedClassOrObject == classOrObject) return true;
337
338 // Process all parent classes as they are context for current class
339 // Process child classes because they probably affect members (heuristic)
340 if (PsiTreeUtil.isAncestor(generatedClassOrObject, classOrObject, true) ||
341 PsiTreeUtil.isAncestor(classOrObject, generatedClassOrObject, true)) {
342 return true;
343 }
344
345 if (generatedClassOrObject.isLocal() && classOrObject.isLocal()) {
346 // Local classes should be process by CodegenAnnotatingVisitor to
347 // decide what class they should be placed in.
348 //
349 // Example:
350 // class A
351 // fun foo() {
352 // trait Z: A {}
353 // fun bar() {
354 // class <caret>O2: Z {}
355 // }
356 // }
357
358 // TODO: current method will process local classes in irrelevant declarations, it should be fixed.
359 PsiElement commonParent = PsiTreeUtil.findCommonParent(generatedClassOrObject, classOrObject);
360 return commonParent != null && !(commonParent instanceof PsiFile);
361 }
362
363 return false;
364 }
365
366 @Override
367 public boolean shouldGenerateScript(JetScript script) {
368 // We generate all enclosing classes
369 return PsiTreeUtil.isAncestor(script, classOrObject, false);
370 }
371 };
372 }
373
374 @Override
375 public void generate(@NotNull GenerationState state, @NotNull Collection<JetFile> files) {
376 PackageCodegen packageCodegen = state.getFactory().forPackage(getPackageFqName(), files);
377 packageCodegen.generateClassOrObject(classOrObject);
378 state.getFactory().asList();
379 }
380
381 @Override
382 public String toString() {
383 return StubGenerationStrategy.class.getName() + " for explicit class " + classOrObject.getName();
384 }
385 }
386 );
387 }
388
389 private static final Logger LOG = Logger.getInstance(KotlinJavaFileStubProvider.class);
390
391 private final Project project;
392 private final StubGenerationStrategy<T> stubGenerationStrategy;
393 private final boolean local;
394
395 private KotlinJavaFileStubProvider(
396 @NotNull Project project,
397 boolean local,
398 @NotNull StubGenerationStrategy<T> stubGenerationStrategy
399 ) {
400 this.project = project;
401 this.stubGenerationStrategy = stubGenerationStrategy;
402 this.local = local;
403 }
404
405 @Nullable
406 @Override
407 public Result<T> compute() {
408 FqName packageFqName = stubGenerationStrategy.getPackageFqName();
409 Collection<JetFile> files = stubGenerationStrategy.getFiles();
410
411 checkForBuiltIns(packageFqName, files);
412
413 LightClassConstructionContext context = stubGenerationStrategy.getContext(files);
414
415 PsiJavaFileStub javaFileStub = createJavaFileStub(packageFqName, files);
416 BindingContext bindingContext;
417 BindingTraceContext forExtraDiagnostics = new BindingTraceContext();
418 try {
419 Stack<StubElement> stubStack = new Stack<StubElement>();
420 stubStack.push(javaFileStub);
421
422 GenerationState state = new GenerationState(
423 project,
424 new KotlinLightClassBuilderFactory(stubStack),
425 context.getModule(),
426 context.getBindingContext(),
427 Lists.newArrayList(files),
428 /*disable not-null assertions*/false, false,
429 /*generateClassFilter=*/stubGenerationStrategy.getGenerateClassFilter(),
430 /*disableInline=*/false,
431 /*disableOptimization=*/false,
432 /*packageFacadesAsMultifileClasses=*/false,
433 forExtraDiagnostics
434 );
435 KotlinCodegenFacade.prepareForCompilation(state);
436
437 bindingContext = state.getBindingContext();
438
439 stubGenerationStrategy.generate(state, files);
440
441 StubElement pop = stubStack.pop();
442 if (pop != javaFileStub) {
443 LOG.error("Unbalanced stack operations: " + pop);
444 }
445 }
446 catch (ProcessCanceledException e) {
447 throw e;
448 }
449 catch (RuntimeException e) {
450 logErrorWithOSInfo(e, packageFqName, null);
451 throw e;
452 }
453
454 Diagnostics extraDiagnostics = forExtraDiagnostics.getBindingContext().getDiagnostics();
455 return Result.create(
456 stubGenerationStrategy.createLightClassData(javaFileStub, bindingContext, extraDiagnostics),
457 local ? PsiModificationTracker.MODIFICATION_COUNT : PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT
458 );
459 }
460
461 @NotNull
462 public static ClsFileImpl createFakeClsFile(
463 @NotNull Project project,
464 @NotNull final FqName packageFqName,
465 @NotNull Collection<JetFile> files,
466 @NotNull final Function0<? extends PsiClassHolderFileStub> fileStubProvider
467 ) {
468 PsiManager manager = PsiManager.getInstance(project);
469
470 VirtualFile virtualFile = getRepresentativeVirtualFile(files);
471 ClsFileImpl fakeFile = new ClsFileImpl(new ClassFileViewProvider(manager, virtualFile)) {
472 @NotNull
473 @Override
474 public PsiClassHolderFileStub getStub() {
475 return fileStubProvider.invoke();
476 }
477
478 @NotNull
479 @Override
480 public String getPackageName() {
481 return packageFqName.asString();
482 }
483 };
484
485 fakeFile.setPhysical(false);
486 return fakeFile;
487 }
488
489 @NotNull
490 private PsiJavaFileStub createJavaFileStub(@NotNull FqName packageFqName, @NotNull Collection<JetFile> files) {
491 final PsiJavaFileStubImpl javaFileStub = new PsiJavaFileStubImpl(packageFqName.asString(), true);
492 javaFileStub.setPsiFactory(new ClsWrapperStubPsiFactory());
493
494 ClsFileImpl fakeFile = createFakeClsFile(project, packageFqName, files, new Function0<PsiClassHolderFileStub>() {
495 @Override
496 public PsiClassHolderFileStub invoke() {
497 return javaFileStub;
498 }
499 });
500
501 javaFileStub.setPsi(fakeFile);
502 return javaFileStub;
503 }
504
505 @NotNull
506 private static VirtualFile getRepresentativeVirtualFile(@NotNull Collection<JetFile> files) {
507 JetFile firstFile = files.iterator().next();
508 VirtualFile virtualFile = firstFile.getVirtualFile();
509 assert virtualFile != null : "No virtual file for " + firstFile;
510 return virtualFile;
511 }
512
513 private static void checkForBuiltIns(@NotNull FqName fqName, @NotNull Collection<JetFile> files) {
514 for (JetFile file : files) {
515 if (LightClassUtil.INSTANCE$.belongsToKotlinBuiltIns(file)) {
516 // We may not fail later due to some luck, but generating JetLightClasses for built-ins is a bad idea anyways
517 // If it fails later, there will be an exception logged
518 logErrorWithOSInfo(null, fqName, file.getVirtualFile());
519 }
520 }
521 }
522
523 private static void logErrorWithOSInfo(@Nullable Throwable cause, @NotNull FqName fqName, @Nullable VirtualFile virtualFile) {
524 String path = virtualFile == null ? "<null>" : virtualFile.getPath();
525 LOG.error(
526 "Could not generate LightClass for " + fqName + " declared in " + path + "\n" +
527 "built-ins dir URL is " + LightClassUtil.INSTANCE$.getBuiltInsDirUrl() + "\n" +
528 "System: " + SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION + " Java Runtime: " + SystemInfo.JAVA_RUNTIME_VERSION,
529 cause);
530 }
531
532 private interface StubGenerationStrategy<T extends WithFileStubAndExtraDiagnostics> {
533 @NotNull Collection<JetFile> getFiles();
534 @NotNull FqName getPackageFqName();
535
536 @NotNull LightClassConstructionContext getContext(@NotNull Collection<JetFile> files);
537 @NotNull T createLightClassData(PsiJavaFileStub javaFileStub, BindingContext bindingContext, Diagnostics extraDiagnostics);
538
539 GenerationState.GenerateClassFilter getGenerateClassFilter();
540 void generate(@NotNull GenerationState state, @NotNull Collection<JetFile> files);
541 }
542 }