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