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    }