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