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 }