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