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.context.PackageContext; 049 import org.jetbrains.kotlin.codegen.state.GenerationState; 050 import org.jetbrains.kotlin.descriptors.ClassDescriptor; 051 import org.jetbrains.kotlin.fileClasses.FileClasses; 052 import org.jetbrains.kotlin.fileClasses.JvmFileClassInfo; 053 import org.jetbrains.kotlin.fileClasses.NoResolveFileClassesProvider; 054 import org.jetbrains.kotlin.name.FqName; 055 import org.jetbrains.kotlin.psi.KtClassOrObject; 056 import org.jetbrains.kotlin.psi.KtFile; 057 import org.jetbrains.kotlin.psi.KtPsiUtil; 058 import org.jetbrains.kotlin.psi.KtScript; 059 import org.jetbrains.kotlin.resolve.BindingContext; 060 import org.jetbrains.kotlin.resolve.BindingTraceContext; 061 import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics; 062 import org.jetbrains.kotlin.resolve.jvm.JvmClassName; 063 import org.jetbrains.org.objectweb.asm.Type; 064 065 import java.util.Collection; 066 import java.util.Collections; 067 import java.util.Map; 068 069 import static org.jetbrains.kotlin.resolve.DescriptorToSourceUtils.descriptorToDeclaration; 070 071 public class KotlinJavaFileStubProvider<T extends WithFileStubAndExtraDiagnostics> implements CachedValueProvider<T> { 072 073 @NotNull 074 public static KotlinJavaFileStubProvider<KotlinFacadeLightClassData> createForPackageClass( 075 @NotNull final Project project, 076 @NotNull final FqName packageFqName, 077 @NotNull final GlobalSearchScope searchScope 078 ) { 079 return new KotlinJavaFileStubProvider<KotlinFacadeLightClassData>( 080 project, 081 false, 082 new StubGenerationStrategy<KotlinFacadeLightClassData>() { 083 @NotNull 084 @Override 085 public LightClassConstructionContext getContext(@NotNull Collection<KtFile> files) { 086 return LightClassGenerationSupport.getInstance(project).getContextForPackage(files); 087 } 088 089 @NotNull 090 @Override 091 public Collection<KtFile> getFiles() { 092 // Don't memoize this, it can be called again after an out-of-code-block modification occurs, 093 // and the set of files changes 094 return LightClassGenerationSupport.getInstance(project).findFilesForPackage(packageFqName, searchScope); 095 } 096 097 @NotNull 098 @Override 099 public KotlinFacadeLightClassData createLightClassData( 100 PsiJavaFileStub javaFileStub, 101 BindingContext bindingContext, 102 Diagnostics extraDiagnostics 103 ) { 104 return new KotlinFacadeLightClassData(javaFileStub, extraDiagnostics); 105 } 106 107 @NotNull 108 @Override 109 public FqName getPackageFqName() { 110 return packageFqName; 111 } 112 113 @Override 114 public GenerationState.GenerateClassFilter getGenerateClassFilter() { 115 return new GenerationState.GenerateClassFilter() { 116 117 @Override 118 public boolean shouldGeneratePackagePart(KtFile jetFile) { 119 return true; 120 } 121 122 @Override 123 public boolean shouldAnnotateClass(KtClassOrObject classOrObject) { 124 return shouldGenerateClass(classOrObject); 125 } 126 127 @Override 128 public boolean shouldGenerateClass(KtClassOrObject classOrObject) { 129 // Top-level classes and such should not be generated for performance reasons. 130 // Local classes in top-level functions must still be generated 131 return KtPsiUtil.isLocal(classOrObject); 132 } 133 134 @Override 135 public boolean shouldGenerateScript(KtScript script) { 136 // Scripts yield top-level classes, and should not be generated 137 return false; 138 } 139 }; 140 } 141 142 @Override 143 public void generate(@NotNull GenerationState state, @NotNull Collection<KtFile> files) { 144 KotlinCodegenFacade.doGenerateFiles(files, state, CompilationErrorHandler.THROW_EXCEPTION); 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<KtFile> 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<KtFile> 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(KtClassOrObject classOrObject) { 198 return shouldGenerateClass(classOrObject); 199 } 200 201 @Override 202 public boolean shouldGenerateClass(KtClassOrObject classOrObject) { 203 return KtPsiUtil.isLocal(classOrObject); 204 } 205 206 @Override 207 public boolean shouldGeneratePackagePart(KtFile jetFile) { 208 return true; 209 } 210 211 @Override 212 public boolean shouldGenerateScript(KtScript script) { 213 return false; 214 } 215 }; 216 } 217 218 @Override 219 public void generate(@NotNull GenerationState state, @NotNull Collection<KtFile> files) { 220 if (!files.isEmpty()) { 221 KtFile representativeFile = files.iterator().next(); 222 JvmFileClassInfo fileClassInfo = NoResolveFileClassesProvider.INSTANCE$.getFileClassInfo(representativeFile); 223 if (!fileClassInfo.getWithJvmMultifileClass()) { 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 KtClassOrObject classOrObject) { 245 return new KotlinJavaFileStubProvider<OutermostKotlinClassLightClassData>( 246 classOrObject.getProject(), 247 classOrObject.isLocal(), 248 new StubGenerationStrategy<OutermostKotlinClassLightClassData>() { 249 private KtFile getFile() { 250 return classOrObject.getContainingJetFile(); 251 } 252 253 @NotNull 254 @Override 255 public LightClassConstructionContext getContext(@NotNull Collection<KtFile> 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.<KtClassOrObject, InnerKotlinClassLightClassData>emptyMap() 271 ); 272 } 273 274 FqName fqName = predictClassFqName(bindingContext, classDescriptor); 275 Collection<ClassDescriptor> allInnerClasses = CodegenBinding.getAllInnerClasses(bindingContext, classDescriptor); 276 277 Map<KtClassOrObject, InnerKotlinClassLightClassData> innerClassesMap = ContainerUtil.newHashMap(); 278 for (ClassDescriptor innerClassDescriptor : allInnerClasses) { 279 PsiElement declaration = descriptorToDeclaration(innerClassDescriptor); 280 if (!(declaration instanceof KtClassOrObject)) continue; 281 KtClassOrObject innerClass = (KtClassOrObject) 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<KtFile> 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(KtFile jetFile) { 325 return true; 326 } 327 328 @Override 329 public boolean shouldAnnotateClass(KtClassOrObject classOrObject) { 330 return shouldGenerateClass(classOrObject); 331 } 332 333 @Override 334 public boolean shouldGenerateClass(KtClassOrObject 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(KtScript 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<KtFile> files) { 376 PackageCodegen packageCodegen = state.getFactory().forPackage(getPackageFqName(), files); 377 KtFile file = classOrObject.getContainingJetFile(); 378 Type packagePartType = FileClasses.getFileClassType(state.getFileClassesProvider(), file); 379 PackageContext context = state.getRootContext().intoPackagePart(packageCodegen.getPackageFragment(), packagePartType, file); 380 packageCodegen.generateClassOrObject(classOrObject, context); 381 state.getFactory().asList(); 382 } 383 384 @Override 385 public String toString() { 386 return StubGenerationStrategy.class.getName() + " for explicit class " + classOrObject.getName(); 387 } 388 } 389 ); 390 } 391 392 private static final Logger LOG = Logger.getInstance(KotlinJavaFileStubProvider.class); 393 394 private final Project project; 395 private final StubGenerationStrategy<T> stubGenerationStrategy; 396 private final boolean local; 397 398 private KotlinJavaFileStubProvider( 399 @NotNull Project project, 400 boolean local, 401 @NotNull StubGenerationStrategy<T> stubGenerationStrategy 402 ) { 403 this.project = project; 404 this.stubGenerationStrategy = stubGenerationStrategy; 405 this.local = local; 406 } 407 408 @Nullable 409 @Override 410 public Result<T> compute() { 411 FqName packageFqName = stubGenerationStrategy.getPackageFqName(); 412 Collection<KtFile> files = stubGenerationStrategy.getFiles(); 413 414 checkForBuiltIns(packageFqName, files); 415 416 LightClassConstructionContext context = stubGenerationStrategy.getContext(files); 417 418 PsiJavaFileStub javaFileStub = createJavaFileStub(packageFqName, files); 419 BindingContext bindingContext; 420 BindingTraceContext forExtraDiagnostics = new BindingTraceContext(); 421 try { 422 Stack<StubElement> stubStack = new Stack<StubElement>(); 423 stubStack.push(javaFileStub); 424 425 GenerationState state = new GenerationState( 426 project, 427 new KotlinLightClassBuilderFactory(stubStack), 428 context.getModule(), 429 context.getBindingContext(), 430 Lists.newArrayList(files), 431 /*disable not-null assertions*/false, false, 432 /*generateClassFilter=*/stubGenerationStrategy.getGenerateClassFilter(), 433 /*disableInline=*/false, 434 /*disableOptimization=*/false, 435 /*useTypeTableInSerializer=*/false, 436 forExtraDiagnostics 437 ); 438 KotlinCodegenFacade.prepareForCompilation(state); 439 440 bindingContext = state.getBindingContext(); 441 442 stubGenerationStrategy.generate(state, files); 443 444 StubElement pop = stubStack.pop(); 445 if (pop != javaFileStub) { 446 LOG.error("Unbalanced stack operations: " + pop); 447 } 448 } 449 catch (ProcessCanceledException e) { 450 throw e; 451 } 452 catch (RuntimeException e) { 453 logErrorWithOSInfo(e, packageFqName, null); 454 throw e; 455 } 456 457 Diagnostics extraDiagnostics = forExtraDiagnostics.getBindingContext().getDiagnostics(); 458 return Result.create( 459 stubGenerationStrategy.createLightClassData(javaFileStub, bindingContext, extraDiagnostics), 460 local ? PsiModificationTracker.MODIFICATION_COUNT : PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT 461 ); 462 } 463 464 @NotNull 465 private static ClsFileImpl createFakeClsFile( 466 @NotNull Project project, 467 @NotNull final FqName packageFqName, 468 @NotNull Collection<KtFile> files, 469 @NotNull final Function0<? extends PsiClassHolderFileStub> fileStubProvider 470 ) { 471 PsiManager manager = PsiManager.getInstance(project); 472 473 VirtualFile virtualFile = getRepresentativeVirtualFile(files); 474 ClsFileImpl fakeFile = new ClsFileImpl(new ClassFileViewProvider(manager, virtualFile)) { 475 @NotNull 476 @Override 477 public PsiClassHolderFileStub getStub() { 478 return fileStubProvider.invoke(); 479 } 480 481 @NotNull 482 @Override 483 public String getPackageName() { 484 return packageFqName.asString(); 485 } 486 }; 487 488 fakeFile.setPhysical(false); 489 return fakeFile; 490 } 491 492 @NotNull 493 private PsiJavaFileStub createJavaFileStub(@NotNull FqName packageFqName, @NotNull Collection<KtFile> files) { 494 final PsiJavaFileStubImpl javaFileStub = new PsiJavaFileStubImpl(packageFqName.asString(), true); 495 javaFileStub.setPsiFactory(new ClsWrapperStubPsiFactory()); 496 497 ClsFileImpl fakeFile = createFakeClsFile(project, packageFqName, files, new Function0<PsiClassHolderFileStub>() { 498 @Override 499 public PsiClassHolderFileStub invoke() { 500 return javaFileStub; 501 } 502 }); 503 504 javaFileStub.setPsi(fakeFile); 505 return javaFileStub; 506 } 507 508 @NotNull 509 private static VirtualFile getRepresentativeVirtualFile(@NotNull Collection<KtFile> files) { 510 KtFile firstFile = files.iterator().next(); 511 VirtualFile virtualFile = firstFile.getVirtualFile(); 512 assert virtualFile != null : "No virtual file for " + firstFile; 513 return virtualFile; 514 } 515 516 private static void checkForBuiltIns(@NotNull FqName fqName, @NotNull Collection<KtFile> files) { 517 for (KtFile file : files) { 518 if (LightClassUtil.INSTANCE$.belongsToKotlinBuiltIns(file)) { 519 // We may not fail later due to some luck, but generating JetLightClasses for built-ins is a bad idea anyways 520 // If it fails later, there will be an exception logged 521 logErrorWithOSInfo(null, fqName, file.getVirtualFile()); 522 } 523 } 524 } 525 526 private static void logErrorWithOSInfo(@Nullable Throwable cause, @NotNull FqName fqName, @Nullable VirtualFile virtualFile) { 527 String path = virtualFile == null ? "<null>" : virtualFile.getPath(); 528 LOG.error( 529 "Could not generate LightClass for " + fqName + " declared in " + path + "\n" + 530 "built-ins dir URL is " + LightClassUtil.INSTANCE$.getBuiltInsDirUrl() + "\n" + 531 "System: " + SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION + " Java Runtime: " + SystemInfo.JAVA_RUNTIME_VERSION, 532 cause); 533 } 534 535 private interface StubGenerationStrategy<T extends WithFileStubAndExtraDiagnostics> { 536 @NotNull Collection<KtFile> getFiles(); 537 @NotNull FqName getPackageFqName(); 538 539 @NotNull LightClassConstructionContext getContext(@NotNull Collection<KtFile> files); 540 @NotNull T createLightClassData(PsiJavaFileStub javaFileStub, BindingContext bindingContext, Diagnostics extraDiagnostics); 541 542 GenerationState.GenerateClassFilter getGenerateClassFilter(); 543 void generate(@NotNull GenerationState state, @NotNull Collection<KtFile> files); 544 } 545 }