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