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