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.Stack; 038 import org.jetbrains.annotations.NotNull; 039 import org.jetbrains.annotations.Nullable; 040 import org.jetbrains.jet.codegen.BuiltinToJavaTypesMapping; 041 import org.jetbrains.jet.codegen.CompilationErrorHandler; 042 import org.jetbrains.jet.codegen.NamespaceCodegen; 043 import org.jetbrains.jet.codegen.state.GenerationState; 044 import org.jetbrains.jet.codegen.state.Progress; 045 import org.jetbrains.jet.lang.descriptors.NamespaceDescriptor; 046 import org.jetbrains.jet.lang.psi.JetClassOrObject; 047 import org.jetbrains.jet.lang.psi.JetFile; 048 import org.jetbrains.jet.lang.psi.JetPsiUtil; 049 import org.jetbrains.jet.lang.resolve.BindingContext; 050 import org.jetbrains.jet.lang.resolve.name.FqName; 051 052 import java.util.Collection; 053 import java.util.Collections; 054 055 public class KotlinJavaFileStubProvider implements CachedValueProvider<PsiJavaFileStub> { 056 057 @NotNull 058 public static KotlinJavaFileStubProvider createForPackageClass( 059 @NotNull final Project project, 060 @NotNull final FqName packageFqName, 061 @NotNull final GlobalSearchScope searchScope 062 ) { 063 return new KotlinJavaFileStubProvider(project, new StubGenerationStrategy.NoDeclaredClasses() { 064 065 @NotNull 066 @Override 067 public Collection<JetFile> getFiles() { 068 // Don't memoize this, it can be called again after an out-of-code-block modification occurs, 069 // and the set of files changes 070 return LightClassGenerationSupport.getInstance(project).findFilesForPackage(packageFqName, searchScope); 071 } 072 073 @NotNull 074 @Override 075 public FqName getPackageFqName() { 076 return packageFqName; 077 } 078 079 @Override 080 public void generate(@NotNull GenerationState state, @NotNull Collection<JetFile> files) { 081 NamespaceCodegen codegen = state.getFactory().forNamespace(packageFqName, files); 082 codegen.generate(CompilationErrorHandler.THROW_EXCEPTION); 083 state.getFactory().files(); 084 } 085 }); 086 } 087 088 @NotNull 089 public static KotlinJavaFileStubProvider createForDeclaredTopLevelClass( 090 @NotNull final JetClassOrObject classOrObject 091 ) { 092 return new KotlinJavaFileStubProvider(classOrObject.getProject(), new StubGenerationStrategy.WithDeclaredClasses() { 093 private JetFile getFile() { 094 JetFile file = (JetFile) classOrObject.getContainingFile(); 095 assert classOrObject.getParent() == file : "Not a top-level class: " + classOrObject.getText(); 096 return file; 097 } 098 099 @NotNull 100 @Override 101 public Collection<JetFile> getFiles() { 102 return Collections.singletonList(getFile()); 103 } 104 105 @NotNull 106 @Override 107 public FqName getPackageFqName() { 108 return JetPsiUtil.getFQName(getFile()); 109 } 110 111 @Override 112 public void generate(@NotNull GenerationState state, @NotNull Collection<JetFile> files) { 113 FqName packageFqName = getPackageFqName(); 114 NamespaceCodegen namespaceCodegen = state.getFactory().forNamespace(packageFqName, files); 115 NamespaceDescriptor namespaceDescriptor = 116 state.getBindingContext().get(BindingContext.FQNAME_TO_NAMESPACE_DESCRIPTOR, packageFqName); 117 assert namespaceDescriptor != null : "No package descriptor for " + packageFqName + " for class " + classOrObject.getText(); 118 namespaceCodegen.generateClassOrObject(namespaceDescriptor, classOrObject); 119 120 state.getFactory().files(); 121 } 122 }); 123 } 124 125 private static final Logger LOG = Logger.getInstance(KotlinJavaFileStubProvider.class); 126 127 private final Project project; 128 private final StubGenerationStrategy stubGenerationStrategy; 129 130 private KotlinJavaFileStubProvider( 131 @NotNull Project project, 132 @NotNull StubGenerationStrategy stubGenerationStrategy 133 ) { 134 this.project = project; 135 this.stubGenerationStrategy = stubGenerationStrategy; 136 } 137 138 @Nullable 139 @Override 140 public Result<PsiJavaFileStub> compute() { 141 FqName packageFqName = stubGenerationStrategy.getPackageFqName(); 142 Collection<JetFile> files = stubGenerationStrategy.getFiles(); 143 144 checkForBuiltIns(packageFqName, files); 145 146 LightClassConstructionContext context = LightClassGenerationSupport.getInstance(project).analyzeRelevantCode(files); 147 148 Throwable error = context.getError(); 149 if (error != null) { 150 throw new IllegalStateException("failed to analyze: " + error, error); 151 } 152 153 PsiJavaFileStub javaFileStub = createJavaFileStub(packageFqName, getRepresentativeVirtualFile(files)); 154 try { 155 Stack<StubElement> stubStack = new Stack<StubElement>(); 156 stubStack.push(javaFileStub); 157 158 GenerationState state = new GenerationState( 159 project, 160 new KotlinLightClassBuilderFactory(stubStack), 161 Progress.DEAF, 162 context.getBindingContext(), 163 Lists.newArrayList(files), 164 BuiltinToJavaTypesMapping.ENABLED, 165 /*not-null assertions*/false, false, 166 /*generateDeclaredClasses=*/stubGenerationStrategy.generateDeclaredClasses()); 167 state.beforeCompile(); 168 169 stubGenerationStrategy.generate(state, files); 170 171 StubElement pop = stubStack.pop(); 172 if (pop != javaFileStub) { 173 LOG.error("Unbalanced stack operations: " + pop); 174 } 175 176 } 177 catch (ProcessCanceledException e) { 178 throw e; 179 } 180 catch (RuntimeException e) { 181 logErrorWithOSInfo(e, packageFqName, null); 182 throw e; 183 } 184 185 return Result.create(javaFileStub, PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT); 186 } 187 188 @NotNull 189 private PsiJavaFileStub createJavaFileStub(@NotNull final FqName packageFqName, @NotNull VirtualFile virtualFile) { 190 PsiManager manager = PsiManager.getInstance(project); 191 192 final PsiJavaFileStubImpl javaFileStub = new PsiJavaFileStubImpl(packageFqName.asString(), true); 193 javaFileStub.setPsiFactory(new ClsWrapperStubPsiFactory()); 194 195 ClsFileImpl fakeFile = 196 new ClsFileImpl((PsiManagerImpl) manager, new ClassFileViewProvider(manager, virtualFile)) { 197 @NotNull 198 @Override 199 public PsiClassHolderFileStub getStub() { 200 return javaFileStub; 201 } 202 203 @NotNull 204 @Override 205 public String getPackageName() { 206 return packageFqName.asString(); 207 } 208 }; 209 210 fakeFile.setPhysical(false); 211 javaFileStub.setPsi(fakeFile); 212 return javaFileStub; 213 } 214 215 @NotNull 216 private static VirtualFile getRepresentativeVirtualFile(@NotNull Collection<JetFile> files) { 217 JetFile firstFile = files.iterator().next(); 218 VirtualFile virtualFile = files.size() == 1 ? firstFile.getVirtualFile() : new LightVirtualFile(); 219 assert virtualFile != null : "No virtual file for " + firstFile; 220 return virtualFile; 221 } 222 223 private static void checkForBuiltIns(@NotNull FqName fqName, @NotNull Collection<JetFile> files) { 224 for (JetFile file : files) { 225 if (LightClassUtil.belongsToKotlinBuiltIns(file)) { 226 // We may not fail later due to some luck, but generating JetLightClasses for built-ins is a bad idea anyways 227 // If it fails later, there will be an exception logged 228 logErrorWithOSInfo(null, fqName, file.getVirtualFile()); 229 } 230 } 231 } 232 233 private static void logErrorWithOSInfo(@Nullable Throwable cause, @NotNull FqName fqName, @Nullable VirtualFile virtualFile) { 234 String path = virtualFile == null ? "<null>" : virtualFile.getPath(); 235 LOG.error( 236 "Could not generate LightClass for " + fqName + " declared in " + path + "\n" + 237 "built-ins dir URL is " + LightClassUtil.getBuiltInsDirResourceUrl() + "\n" + 238 "System: " + SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION + " Java Runtime: " + SystemInfo.JAVA_RUNTIME_VERSION, 239 cause); 240 } 241 242 private interface StubGenerationStrategy { 243 @NotNull Collection<JetFile> getFiles(); 244 @NotNull FqName getPackageFqName(); 245 boolean generateDeclaredClasses(); 246 void generate(@NotNull GenerationState state, @NotNull Collection<JetFile> files); 247 248 abstract class NoDeclaredClasses implements StubGenerationStrategy { 249 @Override 250 public boolean generateDeclaredClasses() { 251 return false; 252 } 253 254 @Override 255 public String toString() { 256 // For subclasses to be identifiable in the debugger 257 return NoDeclaredClasses.class.getSimpleName(); 258 } 259 } 260 261 abstract class WithDeclaredClasses implements StubGenerationStrategy { 262 @Override 263 public boolean generateDeclaredClasses() { 264 return true; 265 } 266 267 @Override 268 public String toString() { 269 // For subclasses to be identifiable in the debugger 270 return WithDeclaredClasses.class.getSimpleName(); 271 } 272 } 273 } 274 }