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 CachedValueProvider<KotlinFacadeLightClassData> createForFacadeClass( 075 @NotNull final Project project, 076 @NotNull final FqName facadeFqName, 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 Collection<KtFile> getFiles() { 086 return LightClassGenerationSupport.getInstance(project).findFilesForFacade(facadeFqName, searchScope); 087 } 088 089 @NotNull 090 @Override 091 public FqName getPackageFqName() { 092 return facadeFqName.parent(); 093 } 094 095 @NotNull 096 @Override 097 public LightClassConstructionContext getContext(@NotNull Collection<KtFile> files) { 098 return LightClassGenerationSupport.getInstance(project).getContextForFacade(files); 099 } 100 101 @NotNull 102 @Override 103 public KotlinFacadeLightClassData createLightClassData( 104 PsiJavaFileStub javaFileStub, 105 BindingContext bindingContext, 106 Diagnostics extraDiagnostics 107 ) { 108 return new KotlinFacadeLightClassData(javaFileStub, extraDiagnostics); 109 } 110 111 @Override 112 public GenerationState.GenerateClassFilter getGenerateClassFilter() { 113 return new GenerationState.GenerateClassFilter() { 114 @Override 115 public boolean shouldAnnotateClass(KtClassOrObject classOrObject) { 116 return shouldGenerateClass(classOrObject); 117 } 118 119 @Override 120 public boolean shouldGenerateClass(KtClassOrObject classOrObject) { 121 return KtPsiUtil.isLocal(classOrObject); 122 } 123 124 @Override 125 public boolean shouldGeneratePackagePart(KtFile jetFile) { 126 return true; 127 } 128 129 @Override 130 public boolean shouldGenerateScript(KtScript script) { 131 return false; 132 } 133 }; 134 } 135 136 @Override 137 public void generate(@NotNull GenerationState state, @NotNull Collection<KtFile> files) { 138 if (!files.isEmpty()) { 139 KtFile representativeFile = files.iterator().next(); 140 JvmFileClassInfo fileClassInfo = NoResolveFileClassesProvider.INSTANCE.getFileClassInfo(representativeFile); 141 if (!fileClassInfo.getWithJvmMultifileClass()) { 142 PackageCodegen codegen = state.getFactory().forPackage(representativeFile.getPackageFqName(), files); 143 codegen.generate(CompilationErrorHandler.THROW_EXCEPTION); 144 state.getFactory().asList(); 145 return; 146 } 147 } 148 149 MultifileClassCodegen codegen = state.getFactory().forMultifileClass(facadeFqName, files); 150 codegen.generate(CompilationErrorHandler.THROW_EXCEPTION); 151 state.getFactory().asList(); 152 } 153 154 @Override 155 public String toString() { 156 return StubGenerationStrategy.class.getName() + " for facade class"; 157 } 158 }); 159 } 160 161 @NotNull 162 public static KotlinJavaFileStubProvider<OutermostKotlinClassLightClassData> createForDeclaredClass(@NotNull final KtClassOrObject classOrObject) { 163 return new KotlinJavaFileStubProvider<OutermostKotlinClassLightClassData>( 164 classOrObject.getProject(), 165 classOrObject.isLocal(), 166 new StubGenerationStrategy<OutermostKotlinClassLightClassData>() { 167 private KtFile getFile() { 168 return classOrObject.getContainingJetFile(); 169 } 170 171 @NotNull 172 @Override 173 public LightClassConstructionContext getContext(@NotNull Collection<KtFile> files) { 174 return LightClassGenerationSupport.getInstance(classOrObject.getProject()).getContextForClassOrObject(classOrObject); 175 } 176 177 @NotNull 178 @Override 179 public OutermostKotlinClassLightClassData createLightClassData( 180 PsiJavaFileStub javaFileStub, 181 BindingContext bindingContext, 182 Diagnostics extraDiagnostics 183 ) { 184 ClassDescriptor classDescriptor = bindingContext.get(BindingContext.CLASS, classOrObject); 185 if (classDescriptor == null) { 186 return new OutermostKotlinClassLightClassData( 187 javaFileStub, extraDiagnostics, FqName.ROOT, classOrObject, 188 Collections.<KtClassOrObject, InnerKotlinClassLightClassData>emptyMap() 189 ); 190 } 191 192 FqName fqName = predictClassFqName(bindingContext, classDescriptor); 193 Collection<ClassDescriptor> allInnerClasses = CodegenBinding.getAllInnerClasses(bindingContext, classDescriptor); 194 195 Map<KtClassOrObject, InnerKotlinClassLightClassData> innerClassesMap = ContainerUtil.newHashMap(); 196 for (ClassDescriptor innerClassDescriptor : allInnerClasses) { 197 PsiElement declaration = descriptorToDeclaration(innerClassDescriptor); 198 if (!(declaration instanceof KtClassOrObject)) continue; 199 KtClassOrObject innerClass = (KtClassOrObject) declaration; 200 201 InnerKotlinClassLightClassData innerLightClassData = new InnerKotlinClassLightClassData( 202 predictClassFqName(bindingContext, innerClassDescriptor), 203 innerClass 204 ); 205 206 innerClassesMap.put(innerClass, innerLightClassData); 207 } 208 209 return new OutermostKotlinClassLightClassData( 210 javaFileStub, 211 extraDiagnostics, 212 fqName, 213 classOrObject, 214 innerClassesMap 215 ); 216 } 217 218 @NotNull 219 private FqName predictClassFqName(BindingContext bindingContext, ClassDescriptor classDescriptor) { 220 Type asmType = CodegenBinding.getAsmType(bindingContext, classDescriptor); 221 //noinspection ConstantConditions 222 return JvmClassName.byInternalName(asmType.getClassName().replace('.', '/')).getFqNameForClassNameWithoutDollars(); 223 } 224 225 @NotNull 226 @Override 227 public Collection<KtFile> getFiles() { 228 return Collections.singletonList(getFile()); 229 } 230 231 @NotNull 232 @Override 233 public FqName getPackageFqName() { 234 return getFile().getPackageFqName(); 235 } 236 237 @Override 238 public GenerationState.GenerateClassFilter getGenerateClassFilter() { 239 return new GenerationState.GenerateClassFilter() { 240 241 @Override 242 public boolean shouldGeneratePackagePart(KtFile jetFile) { 243 return true; 244 } 245 246 @Override 247 public boolean shouldAnnotateClass(KtClassOrObject classOrObject) { 248 return shouldGenerateClass(classOrObject); 249 } 250 251 @Override 252 public boolean shouldGenerateClass(KtClassOrObject generatedClassOrObject) { 253 // Trivial: generate and analyze class we are interested in. 254 if (generatedClassOrObject == classOrObject) return true; 255 256 // Process all parent classes as they are context for current class 257 // Process child classes because they probably affect members (heuristic) 258 if (PsiTreeUtil.isAncestor(generatedClassOrObject, classOrObject, true) || 259 PsiTreeUtil.isAncestor(classOrObject, generatedClassOrObject, true)) { 260 return true; 261 } 262 263 if (generatedClassOrObject.isLocal() && classOrObject.isLocal()) { 264 // Local classes should be process by CodegenAnnotatingVisitor to 265 // decide what class they should be placed in. 266 // 267 // Example: 268 // class A 269 // fun foo() { 270 // trait Z: A {} 271 // fun bar() { 272 // class <caret>O2: Z {} 273 // } 274 // } 275 276 // TODO: current method will process local classes in irrelevant declarations, it should be fixed. 277 PsiElement commonParent = PsiTreeUtil.findCommonParent(generatedClassOrObject, classOrObject); 278 return commonParent != null && !(commonParent instanceof PsiFile); 279 } 280 281 return false; 282 } 283 284 @Override 285 public boolean shouldGenerateScript(KtScript script) { 286 // We generate all enclosing classes 287 return PsiTreeUtil.isAncestor(script, classOrObject, false); 288 } 289 }; 290 } 291 292 @Override 293 public void generate(@NotNull GenerationState state, @NotNull Collection<KtFile> files) { 294 PackageCodegen packageCodegen = state.getFactory().forPackage(getPackageFqName(), files); 295 KtFile file = classOrObject.getContainingJetFile(); 296 Type packagePartType = FileClasses.getFileClassType(state.getFileClassesProvider(), file); 297 PackageContext context = state.getRootContext().intoPackagePart(packageCodegen.getPackageFragment(), packagePartType, file); 298 packageCodegen.generateClassOrObject(classOrObject, context); 299 state.getFactory().asList(); 300 } 301 302 @Override 303 public String toString() { 304 return StubGenerationStrategy.class.getName() + " for explicit class " + classOrObject.getName(); 305 } 306 } 307 ); 308 } 309 310 private static final Logger LOG = Logger.getInstance(KotlinJavaFileStubProvider.class); 311 312 private final Project project; 313 private final StubGenerationStrategy<T> stubGenerationStrategy; 314 private final boolean local; 315 316 private KotlinJavaFileStubProvider( 317 @NotNull Project project, 318 boolean local, 319 @NotNull StubGenerationStrategy<T> stubGenerationStrategy 320 ) { 321 this.project = project; 322 this.stubGenerationStrategy = stubGenerationStrategy; 323 this.local = local; 324 } 325 326 @Nullable 327 @Override 328 public Result<T> compute() { 329 FqName packageFqName = stubGenerationStrategy.getPackageFqName(); 330 Collection<KtFile> files = stubGenerationStrategy.getFiles(); 331 332 checkForBuiltIns(packageFqName, files); 333 334 LightClassConstructionContext context = stubGenerationStrategy.getContext(files); 335 336 PsiJavaFileStub javaFileStub = createJavaFileStub(packageFqName, files); 337 BindingContext bindingContext; 338 BindingTraceContext forExtraDiagnostics = new BindingTraceContext(); 339 try { 340 Stack<StubElement> stubStack = new Stack<StubElement>(); 341 stubStack.push(javaFileStub); 342 343 GenerationState state = new GenerationState( 344 project, 345 new KotlinLightClassBuilderFactory(stubStack), 346 context.getModule(), 347 context.getBindingContext(), 348 Lists.newArrayList(files), 349 /*disable not-null assertions*/false, false, 350 /*generateClassFilter=*/stubGenerationStrategy.getGenerateClassFilter(), 351 /*disableInline=*/false, 352 /*disableOptimization=*/false, 353 /*useTypeTableInSerializer=*/false, 354 forExtraDiagnostics 355 ); 356 KotlinCodegenFacade.prepareForCompilation(state); 357 358 bindingContext = state.getBindingContext(); 359 360 stubGenerationStrategy.generate(state, files); 361 362 StubElement pop = stubStack.pop(); 363 if (pop != javaFileStub) { 364 LOG.error("Unbalanced stack operations: " + pop); 365 } 366 } 367 catch (ProcessCanceledException e) { 368 throw e; 369 } 370 catch (RuntimeException e) { 371 logErrorWithOSInfo(e, packageFqName, null); 372 throw e; 373 } 374 375 Diagnostics extraDiagnostics = forExtraDiagnostics.getBindingContext().getDiagnostics(); 376 return Result.create( 377 stubGenerationStrategy.createLightClassData(javaFileStub, bindingContext, extraDiagnostics), 378 local ? PsiModificationTracker.MODIFICATION_COUNT : PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT 379 ); 380 } 381 382 @NotNull 383 private static ClsFileImpl createFakeClsFile( 384 @NotNull Project project, 385 @NotNull final FqName packageFqName, 386 @NotNull Collection<KtFile> files, 387 @NotNull final Function0<? extends PsiClassHolderFileStub> fileStubProvider 388 ) { 389 PsiManager manager = PsiManager.getInstance(project); 390 391 VirtualFile virtualFile = getRepresentativeVirtualFile(files); 392 ClsFileImpl fakeFile = new ClsFileImpl(new ClassFileViewProvider(manager, virtualFile)) { 393 @NotNull 394 @Override 395 public PsiClassHolderFileStub getStub() { 396 return fileStubProvider.invoke(); 397 } 398 399 @NotNull 400 @Override 401 public String getPackageName() { 402 return packageFqName.asString(); 403 } 404 }; 405 406 fakeFile.setPhysical(false); 407 return fakeFile; 408 } 409 410 @NotNull 411 private PsiJavaFileStub createJavaFileStub(@NotNull FqName packageFqName, @NotNull Collection<KtFile> files) { 412 final PsiJavaFileStubImpl javaFileStub = new PsiJavaFileStubImpl(packageFqName.asString(), true); 413 javaFileStub.setPsiFactory(new ClsWrapperStubPsiFactory()); 414 415 ClsFileImpl fakeFile = createFakeClsFile(project, packageFqName, files, new Function0<PsiClassHolderFileStub>() { 416 @Override 417 public PsiClassHolderFileStub invoke() { 418 return javaFileStub; 419 } 420 }); 421 422 javaFileStub.setPsi(fakeFile); 423 return javaFileStub; 424 } 425 426 @NotNull 427 private static VirtualFile getRepresentativeVirtualFile(@NotNull Collection<KtFile> files) { 428 KtFile firstFile = files.iterator().next(); 429 VirtualFile virtualFile = firstFile.getVirtualFile(); 430 assert virtualFile != null : "No virtual file for " + firstFile; 431 return virtualFile; 432 } 433 434 private static void checkForBuiltIns(@NotNull FqName fqName, @NotNull Collection<KtFile> files) { 435 for (KtFile file : files) { 436 if (LightClassUtil.INSTANCE$.belongsToKotlinBuiltIns(file)) { 437 // We may not fail later due to some luck, but generating JetLightClasses for built-ins is a bad idea anyways 438 // If it fails later, there will be an exception logged 439 logErrorWithOSInfo(null, fqName, file.getVirtualFile()); 440 } 441 } 442 } 443 444 private static void logErrorWithOSInfo(@Nullable Throwable cause, @NotNull FqName fqName, @Nullable VirtualFile virtualFile) { 445 String path = virtualFile == null ? "<null>" : virtualFile.getPath(); 446 LOG.error( 447 "Could not generate LightClass for " + fqName + " declared in " + path + "\n" + 448 "built-ins dir URL is " + LightClassUtil.INSTANCE$.getBuiltInsDirUrl() + "\n" + 449 "System: " + SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION + " Java Runtime: " + SystemInfo.JAVA_RUNTIME_VERSION, 450 cause); 451 } 452 453 private interface StubGenerationStrategy<T extends WithFileStubAndExtraDiagnostics> { 454 @NotNull Collection<KtFile> getFiles(); 455 @NotNull FqName getPackageFqName(); 456 457 @NotNull LightClassConstructionContext getContext(@NotNull Collection<KtFile> files); 458 @NotNull T createLightClassData(PsiJavaFileStub javaFileStub, BindingContext bindingContext, Diagnostics extraDiagnostics); 459 460 GenerationState.GenerateClassFilter getGenerateClassFilter(); 461 void generate(@NotNull GenerationState state, @NotNull Collection<KtFile> files); 462 } 463 }