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