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