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