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 CachedValueProvider<KotlinFacadeLightClassData> createForFacadeClass(
075 @NotNull final Project project,
076 @NotNull final FqName facadeFqName,
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 Collection<KtFile> getFiles() {
086 return LightClassGenerationSupport.getInstance(project).findFilesForFacade(facadeFqName, searchScope);
087 }
088
089 @NotNull
090 @Override
091 public FqName getPackageFqName() {
092 return facadeFqName.parent();
093 }
094
095 @NotNull
096 @Override
097 public LightClassConstructionContext getContext(@NotNull Collection<KtFile> files) {
098 return LightClassGenerationSupport.getInstance(project).getContextForFacade(files);
099 }
100
101 @NotNull
102 @Override
103 public KotlinFacadeLightClassData createLightClassData(
104 PsiJavaFileStub javaFileStub,
105 BindingContext bindingContext,
106 Diagnostics extraDiagnostics
107 ) {
108 return new KotlinFacadeLightClassData(javaFileStub, extraDiagnostics);
109 }
110
111 @Override
112 public GenerationState.GenerateClassFilter getGenerateClassFilter() {
113 return new GenerationState.GenerateClassFilter() {
114 @Override
115 public boolean shouldAnnotateClass(KtClassOrObject classOrObject) {
116 return shouldGenerateClass(classOrObject);
117 }
118
119 @Override
120 public boolean shouldGenerateClass(KtClassOrObject classOrObject) {
121 return KtPsiUtil.isLocal(classOrObject);
122 }
123
124 @Override
125 public boolean shouldGeneratePackagePart(KtFile jetFile) {
126 return true;
127 }
128
129 @Override
130 public boolean shouldGenerateScript(KtScript script) {
131 return false;
132 }
133 };
134 }
135
136 @Override
137 public void generate(@NotNull GenerationState state, @NotNull Collection<KtFile> files) {
138 if (!files.isEmpty()) {
139 KtFile representativeFile = files.iterator().next();
140 JvmFileClassInfo fileClassInfo = NoResolveFileClassesProvider.INSTANCE.getFileClassInfo(representativeFile);
141 if (!fileClassInfo.getWithJvmMultifileClass()) {
142 PackageCodegen codegen = state.getFactory().forPackage(representativeFile.getPackageFqName(), files);
143 codegen.generate(CompilationErrorHandler.THROW_EXCEPTION);
144 state.getFactory().asList();
145 return;
146 }
147 }
148
149 MultifileClassCodegen codegen = state.getFactory().forMultifileClass(facadeFqName, files);
150 codegen.generate(CompilationErrorHandler.THROW_EXCEPTION);
151 state.getFactory().asList();
152 }
153
154 @Override
155 public String toString() {
156 return StubGenerationStrategy.class.getName() + " for facade class";
157 }
158 });
159 }
160
161 @NotNull
162 public static KotlinJavaFileStubProvider<OutermostKotlinClassLightClassData> createForDeclaredClass(@NotNull final KtClassOrObject classOrObject) {
163 return new KotlinJavaFileStubProvider<OutermostKotlinClassLightClassData>(
164 classOrObject.getProject(),
165 classOrObject.isLocal(),
166 new StubGenerationStrategy<OutermostKotlinClassLightClassData>() {
167 private KtFile getFile() {
168 return classOrObject.getContainingJetFile();
169 }
170
171 @NotNull
172 @Override
173 public LightClassConstructionContext getContext(@NotNull Collection<KtFile> files) {
174 return LightClassGenerationSupport.getInstance(classOrObject.getProject()).getContextForClassOrObject(classOrObject);
175 }
176
177 @NotNull
178 @Override
179 public OutermostKotlinClassLightClassData createLightClassData(
180 PsiJavaFileStub javaFileStub,
181 BindingContext bindingContext,
182 Diagnostics extraDiagnostics
183 ) {
184 ClassDescriptor classDescriptor = bindingContext.get(BindingContext.CLASS, classOrObject);
185 if (classDescriptor == null) {
186 return new OutermostKotlinClassLightClassData(
187 javaFileStub, extraDiagnostics, FqName.ROOT, classOrObject,
188 Collections.<KtClassOrObject, InnerKotlinClassLightClassData>emptyMap()
189 );
190 }
191
192 FqName fqName = predictClassFqName(bindingContext, classDescriptor);
193 Collection<ClassDescriptor> allInnerClasses = CodegenBinding.getAllInnerClasses(bindingContext, classDescriptor);
194
195 Map<KtClassOrObject, InnerKotlinClassLightClassData> innerClassesMap = ContainerUtil.newHashMap();
196 for (ClassDescriptor innerClassDescriptor : allInnerClasses) {
197 PsiElement declaration = descriptorToDeclaration(innerClassDescriptor);
198 if (!(declaration instanceof KtClassOrObject)) continue;
199 KtClassOrObject innerClass = (KtClassOrObject) declaration;
200
201 InnerKotlinClassLightClassData innerLightClassData = new InnerKotlinClassLightClassData(
202 predictClassFqName(bindingContext, innerClassDescriptor),
203 innerClass
204 );
205
206 innerClassesMap.put(innerClass, innerLightClassData);
207 }
208
209 return new OutermostKotlinClassLightClassData(
210 javaFileStub,
211 extraDiagnostics,
212 fqName,
213 classOrObject,
214 innerClassesMap
215 );
216 }
217
218 @NotNull
219 private FqName predictClassFqName(BindingContext bindingContext, ClassDescriptor classDescriptor) {
220 Type asmType = CodegenBinding.getAsmType(bindingContext, classDescriptor);
221 //noinspection ConstantConditions
222 return JvmClassName.byInternalName(asmType.getClassName().replace('.', '/')).getFqNameForClassNameWithoutDollars();
223 }
224
225 @NotNull
226 @Override
227 public Collection<KtFile> getFiles() {
228 return Collections.singletonList(getFile());
229 }
230
231 @NotNull
232 @Override
233 public FqName getPackageFqName() {
234 return getFile().getPackageFqName();
235 }
236
237 @Override
238 public GenerationState.GenerateClassFilter getGenerateClassFilter() {
239 return new GenerationState.GenerateClassFilter() {
240
241 @Override
242 public boolean shouldGeneratePackagePart(KtFile jetFile) {
243 return true;
244 }
245
246 @Override
247 public boolean shouldAnnotateClass(KtClassOrObject classOrObject) {
248 return shouldGenerateClass(classOrObject);
249 }
250
251 @Override
252 public boolean shouldGenerateClass(KtClassOrObject generatedClassOrObject) {
253 // Trivial: generate and analyze class we are interested in.
254 if (generatedClassOrObject == classOrObject) return true;
255
256 // Process all parent classes as they are context for current class
257 // Process child classes because they probably affect members (heuristic)
258 if (PsiTreeUtil.isAncestor(generatedClassOrObject, classOrObject, true) ||
259 PsiTreeUtil.isAncestor(classOrObject, generatedClassOrObject, true)) {
260 return true;
261 }
262
263 if (generatedClassOrObject.isLocal() && classOrObject.isLocal()) {
264 // Local classes should be process by CodegenAnnotatingVisitor to
265 // decide what class they should be placed in.
266 //
267 // Example:
268 // class A
269 // fun foo() {
270 // trait Z: A {}
271 // fun bar() {
272 // class <caret>O2: Z {}
273 // }
274 // }
275
276 // TODO: current method will process local classes in irrelevant declarations, it should be fixed.
277 PsiElement commonParent = PsiTreeUtil.findCommonParent(generatedClassOrObject, classOrObject);
278 return commonParent != null && !(commonParent instanceof PsiFile);
279 }
280
281 return false;
282 }
283
284 @Override
285 public boolean shouldGenerateScript(KtScript script) {
286 // We generate all enclosing classes
287 return PsiTreeUtil.isAncestor(script, classOrObject, false);
288 }
289 };
290 }
291
292 @Override
293 public void generate(@NotNull GenerationState state, @NotNull Collection<KtFile> files) {
294 PackageCodegen packageCodegen = state.getFactory().forPackage(getPackageFqName(), files);
295 KtFile file = classOrObject.getContainingJetFile();
296 Type packagePartType = FileClasses.getFileClassType(state.getFileClassesProvider(), file);
297 PackageContext context = state.getRootContext().intoPackagePart(packageCodegen.getPackageFragment(), packagePartType, file);
298 packageCodegen.generateClassOrObject(classOrObject, context);
299 state.getFactory().asList();
300 }
301
302 @Override
303 public String toString() {
304 return StubGenerationStrategy.class.getName() + " for explicit class " + classOrObject.getName();
305 }
306 }
307 );
308 }
309
310 private static final Logger LOG = Logger.getInstance(KotlinJavaFileStubProvider.class);
311
312 private final Project project;
313 private final StubGenerationStrategy<T> stubGenerationStrategy;
314 private final boolean local;
315
316 private KotlinJavaFileStubProvider(
317 @NotNull Project project,
318 boolean local,
319 @NotNull StubGenerationStrategy<T> stubGenerationStrategy
320 ) {
321 this.project = project;
322 this.stubGenerationStrategy = stubGenerationStrategy;
323 this.local = local;
324 }
325
326 @Nullable
327 @Override
328 public Result<T> compute() {
329 FqName packageFqName = stubGenerationStrategy.getPackageFqName();
330 Collection<KtFile> files = stubGenerationStrategy.getFiles();
331
332 checkForBuiltIns(packageFqName, files);
333
334 LightClassConstructionContext context = stubGenerationStrategy.getContext(files);
335
336 PsiJavaFileStub javaFileStub = createJavaFileStub(packageFqName, files);
337 BindingContext bindingContext;
338 BindingTraceContext forExtraDiagnostics = new BindingTraceContext();
339 try {
340 Stack<StubElement> stubStack = new Stack<StubElement>();
341 stubStack.push(javaFileStub);
342
343 GenerationState state = new GenerationState(
344 project,
345 new KotlinLightClassBuilderFactory(stubStack),
346 context.getModule(),
347 context.getBindingContext(),
348 Lists.newArrayList(files),
349 /*disable not-null assertions*/false, false,
350 /*generateClassFilter=*/stubGenerationStrategy.getGenerateClassFilter(),
351 /*disableInline=*/false,
352 /*disableOptimization=*/false,
353 /*useTypeTableInSerializer=*/false,
354 forExtraDiagnostics
355 );
356 KotlinCodegenFacade.prepareForCompilation(state);
357
358 bindingContext = state.getBindingContext();
359
360 stubGenerationStrategy.generate(state, files);
361
362 StubElement pop = stubStack.pop();
363 if (pop != javaFileStub) {
364 LOG.error("Unbalanced stack operations: " + pop);
365 }
366 }
367 catch (ProcessCanceledException e) {
368 throw e;
369 }
370 catch (RuntimeException e) {
371 logErrorWithOSInfo(e, packageFqName, null);
372 throw e;
373 }
374
375 Diagnostics extraDiagnostics = forExtraDiagnostics.getBindingContext().getDiagnostics();
376 return Result.create(
377 stubGenerationStrategy.createLightClassData(javaFileStub, bindingContext, extraDiagnostics),
378 local ? PsiModificationTracker.MODIFICATION_COUNT : PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT
379 );
380 }
381
382 @NotNull
383 private static ClsFileImpl createFakeClsFile(
384 @NotNull Project project,
385 @NotNull final FqName packageFqName,
386 @NotNull Collection<KtFile> files,
387 @NotNull final Function0<? extends PsiClassHolderFileStub> fileStubProvider
388 ) {
389 PsiManager manager = PsiManager.getInstance(project);
390
391 VirtualFile virtualFile = getRepresentativeVirtualFile(files);
392 ClsFileImpl fakeFile = new ClsFileImpl(new ClassFileViewProvider(manager, virtualFile)) {
393 @NotNull
394 @Override
395 public PsiClassHolderFileStub getStub() {
396 return fileStubProvider.invoke();
397 }
398
399 @NotNull
400 @Override
401 public String getPackageName() {
402 return packageFqName.asString();
403 }
404 };
405
406 fakeFile.setPhysical(false);
407 return fakeFile;
408 }
409
410 @NotNull
411 private PsiJavaFileStub createJavaFileStub(@NotNull FqName packageFqName, @NotNull Collection<KtFile> files) {
412 final PsiJavaFileStubImpl javaFileStub = new PsiJavaFileStubImpl(packageFqName.asString(), true);
413 javaFileStub.setPsiFactory(new ClsWrapperStubPsiFactory());
414
415 ClsFileImpl fakeFile = createFakeClsFile(project, packageFqName, files, new Function0<PsiClassHolderFileStub>() {
416 @Override
417 public PsiClassHolderFileStub invoke() {
418 return javaFileStub;
419 }
420 });
421
422 javaFileStub.setPsi(fakeFile);
423 return javaFileStub;
424 }
425
426 @NotNull
427 private static VirtualFile getRepresentativeVirtualFile(@NotNull Collection<KtFile> files) {
428 KtFile firstFile = files.iterator().next();
429 VirtualFile virtualFile = firstFile.getVirtualFile();
430 assert virtualFile != null : "No virtual file for " + firstFile;
431 return virtualFile;
432 }
433
434 private static void checkForBuiltIns(@NotNull FqName fqName, @NotNull Collection<KtFile> files) {
435 for (KtFile file : files) {
436 if (LightClassUtil.INSTANCE$.belongsToKotlinBuiltIns(file)) {
437 // We may not fail later due to some luck, but generating JetLightClasses for built-ins is a bad idea anyways
438 // If it fails later, there will be an exception logged
439 logErrorWithOSInfo(null, fqName, file.getVirtualFile());
440 }
441 }
442 }
443
444 private static void logErrorWithOSInfo(@Nullable Throwable cause, @NotNull FqName fqName, @Nullable VirtualFile virtualFile) {
445 String path = virtualFile == null ? "<null>" : virtualFile.getPath();
446 LOG.error(
447 "Could not generate LightClass for " + fqName + " declared in " + path + "\n" +
448 "built-ins dir URL is " + LightClassUtil.INSTANCE$.getBuiltInsDirUrl() + "\n" +
449 "System: " + SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION + " Java Runtime: " + SystemInfo.JAVA_RUNTIME_VERSION,
450 cause);
451 }
452
453 private interface StubGenerationStrategy<T extends WithFileStubAndExtraDiagnostics> {
454 @NotNull Collection<KtFile> getFiles();
455 @NotNull FqName getPackageFqName();
456
457 @NotNull LightClassConstructionContext getContext(@NotNull Collection<KtFile> files);
458 @NotNull T createLightClassData(PsiJavaFileStub javaFileStub, BindingContext bindingContext, Diagnostics extraDiagnostics);
459
460 GenerationState.GenerateClassFilter getGenerateClassFilter();
461 void generate(@NotNull GenerationState state, @NotNull Collection<KtFile> files);
462 }
463 }