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.PsiManagerImpl; 030 import com.intellij.psi.impl.compiled.ClsFileImpl; 031 import com.intellij.psi.impl.java.stubs.PsiJavaFileStub; 032 import com.intellij.psi.impl.java.stubs.impl.PsiJavaFileStubImpl; 033 import com.intellij.psi.search.GlobalSearchScope; 034 import com.intellij.psi.stubs.PsiClassHolderFileStub; 035 import com.intellij.psi.stubs.StubElement; 036 import com.intellij.psi.util.CachedValueProvider; 037 import com.intellij.psi.util.PsiModificationTracker; 038 import com.intellij.psi.util.PsiTreeUtil; 039 import com.intellij.testFramework.LightVirtualFile; 040 import com.intellij.util.containers.ContainerUtil; 041 import com.intellij.util.containers.Stack; 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 @Override 114 public boolean shouldProcessClass(JetClassOrObject classOrObject) { 115 // Top-level classes and such should not be generated for performance reasons. 116 // Local classes in top-level functions must still be generated 117 return JetPsiUtil.isLocal(classOrObject); 118 } 119 120 @Override 121 public boolean shouldProcessScript(JetScript script) { 122 // Scripts yield top-level classes, and should not be generated 123 return false; 124 } 125 }; 126 } 127 128 @Override 129 public void generate(@NotNull GenerationState state, @NotNull Collection<JetFile> files) { 130 PackageCodegen codegen = state.getFactory().forPackage(packageFqName, files); 131 codegen.generate(CompilationErrorHandler.THROW_EXCEPTION); 132 state.getFactory().asList(); 133 } 134 135 @Override 136 public String toString() { 137 return StubGenerationStrategy.class.getName() + " for package class"; 138 } 139 } 140 ); 141 } 142 143 @NotNull 144 public static KotlinJavaFileStubProvider<OutermostKotlinClassLightClassData> createForDeclaredClass(@NotNull final JetClassOrObject classOrObject) { 145 return new KotlinJavaFileStubProvider<OutermostKotlinClassLightClassData>( 146 classOrObject.getProject(), 147 classOrObject.isLocal(), 148 new StubGenerationStrategy<OutermostKotlinClassLightClassData>() { 149 private JetFile getFile() { 150 return classOrObject.getContainingJetFile(); 151 } 152 153 @NotNull 154 @Override 155 public LightClassConstructionContext getContext(@NotNull Collection<JetFile> files) { 156 return LightClassGenerationSupport.getInstance(classOrObject.getProject()).getContextForClassOrObject(classOrObject); 157 } 158 159 @NotNull 160 @Override 161 public OutermostKotlinClassLightClassData createLightClassData( 162 PsiJavaFileStub javaFileStub, 163 BindingContext bindingContext, 164 Diagnostics extraDiagnostics 165 ) { 166 ClassDescriptor classDescriptor = bindingContext.get(BindingContext.CLASS, classOrObject); 167 if (classDescriptor == null) { 168 return new OutermostKotlinClassLightClassData( 169 javaFileStub, extraDiagnostics, FqName.ROOT, classOrObject, 170 null, Collections.<JetClassOrObject, InnerKotlinClassLightClassData>emptyMap() 171 ); 172 } 173 174 FqName fqName = predictClassFqName(bindingContext, classDescriptor); 175 Collection<ClassDescriptor> allInnerClasses = CodegenBinding.getAllInnerClasses(bindingContext, classDescriptor); 176 177 Map<JetClassOrObject, InnerKotlinClassLightClassData> innerClassesMap = ContainerUtil.newHashMap(); 178 for (ClassDescriptor innerClassDescriptor : allInnerClasses) { 179 PsiElement declaration = descriptorToDeclaration(innerClassDescriptor); 180 if (!(declaration instanceof JetClassOrObject)) continue; 181 JetClassOrObject innerClass = (JetClassOrObject) declaration; 182 183 InnerKotlinClassLightClassData innerLightClassData = new InnerKotlinClassLightClassData( 184 predictClassFqName(bindingContext, innerClassDescriptor), 185 innerClass, 186 innerClassDescriptor 187 ); 188 189 innerClassesMap.put(innerClass, innerLightClassData); 190 } 191 192 return new OutermostKotlinClassLightClassData( 193 javaFileStub, 194 extraDiagnostics, 195 fqName, 196 classOrObject, 197 classDescriptor, 198 innerClassesMap 199 ); 200 } 201 202 @NotNull 203 private FqName predictClassFqName(BindingContext bindingContext, ClassDescriptor classDescriptor) { 204 Type asmType = CodegenBinding.getAsmType(bindingContext, classDescriptor); 205 //noinspection ConstantConditions 206 return JvmClassName.byInternalName(asmType.getClassName().replace('.', '/')).getFqNameForClassNameWithoutDollars(); 207 } 208 209 @NotNull 210 @Override 211 public Collection<JetFile> getFiles() { 212 return Collections.singletonList(getFile()); 213 } 214 215 @NotNull 216 @Override 217 public FqName getPackageFqName() { 218 return getFile().getPackageFqName(); 219 } 220 221 @Override 222 public GenerationState.GenerateClassFilter getGenerateClassFilter() { 223 return new GenerationState.GenerateClassFilter() { 224 @Override 225 public boolean shouldProcessClass(JetClassOrObject generatedClassOrObject) { 226 // Trivial: generate and analyze class we are interested in. 227 if (generatedClassOrObject == classOrObject) return true; 228 229 // Process all parent classes as they are context for current class 230 // Process child classes because they probably affect members (heuristic) 231 if (PsiTreeUtil.isAncestor(generatedClassOrObject, classOrObject, true) || 232 PsiTreeUtil.isAncestor(classOrObject, generatedClassOrObject, true)) { 233 return true; 234 } 235 236 if (generatedClassOrObject.isLocal() && classOrObject.isLocal()) { 237 // Local classes should be process by CodegenAnnotatingVisitor to 238 // decide what class they should be placed in. 239 // 240 // Example: 241 // class A 242 // fun foo() { 243 // trait Z: A {} 244 // fun bar() { 245 // class <caret>O2: Z {} 246 // } 247 // } 248 249 // TODO: current method will process local classes in irrelevant declarations, it should be fixed. 250 PsiElement commonParent = PsiTreeUtil.findCommonParent(generatedClassOrObject, classOrObject); 251 return commonParent != null && !(commonParent instanceof PsiFile); 252 } 253 254 return false; 255 } 256 257 @Override 258 public boolean shouldProcessScript(JetScript script) { 259 // We generate all enclosing classes 260 return PsiTreeUtil.isAncestor(script, classOrObject, false); 261 } 262 }; 263 } 264 265 @Override 266 public void generate(@NotNull GenerationState state, @NotNull Collection<JetFile> files) { 267 PackageCodegen packageCodegen = state.getFactory().forPackage(getPackageFqName(), files); 268 packageCodegen.generateClassOrObject(classOrObject); 269 state.getFactory().asList(); 270 } 271 272 @Override 273 public String toString() { 274 return StubGenerationStrategy.class.getName() + " for explicit class " + classOrObject.getName(); 275 } 276 } 277 ); 278 } 279 280 private static final Logger LOG = Logger.getInstance(KotlinJavaFileStubProvider.class); 281 282 private final Project project; 283 private final StubGenerationStrategy<T> stubGenerationStrategy; 284 private final boolean local; 285 286 private KotlinJavaFileStubProvider( 287 @NotNull Project project, 288 boolean local, 289 @NotNull StubGenerationStrategy<T> stubGenerationStrategy 290 ) { 291 this.project = project; 292 this.stubGenerationStrategy = stubGenerationStrategy; 293 this.local = local; 294 } 295 296 @Nullable 297 @Override 298 public Result<T> compute() { 299 FqName packageFqName = stubGenerationStrategy.getPackageFqName(); 300 Collection<JetFile> files = stubGenerationStrategy.getFiles(); 301 302 checkForBuiltIns(packageFqName, files); 303 304 LightClassConstructionContext context = stubGenerationStrategy.getContext(files); 305 306 PsiJavaFileStub javaFileStub = createJavaFileStub(packageFqName, getRepresentativeVirtualFile(files)); 307 BindingContext bindingContext; 308 BindingTraceContext forExtraDiagnostics = new BindingTraceContext(); 309 try { 310 Stack<StubElement> stubStack = new Stack<StubElement>(); 311 stubStack.push(javaFileStub); 312 313 GenerationState state = new GenerationState( 314 project, 315 new KotlinLightClassBuilderFactory(stubStack), 316 Progress.DEAF, 317 context.getModule(), 318 context.getBindingContext(), 319 Lists.newArrayList(files), 320 /*disable not-null assertions*/false, false, 321 /*generateClassFilter=*/stubGenerationStrategy.getGenerateClassFilter(), 322 /*disableInline=*/false, 323 /*disableOptimization=*/false, 324 null, 325 null, 326 forExtraDiagnostics, 327 null 328 ); 329 KotlinCodegenFacade.prepareForCompilation(state); 330 331 bindingContext = state.getBindingContext(); 332 333 stubGenerationStrategy.generate(state, files); 334 335 StubElement pop = stubStack.pop(); 336 if (pop != javaFileStub) { 337 LOG.error("Unbalanced stack operations: " + pop); 338 } 339 } 340 catch (ProcessCanceledException e) { 341 throw e; 342 } 343 catch (RuntimeException e) { 344 logErrorWithOSInfo(e, packageFqName, null); 345 throw e; 346 } 347 348 Diagnostics extraDiagnostics = forExtraDiagnostics.getBindingContext().getDiagnostics(); 349 return Result.create( 350 stubGenerationStrategy.createLightClassData(javaFileStub, bindingContext, extraDiagnostics), 351 local ? PsiModificationTracker.MODIFICATION_COUNT : PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT 352 ); 353 } 354 355 @NotNull 356 private PsiJavaFileStub createJavaFileStub(@NotNull final FqName packageFqName, @NotNull VirtualFile virtualFile) { 357 PsiManager manager = PsiManager.getInstance(project); 358 359 final PsiJavaFileStubImpl javaFileStub = new PsiJavaFileStubImpl(packageFqName.asString(), true); 360 javaFileStub.setPsiFactory(new ClsWrapperStubPsiFactory()); 361 362 ClsFileImpl fakeFile = 363 new ClsFileImpl((PsiManagerImpl) manager, new ClassFileViewProvider(manager, virtualFile)) { 364 @NotNull 365 @Override 366 public PsiClassHolderFileStub getStub() { 367 return javaFileStub; 368 } 369 370 @NotNull 371 @Override 372 public String getPackageName() { 373 return packageFqName.asString(); 374 } 375 }; 376 377 fakeFile.setPhysical(false); 378 javaFileStub.setPsi(fakeFile); 379 return javaFileStub; 380 } 381 382 @NotNull 383 private static VirtualFile getRepresentativeVirtualFile(@NotNull Collection<JetFile> files) { 384 JetFile firstFile = files.iterator().next(); 385 VirtualFile virtualFile = files.size() == 1 ? firstFile.getVirtualFile() : new LightVirtualFile(); 386 assert virtualFile != null : "No virtual file for " + firstFile; 387 return virtualFile; 388 } 389 390 private static void checkForBuiltIns(@NotNull FqName fqName, @NotNull Collection<JetFile> files) { 391 for (JetFile file : files) { 392 if (LightClassUtil.belongsToKotlinBuiltIns(file)) { 393 // We may not fail later due to some luck, but generating JetLightClasses for built-ins is a bad idea anyways 394 // If it fails later, there will be an exception logged 395 logErrorWithOSInfo(null, fqName, file.getVirtualFile()); 396 } 397 } 398 } 399 400 private static void logErrorWithOSInfo(@Nullable Throwable cause, @NotNull FqName fqName, @Nullable VirtualFile virtualFile) { 401 String path = virtualFile == null ? "<null>" : virtualFile.getPath(); 402 LOG.error( 403 "Could not generate LightClass for " + fqName + " declared in " + path + "\n" + 404 "built-ins dir URL is " + LightClassUtil.getBuiltInsDirUrl() + "\n" + 405 "System: " + SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION + " Java Runtime: " + SystemInfo.JAVA_RUNTIME_VERSION, 406 cause); 407 } 408 409 private interface StubGenerationStrategy<T extends WithFileStubAndExtraDiagnostics> { 410 @NotNull Collection<JetFile> getFiles(); 411 @NotNull FqName getPackageFqName(); 412 413 @NotNull LightClassConstructionContext getContext(@NotNull Collection<JetFile> files); 414 @NotNull T createLightClassData(PsiJavaFileStub javaFileStub, BindingContext bindingContext, Diagnostics extraDiagnostics); 415 416 GenerationState.GenerateClassFilter getGenerateClassFilter(); 417 void generate(@NotNull GenerationState state, @NotNull Collection<JetFile> files); 418 } 419 }