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 import org.jetbrains.jet.lang.types.lang.InlineUtil;
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().asList();
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().asList();
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 /*not-null assertions*/false, false,
156 /*generateDeclaredClasses=*/stubGenerationStrategy.generateDeclaredClasses(),
157 InlineUtil.DEFAULT_INLINE_FLAG_FOR_STUB);
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 }