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