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