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