001 /*
002 * Copyright 2010-2014 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.PsiElement;
027 import com.intellij.psi.PsiFile;
028 import com.intellij.psi.PsiManager;
029 import com.intellij.psi.impl.PsiManagerImpl;
030 import com.intellij.psi.impl.compiled.ClsFileImpl;
031 import com.intellij.psi.impl.java.stubs.PsiJavaFileStub;
032 import com.intellij.psi.impl.java.stubs.impl.PsiJavaFileStubImpl;
033 import com.intellij.psi.search.GlobalSearchScope;
034 import com.intellij.psi.stubs.PsiClassHolderFileStub;
035 import com.intellij.psi.stubs.StubElement;
036 import com.intellij.psi.util.CachedValueProvider;
037 import com.intellij.psi.util.PsiModificationTracker;
038 import com.intellij.psi.util.PsiTreeUtil;
039 import com.intellij.testFramework.LightVirtualFile;
040 import com.intellij.util.containers.ContainerUtil;
041 import com.intellij.util.containers.Stack;
042 import org.jetbrains.annotations.NotNull;
043 import org.jetbrains.annotations.Nullable;
044 import org.jetbrains.jet.codegen.CompilationErrorHandler;
045 import org.jetbrains.jet.codegen.KotlinCodegenFacade;
046 import org.jetbrains.jet.codegen.PackageCodegen;
047 import org.jetbrains.jet.codegen.binding.CodegenBinding;
048 import org.jetbrains.jet.codegen.state.GenerationState;
049 import org.jetbrains.jet.codegen.state.Progress;
050 import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
051 import org.jetbrains.jet.lang.psi.JetClassOrObject;
052 import org.jetbrains.jet.lang.psi.JetFile;
053 import org.jetbrains.jet.lang.psi.JetPsiUtil;
054 import org.jetbrains.jet.lang.resolve.BindingContext;
055 import org.jetbrains.jet.lang.resolve.BindingTraceContext;
056 import org.jetbrains.jet.lang.resolve.Diagnostics;
057 import org.jetbrains.jet.lang.resolve.java.JvmClassName;
058 import org.jetbrains.jet.lang.resolve.name.FqName;
059 import org.jetbrains.org.objectweb.asm.Type;
060
061 import java.util.Collection;
062 import java.util.Collections;
063 import java.util.Map;
064
065 import static org.jetbrains.jet.lang.resolve.DescriptorToSourceUtils.descriptorToDeclaration;
066
067 public class KotlinJavaFileStubProvider<T extends WithFileStubAndExtraDiagnostics> implements CachedValueProvider<T> {
068
069 @NotNull
070 public static KotlinJavaFileStubProvider<KotlinPackageLightClassData> createForPackageClass(
071 @NotNull final Project project,
072 @NotNull final FqName packageFqName,
073 @NotNull final GlobalSearchScope searchScope
074 ) {
075 return new KotlinJavaFileStubProvider<KotlinPackageLightClassData>(
076 project,
077 false,
078 new StubGenerationStrategy<KotlinPackageLightClassData>() {
079 @NotNull
080 @Override
081 public LightClassConstructionContext getContext(@NotNull Collection<JetFile> files) {
082 return LightClassGenerationSupport.getInstance(project).getContextForPackage(files);
083 }
084
085 @NotNull
086 @Override
087 public Collection<JetFile> getFiles() {
088 // Don't memoize this, it can be called again after an out-of-code-block modification occurs,
089 // and the set of files changes
090 return LightClassGenerationSupport.getInstance(project).findFilesForPackage(packageFqName, searchScope);
091 }
092
093 @NotNull
094 @Override
095 public KotlinPackageLightClassData createLightClassData(
096 PsiJavaFileStub javaFileStub,
097 BindingContext bindingContext,
098 Diagnostics extraDiagnostics
099 ) {
100 return new KotlinPackageLightClassData(javaFileStub, extraDiagnostics);
101 }
102
103 @NotNull
104 @Override
105 public FqName getPackageFqName() {
106 return packageFqName;
107 }
108
109 @Override
110 public GenerationState.GenerateClassFilter getGenerateClassFilter() {
111 return new GenerationState.GenerateClassFilter() {
112 @Override
113 public boolean shouldProcess(JetClassOrObject classOrObject) {
114 // Top-level classes and such should not be generated for performance reasons.
115 // Local classes in top-level functions must still be generated
116 return JetPsiUtil.isLocal(classOrObject);
117 }
118 };
119 }
120
121 @Override
122 public void generate(@NotNull GenerationState state, @NotNull Collection<JetFile> files) {
123 PackageCodegen codegen = state.getFactory().forPackage(packageFqName, files);
124 codegen.generate(CompilationErrorHandler.THROW_EXCEPTION);
125 state.getFactory().asList();
126 }
127
128 @Override
129 public String toString() {
130 return StubGenerationStrategy.class.getName() + " for package class";
131 }
132 }
133 );
134 }
135
136 @NotNull
137 public static KotlinJavaFileStubProvider<OutermostKotlinClassLightClassData> createForDeclaredClass(@NotNull final JetClassOrObject classOrObject) {
138 return new KotlinJavaFileStubProvider<OutermostKotlinClassLightClassData>(
139 classOrObject.getProject(),
140 classOrObject.isLocal(),
141 new StubGenerationStrategy<OutermostKotlinClassLightClassData>() {
142 private JetFile getFile() {
143 return classOrObject.getContainingJetFile();
144 }
145
146 @NotNull
147 @Override
148 public LightClassConstructionContext getContext(@NotNull Collection<JetFile> files) {
149 return LightClassGenerationSupport.getInstance(classOrObject.getProject()).getContextForClassOrObject(classOrObject);
150 }
151
152 @NotNull
153 @Override
154 public OutermostKotlinClassLightClassData createLightClassData(
155 PsiJavaFileStub javaFileStub,
156 BindingContext bindingContext,
157 Diagnostics extraDiagnostics
158 ) {
159 ClassDescriptor classDescriptor = bindingContext.get(BindingContext.CLASS, classOrObject);
160 if (classDescriptor == null) {
161 return new OutermostKotlinClassLightClassData(
162 javaFileStub, extraDiagnostics, FqName.ROOT, classOrObject,
163 null, Collections.<JetClassOrObject, InnerKotlinClassLightClassData>emptyMap()
164 );
165 }
166
167 FqName fqName = predictClassFqName(bindingContext, classDescriptor);
168 Collection<ClassDescriptor> allInnerClasses = CodegenBinding.getAllInnerClasses(bindingContext, classDescriptor);
169
170 Map<JetClassOrObject, InnerKotlinClassLightClassData> innerClassesMap = ContainerUtil.newHashMap();
171 for (ClassDescriptor innerClassDescriptor : allInnerClasses) {
172 PsiElement declaration = descriptorToDeclaration(innerClassDescriptor);
173 if (!(declaration instanceof JetClassOrObject)) continue;
174 JetClassOrObject innerClass = (JetClassOrObject) declaration;
175
176 InnerKotlinClassLightClassData innerLightClassData = new InnerKotlinClassLightClassData(
177 predictClassFqName(bindingContext, innerClassDescriptor),
178 innerClass,
179 innerClassDescriptor
180 );
181
182 innerClassesMap.put(innerClass, innerLightClassData);
183 }
184
185 return new OutermostKotlinClassLightClassData(
186 javaFileStub,
187 extraDiagnostics,
188 fqName,
189 classOrObject,
190 classDescriptor,
191 innerClassesMap
192 );
193 }
194
195 @NotNull
196 private FqName predictClassFqName(BindingContext bindingContext, ClassDescriptor classDescriptor) {
197 Type asmType = CodegenBinding.getAsmType(bindingContext, classDescriptor);
198 //noinspection ConstantConditions
199 return JvmClassName.byInternalName(asmType.getClassName().replace('.', '/')).getFqNameForClassNameWithoutDollars();
200 }
201
202 @NotNull
203 @Override
204 public Collection<JetFile> getFiles() {
205 return Collections.singletonList(getFile());
206 }
207
208 @NotNull
209 @Override
210 public FqName getPackageFqName() {
211 return getFile().getPackageFqName();
212 }
213
214 @Override
215 public GenerationState.GenerateClassFilter getGenerateClassFilter() {
216 return new GenerationState.GenerateClassFilter() {
217 @Override
218 public boolean shouldProcess(JetClassOrObject generatedClassOrObject) {
219 // Trivial: generate and analyze class we are interested in.
220 if (generatedClassOrObject == classOrObject) return true;
221
222 // Process all parent classes as they are context for current class
223 // Process child classes because they probably affect members (heuristic)
224 if (PsiTreeUtil.isAncestor(generatedClassOrObject, classOrObject, true) ||
225 PsiTreeUtil.isAncestor(classOrObject, generatedClassOrObject, true)) {
226 return true;
227 }
228
229 if (generatedClassOrObject.isLocal() && classOrObject.isLocal()) {
230 // Local classes should be process by CodegenAnnotatingVisitor to
231 // decide what class they should be placed in.
232 //
233 // Example:
234 // class A
235 // fun foo() {
236 // trait Z: A {}
237 // fun bar() {
238 // class <caret>O2: Z {}
239 // }
240 // }
241
242 // TODO: current method will process local classes in irrelevant declarations, it should be fixed.
243 PsiElement commonParent = PsiTreeUtil.findCommonParent(generatedClassOrObject, classOrObject);
244 return commonParent != null && !(commonParent instanceof PsiFile);
245 }
246
247 return false;
248 }
249 };
250 }
251
252 @Override
253 public void generate(@NotNull GenerationState state, @NotNull Collection<JetFile> files) {
254 PackageCodegen packageCodegen = state.getFactory().forPackage(getPackageFqName(), files);
255 packageCodegen.generateClassOrObject(classOrObject);
256 state.getFactory().asList();
257 }
258
259 @Override
260 public String toString() {
261 return StubGenerationStrategy.class.getName() + " for explicit class " + classOrObject.getName();
262 }
263 }
264 );
265 }
266
267 private static final Logger LOG = Logger.getInstance(KotlinJavaFileStubProvider.class);
268
269 private final Project project;
270 private final StubGenerationStrategy<T> stubGenerationStrategy;
271 private final boolean local;
272
273 private KotlinJavaFileStubProvider(
274 @NotNull Project project,
275 boolean local,
276 @NotNull StubGenerationStrategy<T> stubGenerationStrategy
277 ) {
278 this.project = project;
279 this.stubGenerationStrategy = stubGenerationStrategy;
280 this.local = local;
281 }
282
283 @Nullable
284 @Override
285 public Result<T> compute() {
286 FqName packageFqName = stubGenerationStrategy.getPackageFqName();
287 Collection<JetFile> files = stubGenerationStrategy.getFiles();
288
289 checkForBuiltIns(packageFqName, files);
290
291 LightClassConstructionContext context = stubGenerationStrategy.getContext(files);
292
293 PsiJavaFileStub javaFileStub = createJavaFileStub(packageFqName, getRepresentativeVirtualFile(files));
294 BindingContext bindingContext;
295 BindingTraceContext forExtraDiagnostics = new BindingTraceContext();
296 try {
297 Stack<StubElement> stubStack = new Stack<StubElement>();
298 stubStack.push(javaFileStub);
299
300 GenerationState state = new GenerationState(
301 project,
302 new KotlinLightClassBuilderFactory(stubStack),
303 Progress.DEAF,
304 context.getModule(),
305 context.getBindingContext(),
306 Lists.newArrayList(files),
307 /*disable not-null assertions*/false, false,
308 /*generateClassFilter=*/stubGenerationStrategy.getGenerateClassFilter(),
309 /*disableInline=*/false,
310 /*disableOptimization=*/false,
311 null,
312 null,
313 forExtraDiagnostics,
314 null
315 );
316 KotlinCodegenFacade.prepareForCompilation(state);
317
318 bindingContext = state.getBindingContext();
319
320 stubGenerationStrategy.generate(state, files);
321
322 StubElement pop = stubStack.pop();
323 if (pop != javaFileStub) {
324 LOG.error("Unbalanced stack operations: " + pop);
325 }
326 }
327 catch (ProcessCanceledException e) {
328 throw e;
329 }
330 catch (RuntimeException e) {
331 logErrorWithOSInfo(e, packageFqName, null);
332 throw e;
333 }
334
335 Diagnostics extraDiagnostics = forExtraDiagnostics.getBindingContext().getDiagnostics();
336 return Result.create(
337 stubGenerationStrategy.createLightClassData(javaFileStub, bindingContext, extraDiagnostics),
338 local ? PsiModificationTracker.MODIFICATION_COUNT : PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT
339 );
340 }
341
342 @NotNull
343 private PsiJavaFileStub createJavaFileStub(@NotNull final FqName packageFqName, @NotNull VirtualFile virtualFile) {
344 PsiManager manager = PsiManager.getInstance(project);
345
346 final PsiJavaFileStubImpl javaFileStub = new PsiJavaFileStubImpl(packageFqName.asString(), true);
347 javaFileStub.setPsiFactory(new ClsWrapperStubPsiFactory());
348
349 ClsFileImpl fakeFile =
350 new ClsFileImpl((PsiManagerImpl) manager, new ClassFileViewProvider(manager, virtualFile)) {
351 @NotNull
352 @Override
353 public PsiClassHolderFileStub getStub() {
354 return javaFileStub;
355 }
356
357 @NotNull
358 @Override
359 public String getPackageName() {
360 return packageFqName.asString();
361 }
362 };
363
364 fakeFile.setPhysical(false);
365 javaFileStub.setPsi(fakeFile);
366 return javaFileStub;
367 }
368
369 @NotNull
370 private static VirtualFile getRepresentativeVirtualFile(@NotNull Collection<JetFile> files) {
371 JetFile firstFile = files.iterator().next();
372 VirtualFile virtualFile = files.size() == 1 ? firstFile.getVirtualFile() : new LightVirtualFile();
373 assert virtualFile != null : "No virtual file for " + firstFile;
374 return virtualFile;
375 }
376
377 private static void checkForBuiltIns(@NotNull FqName fqName, @NotNull Collection<JetFile> files) {
378 for (JetFile file : files) {
379 if (LightClassUtil.belongsToKotlinBuiltIns(file)) {
380 // We may not fail later due to some luck, but generating JetLightClasses for built-ins is a bad idea anyways
381 // If it fails later, there will be an exception logged
382 logErrorWithOSInfo(null, fqName, file.getVirtualFile());
383 }
384 }
385 }
386
387 private static void logErrorWithOSInfo(@Nullable Throwable cause, @NotNull FqName fqName, @Nullable VirtualFile virtualFile) {
388 String path = virtualFile == null ? "<null>" : virtualFile.getPath();
389 LOG.error(
390 "Could not generate LightClass for " + fqName + " declared in " + path + "\n" +
391 "built-ins dir URL is " + LightClassUtil.getBuiltInsDirUrl() + "\n" +
392 "System: " + SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION + " Java Runtime: " + SystemInfo.JAVA_RUNTIME_VERSION,
393 cause);
394 }
395
396 private interface StubGenerationStrategy<T extends WithFileStubAndExtraDiagnostics> {
397 @NotNull Collection<JetFile> getFiles();
398 @NotNull FqName getPackageFqName();
399
400 @NotNull LightClassConstructionContext getContext(@NotNull Collection<JetFile> files);
401 @NotNull T createLightClassData(PsiJavaFileStub javaFileStub, BindingContext bindingContext, Diagnostics extraDiagnostics);
402
403 GenerationState.GenerateClassFilter getGenerateClassFilter();
404 void generate(@NotNull GenerationState state, @NotNull Collection<JetFile> files);
405 }
406 }