001 /*
002 * Copyright 2010-2015 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.kotlin.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.compiled.ClsFileImpl;
030 import com.intellij.psi.impl.java.stubs.PsiJavaFileStub;
031 import com.intellij.psi.impl.java.stubs.impl.PsiJavaFileStubImpl;
032 import com.intellij.psi.search.GlobalSearchScope;
033 import com.intellij.psi.stubs.PsiClassHolderFileStub;
034 import com.intellij.psi.stubs.StubElement;
035 import com.intellij.psi.util.CachedValueProvider;
036 import com.intellij.psi.util.PsiModificationTracker;
037 import com.intellij.psi.util.PsiTreeUtil;
038 import com.intellij.util.containers.ContainerUtil;
039 import com.intellij.util.containers.Stack;
040 import kotlin.jvm.functions.Function0;
041 import org.jetbrains.annotations.NotNull;
042 import org.jetbrains.annotations.Nullable;
043 import org.jetbrains.kotlin.codegen.CompilationErrorHandler;
044 import org.jetbrains.kotlin.codegen.KotlinCodegenFacade;
045 import org.jetbrains.kotlin.codegen.MultifileClassCodegen;
046 import org.jetbrains.kotlin.codegen.PackageCodegen;
047 import org.jetbrains.kotlin.codegen.binding.CodegenBinding;
048 import org.jetbrains.kotlin.codegen.context.PackageContext;
049 import org.jetbrains.kotlin.codegen.state.GenerationState;
050 import org.jetbrains.kotlin.descriptors.ClassDescriptor;
051 import org.jetbrains.kotlin.fileClasses.FileClasses;
052 import org.jetbrains.kotlin.fileClasses.JvmFileClassInfo;
053 import org.jetbrains.kotlin.fileClasses.NoResolveFileClassesProvider;
054 import org.jetbrains.kotlin.name.FqName;
055 import org.jetbrains.kotlin.psi.KtClassOrObject;
056 import org.jetbrains.kotlin.psi.KtFile;
057 import org.jetbrains.kotlin.psi.KtPsiUtil;
058 import org.jetbrains.kotlin.psi.KtScript;
059 import org.jetbrains.kotlin.resolve.BindingContext;
060 import org.jetbrains.kotlin.resolve.BindingTraceContext;
061 import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics;
062 import org.jetbrains.kotlin.resolve.jvm.JvmClassName;
063 import org.jetbrains.org.objectweb.asm.Type;
064
065 import java.util.Collection;
066 import java.util.Collections;
067 import java.util.Map;
068
069 import static org.jetbrains.kotlin.resolve.DescriptorToSourceUtils.descriptorToDeclaration;
070
071 public class KotlinJavaFileStubProvider<T extends WithFileStubAndExtraDiagnostics> implements CachedValueProvider<T> {
072
073 @NotNull
074 public static KotlinJavaFileStubProvider<KotlinFacadeLightClassData> createForPackageClass(
075 @NotNull final Project project,
076 @NotNull final FqName packageFqName,
077 @NotNull final GlobalSearchScope searchScope
078 ) {
079 return new KotlinJavaFileStubProvider<KotlinFacadeLightClassData>(
080 project,
081 false,
082 new StubGenerationStrategy<KotlinFacadeLightClassData>() {
083 @NotNull
084 @Override
085 public LightClassConstructionContext getContext(@NotNull Collection<KtFile> files) {
086 return LightClassGenerationSupport.getInstance(project).getContextForPackage(files);
087 }
088
089 @NotNull
090 @Override
091 public Collection<KtFile> getFiles() {
092 // Don't memoize this, it can be called again after an out-of-code-block modification occurs,
093 // and the set of files changes
094 return LightClassGenerationSupport.getInstance(project).findFilesForPackage(packageFqName, searchScope);
095 }
096
097 @NotNull
098 @Override
099 public KotlinFacadeLightClassData createLightClassData(
100 PsiJavaFileStub javaFileStub,
101 BindingContext bindingContext,
102 Diagnostics extraDiagnostics
103 ) {
104 return new KotlinFacadeLightClassData(javaFileStub, extraDiagnostics);
105 }
106
107 @NotNull
108 @Override
109 public FqName getPackageFqName() {
110 return packageFqName;
111 }
112
113 @Override
114 public GenerationState.GenerateClassFilter getGenerateClassFilter() {
115 return new GenerationState.GenerateClassFilter() {
116
117 @Override
118 public boolean shouldGeneratePackagePart(KtFile jetFile) {
119 return true;
120 }
121
122 @Override
123 public boolean shouldAnnotateClass(KtClassOrObject classOrObject) {
124 return shouldGenerateClass(classOrObject);
125 }
126
127 @Override
128 public boolean shouldGenerateClass(KtClassOrObject classOrObject) {
129 // Top-level classes and such should not be generated for performance reasons.
130 // Local classes in top-level functions must still be generated
131 return KtPsiUtil.isLocal(classOrObject);
132 }
133
134 @Override
135 public boolean shouldGenerateScript(KtScript script) {
136 // Scripts yield top-level classes, and should not be generated
137 return false;
138 }
139 };
140 }
141
142 @Override
143 public void generate(@NotNull GenerationState state, @NotNull Collection<KtFile> files) {
144 KotlinCodegenFacade.doGenerateFiles(files, state, CompilationErrorHandler.THROW_EXCEPTION);
145 }
146
147 @Override
148 public String toString() {
149 return StubGenerationStrategy.class.getName() + " for package class";
150 }
151 }
152 );
153 }
154
155 @NotNull
156 public static CachedValueProvider<KotlinFacadeLightClassData> createForFacadeClass(
157 @NotNull final Project project,
158 @NotNull final FqName facadeFqName,
159 @NotNull final GlobalSearchScope searchScope
160 ) {
161 return new KotlinJavaFileStubProvider<KotlinFacadeLightClassData>(
162 project,
163 false,
164 new StubGenerationStrategy<KotlinFacadeLightClassData>() {
165 @NotNull
166 @Override
167 public Collection<KtFile> getFiles() {
168 return LightClassGenerationSupport.getInstance(project).findFilesForFacade(facadeFqName, searchScope);
169 }
170
171 @NotNull
172 @Override
173 public FqName getPackageFqName() {
174 return facadeFqName.parent();
175 }
176
177 @NotNull
178 @Override
179 public LightClassConstructionContext getContext(@NotNull Collection<KtFile> files) {
180 return LightClassGenerationSupport.getInstance(project).getContextForFacade(files);
181 }
182
183 @NotNull
184 @Override
185 public KotlinFacadeLightClassData createLightClassData(
186 PsiJavaFileStub javaFileStub,
187 BindingContext bindingContext,
188 Diagnostics extraDiagnostics
189 ) {
190 return new KotlinFacadeLightClassData(javaFileStub, extraDiagnostics);
191 }
192
193 @Override
194 public GenerationState.GenerateClassFilter getGenerateClassFilter() {
195 return new GenerationState.GenerateClassFilter() {
196 @Override
197 public boolean shouldAnnotateClass(KtClassOrObject classOrObject) {
198 return shouldGenerateClass(classOrObject);
199 }
200
201 @Override
202 public boolean shouldGenerateClass(KtClassOrObject classOrObject) {
203 return KtPsiUtil.isLocal(classOrObject);
204 }
205
206 @Override
207 public boolean shouldGeneratePackagePart(KtFile jetFile) {
208 return true;
209 }
210
211 @Override
212 public boolean shouldGenerateScript(KtScript script) {
213 return false;
214 }
215 };
216 }
217
218 @Override
219 public void generate(@NotNull GenerationState state, @NotNull Collection<KtFile> files) {
220 if (!files.isEmpty()) {
221 KtFile representativeFile = files.iterator().next();
222 JvmFileClassInfo fileClassInfo = NoResolveFileClassesProvider.INSTANCE$.getFileClassInfo(representativeFile);
223 if (!fileClassInfo.getWithJvmMultifileClass()) {
224 PackageCodegen codegen = state.getFactory().forPackage(representativeFile.getPackageFqName(), files);
225 codegen.generate(CompilationErrorHandler.THROW_EXCEPTION);
226 state.getFactory().asList();
227 return;
228 }
229 }
230
231 MultifileClassCodegen codegen = state.getFactory().forMultifileClass(facadeFqName, files);
232 codegen.generate(CompilationErrorHandler.THROW_EXCEPTION);
233 state.getFactory().asList();
234 }
235
236 @Override
237 public String toString() {
238 return StubGenerationStrategy.class.getName() + " for facade class";
239 }
240 });
241 }
242
243 @NotNull
244 public static KotlinJavaFileStubProvider<OutermostKotlinClassLightClassData> createForDeclaredClass(@NotNull final KtClassOrObject classOrObject) {
245 return new KotlinJavaFileStubProvider<OutermostKotlinClassLightClassData>(
246 classOrObject.getProject(),
247 classOrObject.isLocal(),
248 new StubGenerationStrategy<OutermostKotlinClassLightClassData>() {
249 private KtFile getFile() {
250 return classOrObject.getContainingJetFile();
251 }
252
253 @NotNull
254 @Override
255 public LightClassConstructionContext getContext(@NotNull Collection<KtFile> files) {
256 return LightClassGenerationSupport.getInstance(classOrObject.getProject()).getContextForClassOrObject(classOrObject);
257 }
258
259 @NotNull
260 @Override
261 public OutermostKotlinClassLightClassData createLightClassData(
262 PsiJavaFileStub javaFileStub,
263 BindingContext bindingContext,
264 Diagnostics extraDiagnostics
265 ) {
266 ClassDescriptor classDescriptor = bindingContext.get(BindingContext.CLASS, classOrObject);
267 if (classDescriptor == null) {
268 return new OutermostKotlinClassLightClassData(
269 javaFileStub, extraDiagnostics, FqName.ROOT, classOrObject,
270 Collections.<KtClassOrObject, InnerKotlinClassLightClassData>emptyMap()
271 );
272 }
273
274 FqName fqName = predictClassFqName(bindingContext, classDescriptor);
275 Collection<ClassDescriptor> allInnerClasses = CodegenBinding.getAllInnerClasses(bindingContext, classDescriptor);
276
277 Map<KtClassOrObject, InnerKotlinClassLightClassData> innerClassesMap = ContainerUtil.newHashMap();
278 for (ClassDescriptor innerClassDescriptor : allInnerClasses) {
279 PsiElement declaration = descriptorToDeclaration(innerClassDescriptor);
280 if (!(declaration instanceof KtClassOrObject)) continue;
281 KtClassOrObject innerClass = (KtClassOrObject) declaration;
282
283 InnerKotlinClassLightClassData innerLightClassData = new InnerKotlinClassLightClassData(
284 predictClassFqName(bindingContext, innerClassDescriptor),
285 innerClass
286 );
287
288 innerClassesMap.put(innerClass, innerLightClassData);
289 }
290
291 return new OutermostKotlinClassLightClassData(
292 javaFileStub,
293 extraDiagnostics,
294 fqName,
295 classOrObject,
296 innerClassesMap
297 );
298 }
299
300 @NotNull
301 private FqName predictClassFqName(BindingContext bindingContext, ClassDescriptor classDescriptor) {
302 Type asmType = CodegenBinding.getAsmType(bindingContext, classDescriptor);
303 //noinspection ConstantConditions
304 return JvmClassName.byInternalName(asmType.getClassName().replace('.', '/')).getFqNameForClassNameWithoutDollars();
305 }
306
307 @NotNull
308 @Override
309 public Collection<KtFile> getFiles() {
310 return Collections.singletonList(getFile());
311 }
312
313 @NotNull
314 @Override
315 public FqName getPackageFqName() {
316 return getFile().getPackageFqName();
317 }
318
319 @Override
320 public GenerationState.GenerateClassFilter getGenerateClassFilter() {
321 return new GenerationState.GenerateClassFilter() {
322
323 @Override
324 public boolean shouldGeneratePackagePart(KtFile jetFile) {
325 return true;
326 }
327
328 @Override
329 public boolean shouldAnnotateClass(KtClassOrObject classOrObject) {
330 return shouldGenerateClass(classOrObject);
331 }
332
333 @Override
334 public boolean shouldGenerateClass(KtClassOrObject generatedClassOrObject) {
335 // Trivial: generate and analyze class we are interested in.
336 if (generatedClassOrObject == classOrObject) return true;
337
338 // Process all parent classes as they are context for current class
339 // Process child classes because they probably affect members (heuristic)
340 if (PsiTreeUtil.isAncestor(generatedClassOrObject, classOrObject, true) ||
341 PsiTreeUtil.isAncestor(classOrObject, generatedClassOrObject, true)) {
342 return true;
343 }
344
345 if (generatedClassOrObject.isLocal() && classOrObject.isLocal()) {
346 // Local classes should be process by CodegenAnnotatingVisitor to
347 // decide what class they should be placed in.
348 //
349 // Example:
350 // class A
351 // fun foo() {
352 // trait Z: A {}
353 // fun bar() {
354 // class <caret>O2: Z {}
355 // }
356 // }
357
358 // TODO: current method will process local classes in irrelevant declarations, it should be fixed.
359 PsiElement commonParent = PsiTreeUtil.findCommonParent(generatedClassOrObject, classOrObject);
360 return commonParent != null && !(commonParent instanceof PsiFile);
361 }
362
363 return false;
364 }
365
366 @Override
367 public boolean shouldGenerateScript(KtScript script) {
368 // We generate all enclosing classes
369 return PsiTreeUtil.isAncestor(script, classOrObject, false);
370 }
371 };
372 }
373
374 @Override
375 public void generate(@NotNull GenerationState state, @NotNull Collection<KtFile> files) {
376 PackageCodegen packageCodegen = state.getFactory().forPackage(getPackageFqName(), files);
377 KtFile file = classOrObject.getContainingJetFile();
378 Type packagePartType = FileClasses.getFileClassType(state.getFileClassesProvider(), file);
379 PackageContext context = state.getRootContext().intoPackagePart(packageCodegen.getPackageFragment(), packagePartType, file);
380 packageCodegen.generateClassOrObject(classOrObject, context);
381 state.getFactory().asList();
382 }
383
384 @Override
385 public String toString() {
386 return StubGenerationStrategy.class.getName() + " for explicit class " + classOrObject.getName();
387 }
388 }
389 );
390 }
391
392 private static final Logger LOG = Logger.getInstance(KotlinJavaFileStubProvider.class);
393
394 private final Project project;
395 private final StubGenerationStrategy<T> stubGenerationStrategy;
396 private final boolean local;
397
398 private KotlinJavaFileStubProvider(
399 @NotNull Project project,
400 boolean local,
401 @NotNull StubGenerationStrategy<T> stubGenerationStrategy
402 ) {
403 this.project = project;
404 this.stubGenerationStrategy = stubGenerationStrategy;
405 this.local = local;
406 }
407
408 @Nullable
409 @Override
410 public Result<T> compute() {
411 FqName packageFqName = stubGenerationStrategy.getPackageFqName();
412 Collection<KtFile> files = stubGenerationStrategy.getFiles();
413
414 checkForBuiltIns(packageFqName, files);
415
416 LightClassConstructionContext context = stubGenerationStrategy.getContext(files);
417
418 PsiJavaFileStub javaFileStub = createJavaFileStub(packageFqName, files);
419 BindingContext bindingContext;
420 BindingTraceContext forExtraDiagnostics = new BindingTraceContext();
421 try {
422 Stack<StubElement> stubStack = new Stack<StubElement>();
423 stubStack.push(javaFileStub);
424
425 GenerationState state = new GenerationState(
426 project,
427 new KotlinLightClassBuilderFactory(stubStack),
428 context.getModule(),
429 context.getBindingContext(),
430 Lists.newArrayList(files),
431 /*disable not-null assertions*/false, false,
432 /*generateClassFilter=*/stubGenerationStrategy.getGenerateClassFilter(),
433 /*disableInline=*/false,
434 /*disableOptimization=*/false,
435 /*useTypeTableInSerializer=*/false,
436 forExtraDiagnostics
437 );
438 KotlinCodegenFacade.prepareForCompilation(state);
439
440 bindingContext = state.getBindingContext();
441
442 stubGenerationStrategy.generate(state, files);
443
444 StubElement pop = stubStack.pop();
445 if (pop != javaFileStub) {
446 LOG.error("Unbalanced stack operations: " + pop);
447 }
448 }
449 catch (ProcessCanceledException e) {
450 throw e;
451 }
452 catch (RuntimeException e) {
453 logErrorWithOSInfo(e, packageFqName, null);
454 throw e;
455 }
456
457 Diagnostics extraDiagnostics = forExtraDiagnostics.getBindingContext().getDiagnostics();
458 return Result.create(
459 stubGenerationStrategy.createLightClassData(javaFileStub, bindingContext, extraDiagnostics),
460 local ? PsiModificationTracker.MODIFICATION_COUNT : PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT
461 );
462 }
463
464 @NotNull
465 private static ClsFileImpl createFakeClsFile(
466 @NotNull Project project,
467 @NotNull final FqName packageFqName,
468 @NotNull Collection<KtFile> files,
469 @NotNull final Function0<? extends PsiClassHolderFileStub> fileStubProvider
470 ) {
471 PsiManager manager = PsiManager.getInstance(project);
472
473 VirtualFile virtualFile = getRepresentativeVirtualFile(files);
474 ClsFileImpl fakeFile = new ClsFileImpl(new ClassFileViewProvider(manager, virtualFile)) {
475 @NotNull
476 @Override
477 public PsiClassHolderFileStub getStub() {
478 return fileStubProvider.invoke();
479 }
480
481 @NotNull
482 @Override
483 public String getPackageName() {
484 return packageFqName.asString();
485 }
486 };
487
488 fakeFile.setPhysical(false);
489 return fakeFile;
490 }
491
492 @NotNull
493 private PsiJavaFileStub createJavaFileStub(@NotNull FqName packageFqName, @NotNull Collection<KtFile> files) {
494 final PsiJavaFileStubImpl javaFileStub = new PsiJavaFileStubImpl(packageFqName.asString(), true);
495 javaFileStub.setPsiFactory(new ClsWrapperStubPsiFactory());
496
497 ClsFileImpl fakeFile = createFakeClsFile(project, packageFqName, files, new Function0<PsiClassHolderFileStub>() {
498 @Override
499 public PsiClassHolderFileStub invoke() {
500 return javaFileStub;
501 }
502 });
503
504 javaFileStub.setPsi(fakeFile);
505 return javaFileStub;
506 }
507
508 @NotNull
509 private static VirtualFile getRepresentativeVirtualFile(@NotNull Collection<KtFile> files) {
510 KtFile firstFile = files.iterator().next();
511 VirtualFile virtualFile = firstFile.getVirtualFile();
512 assert virtualFile != null : "No virtual file for " + firstFile;
513 return virtualFile;
514 }
515
516 private static void checkForBuiltIns(@NotNull FqName fqName, @NotNull Collection<KtFile> files) {
517 for (KtFile file : files) {
518 if (LightClassUtil.INSTANCE$.belongsToKotlinBuiltIns(file)) {
519 // We may not fail later due to some luck, but generating JetLightClasses for built-ins is a bad idea anyways
520 // If it fails later, there will be an exception logged
521 logErrorWithOSInfo(null, fqName, file.getVirtualFile());
522 }
523 }
524 }
525
526 private static void logErrorWithOSInfo(@Nullable Throwable cause, @NotNull FqName fqName, @Nullable VirtualFile virtualFile) {
527 String path = virtualFile == null ? "<null>" : virtualFile.getPath();
528 LOG.error(
529 "Could not generate LightClass for " + fqName + " declared in " + path + "\n" +
530 "built-ins dir URL is " + LightClassUtil.INSTANCE$.getBuiltInsDirUrl() + "\n" +
531 "System: " + SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION + " Java Runtime: " + SystemInfo.JAVA_RUNTIME_VERSION,
532 cause);
533 }
534
535 private interface StubGenerationStrategy<T extends WithFileStubAndExtraDiagnostics> {
536 @NotNull Collection<KtFile> getFiles();
537 @NotNull FqName getPackageFqName();
538
539 @NotNull LightClassConstructionContext getContext(@NotNull Collection<KtFile> files);
540 @NotNull T createLightClassData(PsiJavaFileStub javaFileStub, BindingContext bindingContext, Diagnostics extraDiagnostics);
541
542 GenerationState.GenerateClassFilter getGenerateClassFilter();
543 void generate(@NotNull GenerationState state, @NotNull Collection<KtFile> files);
544 }
545 }