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