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