001 /* 002 * Copyright 2010-2013 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.PsiManager; 027 import com.intellij.psi.impl.PsiManagerImpl; 028 import com.intellij.psi.impl.compiled.ClsFileImpl; 029 import com.intellij.psi.impl.java.stubs.PsiJavaFileStub; 030 import com.intellij.psi.impl.java.stubs.impl.PsiJavaFileStubImpl; 031 import com.intellij.psi.search.GlobalSearchScope; 032 import com.intellij.psi.stubs.PsiClassHolderFileStub; 033 import com.intellij.psi.stubs.StubElement; 034 import com.intellij.psi.util.CachedValueProvider; 035 import com.intellij.psi.util.PsiModificationTracker; 036 import com.intellij.testFramework.LightVirtualFile; 037 import com.intellij.util.containers.ContainerUtil; 038 import com.intellij.util.containers.Stack; 039 import org.jetbrains.annotations.NotNull; 040 import org.jetbrains.annotations.Nullable; 041 import org.jetbrains.jet.codegen.CompilationErrorHandler; 042 import org.jetbrains.jet.codegen.NamespaceCodegen; 043 import org.jetbrains.jet.codegen.binding.CodegenBinding; 044 import org.jetbrains.jet.codegen.state.GenerationState; 045 import org.jetbrains.jet.codegen.state.Progress; 046 import org.jetbrains.jet.lang.descriptors.ClassDescriptor; 047 import org.jetbrains.jet.lang.psi.JetClassOrObject; 048 import org.jetbrains.jet.lang.psi.JetFile; 049 import org.jetbrains.jet.lang.psi.JetPsiUtil; 050 import org.jetbrains.jet.lang.resolve.BindingContext; 051 import org.jetbrains.jet.lang.resolve.BindingContextUtils; 052 import org.jetbrains.jet.lang.resolve.name.FqName; 053 import org.jetbrains.jet.lang.types.lang.InlineUtil; 054 055 import java.util.Collection; 056 import java.util.Collections; 057 import java.util.Map; 058 059 public class KotlinJavaFileStubProvider implements CachedValueProvider<LightClassStubWithData> { 060 061 @NotNull 062 public static KotlinJavaFileStubProvider createForPackageClass( 063 @NotNull final Project project, 064 @NotNull final FqName packageFqName, 065 @NotNull final GlobalSearchScope searchScope 066 ) { 067 return new KotlinJavaFileStubProvider( 068 project, 069 false, 070 new StubGenerationStrategy.NoDeclaredClasses() { 071 @NotNull 072 @Override 073 public LightClassConstructionContext createLightClassConstructionContext(@NotNull Collection<JetFile> files) { 074 return LightClassGenerationSupport.getInstance(project).analyzeRelevantCode(files); 075 } 076 077 @NotNull 078 @Override 079 public Collection<JetFile> getFiles() { 080 // Don't memoize this, it can be called again after an out-of-code-block modification occurs, 081 // and the set of files changes 082 return LightClassGenerationSupport.getInstance(project).findFilesForPackage(packageFqName, searchScope); 083 } 084 085 @NotNull 086 @Override 087 public LightClassStubWithData createLightClassStubWithData(PsiJavaFileStub javaFileStub, BindingContext bindingContext) { 088 return new LightClassStubWithData(javaFileStub, KotlinPackageLightClassData.instance$); 089 } 090 091 @NotNull 092 @Override 093 public FqName getPackageFqName() { 094 return packageFqName; 095 } 096 097 @Override 098 public void generate(@NotNull GenerationState state, @NotNull Collection<JetFile> files) { 099 NamespaceCodegen codegen = state.getFactory().forNamespace(packageFqName, files); 100 codegen.generate(CompilationErrorHandler.THROW_EXCEPTION); 101 state.getFactory().asList(); 102 } 103 } 104 ); 105 } 106 107 @NotNull 108 public static KotlinJavaFileStubProvider createForDeclaredClass(@NotNull final JetClassOrObject classOrObject) { 109 return new KotlinJavaFileStubProvider( 110 classOrObject.getProject(), 111 JetPsiUtil.isLocal(classOrObject), 112 new StubGenerationStrategy.WithDeclaredClasses() { 113 private JetFile getFile() { 114 return (JetFile) classOrObject.getContainingFile(); 115 } 116 117 @NotNull 118 @Override 119 public LightClassConstructionContext createLightClassConstructionContext(@NotNull Collection<JetFile> files) { 120 return LightClassGenerationSupport.getInstance(classOrObject.getProject()).analyzeRelevantCode(classOrObject); 121 } 122 123 @NotNull 124 @Override 125 public LightClassStubWithData createLightClassStubWithData(PsiJavaFileStub javaFileStub, BindingContext bindingContext) { 126 ClassDescriptor classDescriptor = bindingContext.get(BindingContext.CLASS, classOrObject); 127 if (classDescriptor == null) { 128 return new LightClassStubWithData( 129 javaFileStub, 130 new OutermostKotlinClassLightClassData("", classOrObject, null, Collections.<JetClassOrObject, LightClassDataForKotlinClass>emptyMap()) 131 ); 132 } 133 134 String jvmInternalName = CodegenBinding.getJvmInternalName(bindingContext, classDescriptor); 135 Collection<ClassDescriptor> allInnerClasses = CodegenBinding.getAllInnerClasses(bindingContext, classDescriptor); 136 137 Map<JetClassOrObject, LightClassDataForKotlinClass> innerClassesMap = ContainerUtil.newHashMap(); 138 for (ClassDescriptor innerClassDescriptor : allInnerClasses) { 139 JetClassOrObject innerClass = (JetClassOrObject) BindingContextUtils.descriptorToDeclaration( 140 bindingContext, innerClassDescriptor 141 ); 142 if (innerClass == null) continue; 143 144 InnerKotlinClassLightClassData innerLightClassData = new InnerKotlinClassLightClassData( 145 CodegenBinding.getJvmInternalName(bindingContext, innerClassDescriptor), 146 innerClass, 147 innerClassDescriptor 148 ); 149 innerClassesMap.put(innerClass, innerLightClassData); 150 } 151 152 return new LightClassStubWithData( 153 javaFileStub, 154 new OutermostKotlinClassLightClassData(jvmInternalName, classOrObject, classDescriptor, innerClassesMap) 155 ); 156 } 157 158 @NotNull 159 @Override 160 public Collection<JetFile> getFiles() { 161 return Collections.singletonList(getFile()); 162 } 163 164 @NotNull 165 @Override 166 public FqName getPackageFqName() { 167 return JetPsiUtil.getFQName(getFile()); 168 } 169 170 @Override 171 public void generate(@NotNull GenerationState state, @NotNull Collection<JetFile> files) { 172 NamespaceCodegen namespaceCodegen = state.getFactory().forNamespace(getPackageFqName(), files); 173 namespaceCodegen.generateClassOrObject(classOrObject); 174 state.getFactory().asList(); 175 } 176 } 177 ); 178 } 179 180 private static final Logger LOG = Logger.getInstance(KotlinJavaFileStubProvider.class); 181 182 private final Project project; 183 private final StubGenerationStrategy stubGenerationStrategy; 184 private final boolean local; 185 186 private KotlinJavaFileStubProvider( 187 @NotNull Project project, 188 boolean local, 189 @NotNull StubGenerationStrategy stubGenerationStrategy 190 ) { 191 this.project = project; 192 this.stubGenerationStrategy = stubGenerationStrategy; 193 this.local = local; 194 } 195 196 @Nullable 197 @Override 198 public Result<LightClassStubWithData> compute() { 199 FqName packageFqName = stubGenerationStrategy.getPackageFqName(); 200 Collection<JetFile> files = stubGenerationStrategy.getFiles(); 201 202 checkForBuiltIns(packageFqName, files); 203 204 LightClassConstructionContext context = stubGenerationStrategy.createLightClassConstructionContext(files); 205 Throwable error = context.getError(); 206 if (error != null) { 207 throw new IllegalStateException("failed to analyze: " + error, error); 208 } 209 210 PsiJavaFileStub javaFileStub = createJavaFileStub(packageFqName, getRepresentativeVirtualFile(files)); 211 BindingContext bindingContext; 212 try { 213 Stack<StubElement> stubStack = new Stack<StubElement>(); 214 stubStack.push(javaFileStub); 215 216 GenerationState state = new GenerationState( 217 project, 218 new KotlinLightClassBuilderFactory(stubStack), 219 Progress.DEAF, 220 context.getBindingContext(), 221 Lists.newArrayList(files), 222 /*not-null assertions*/false, false, 223 /*generateDeclaredClasses=*/stubGenerationStrategy.generateDeclaredClasses(), 224 InlineUtil.DEFAULT_INLINE_FLAG_FOR_STUB); 225 state.beforeCompile(); 226 227 bindingContext = state.getBindingContext(); 228 229 stubGenerationStrategy.generate(state, files); 230 231 StubElement pop = stubStack.pop(); 232 if (pop != javaFileStub) { 233 LOG.error("Unbalanced stack operations: " + pop); 234 } 235 236 } 237 catch (ProcessCanceledException e) { 238 throw e; 239 } 240 catch (RuntimeException e) { 241 logErrorWithOSInfo(e, packageFqName, null); 242 throw e; 243 } 244 245 return Result.create( 246 stubGenerationStrategy.createLightClassStubWithData(javaFileStub, bindingContext), 247 local ? PsiModificationTracker.MODIFICATION_COUNT : PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT 248 ); 249 } 250 251 @NotNull 252 private PsiJavaFileStub createJavaFileStub(@NotNull final FqName packageFqName, @NotNull VirtualFile virtualFile) { 253 PsiManager manager = PsiManager.getInstance(project); 254 255 final PsiJavaFileStubImpl javaFileStub = new PsiJavaFileStubImpl(packageFqName.asString(), true); 256 javaFileStub.setPsiFactory(new ClsWrapperStubPsiFactory()); 257 258 ClsFileImpl fakeFile = 259 new ClsFileImpl((PsiManagerImpl) manager, new ClassFileViewProvider(manager, virtualFile)) { 260 @NotNull 261 @Override 262 public PsiClassHolderFileStub getStub() { 263 return javaFileStub; 264 } 265 266 @NotNull 267 @Override 268 public String getPackageName() { 269 return packageFqName.asString(); 270 } 271 }; 272 273 fakeFile.setPhysical(false); 274 javaFileStub.setPsi(fakeFile); 275 return javaFileStub; 276 } 277 278 @NotNull 279 private static VirtualFile getRepresentativeVirtualFile(@NotNull Collection<JetFile> files) { 280 JetFile firstFile = files.iterator().next(); 281 VirtualFile virtualFile = files.size() == 1 ? firstFile.getVirtualFile() : new LightVirtualFile(); 282 assert virtualFile != null : "No virtual file for " + firstFile; 283 return virtualFile; 284 } 285 286 private static void checkForBuiltIns(@NotNull FqName fqName, @NotNull Collection<JetFile> files) { 287 for (JetFile file : files) { 288 if (LightClassUtil.belongsToKotlinBuiltIns(file)) { 289 // We may not fail later due to some luck, but generating JetLightClasses for built-ins is a bad idea anyways 290 // If it fails later, there will be an exception logged 291 logErrorWithOSInfo(null, fqName, file.getVirtualFile()); 292 } 293 } 294 } 295 296 private static void logErrorWithOSInfo(@Nullable Throwable cause, @NotNull FqName fqName, @Nullable VirtualFile virtualFile) { 297 String path = virtualFile == null ? "<null>" : virtualFile.getPath(); 298 LOG.error( 299 "Could not generate LightClass for " + fqName + " declared in " + path + "\n" + 300 "built-ins dir URL is " + LightClassUtil.getBuiltInsDirUrl() + "\n" + 301 "System: " + SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION + " Java Runtime: " + SystemInfo.JAVA_RUNTIME_VERSION, 302 cause); 303 } 304 305 private interface StubGenerationStrategy { 306 @NotNull LightClassConstructionContext createLightClassConstructionContext(@NotNull Collection<JetFile> files); 307 @NotNull 308 LightClassStubWithData createLightClassStubWithData(PsiJavaFileStub javaFileStub, BindingContext bindingContext); 309 @NotNull Collection<JetFile> getFiles(); 310 @NotNull FqName getPackageFqName(); 311 boolean generateDeclaredClasses(); 312 void generate(@NotNull GenerationState state, @NotNull Collection<JetFile> files); 313 314 abstract class NoDeclaredClasses implements StubGenerationStrategy { 315 @Override 316 public boolean generateDeclaredClasses() { 317 return false; 318 } 319 320 @Override 321 public String toString() { 322 // For subclasses to be identifiable in the debugger 323 return NoDeclaredClasses.class.getSimpleName(); 324 } 325 } 326 327 abstract class WithDeclaredClasses implements StubGenerationStrategy { 328 @Override 329 public boolean generateDeclaredClasses() { 330 return true; 331 } 332 333 @Override 334 public String toString() { 335 // For subclasses to be identifiable in the debugger 336 return WithDeclaredClasses.class.getSimpleName(); 337 } 338 } 339 } 340 }