001/*
002 * Copyright 2010-2013 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
017package org.jetbrains.jet.cli.jvm.compiler;
018
019import com.google.common.base.Predicate;
020import com.google.common.collect.Collections2;
021import com.intellij.openapi.application.ApplicationManager;
022import com.intellij.openapi.components.ServiceManager;
023import com.intellij.openapi.project.Project;
024import com.intellij.psi.PsiClass;
025import com.intellij.psi.PsiElement;
026import com.intellij.psi.search.GlobalSearchScope;
027import com.intellij.psi.search.PsiSearchScopeUtil;
028import com.intellij.util.Function;
029import com.intellij.util.SmartList;
030import com.intellij.util.containers.ContainerUtil;
031import org.jetbrains.annotations.NotNull;
032import org.jetbrains.annotations.Nullable;
033import org.jetbrains.jet.asJava.KotlinLightClassForExplicitDeclaration;
034import org.jetbrains.jet.asJava.LightClassConstructionContext;
035import org.jetbrains.jet.asJava.LightClassGenerationSupport;
036import org.jetbrains.jet.codegen.binding.PsiCodegenPredictor;
037import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
038import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
039import org.jetbrains.jet.lang.descriptors.NamespaceDescriptor;
040import org.jetbrains.jet.lang.psi.*;
041import org.jetbrains.jet.lang.resolve.*;
042import org.jetbrains.jet.lang.resolve.java.JvmAbi;
043import org.jetbrains.jet.lang.resolve.java.JvmClassName;
044import org.jetbrains.jet.lang.resolve.name.FqName;
045
046import java.util.Collection;
047import java.util.Collections;
048import java.util.List;
049
050/**
051 * This class solves the problem of interdependency between analyzing Kotlin code and generating JetLightClasses
052 *
053 * Consider the following example:
054 *
055 * KClass.kt refers to JClass.java and vice versa
056 *
057 * To analyze KClass.kt we need to load descriptors from JClass.java, and to do that we need a JetLightClass instance for KClass,
058 * which can only be constructed when the structure of KClass is known.
059 *
060 * To mitigate this, CliLightClassGenerationSupport hold a trace that is shared between the analyzer and JetLightClasses
061 */
062public class CliLightClassGenerationSupport extends LightClassGenerationSupport {
063
064    public static CliLightClassGenerationSupport getInstanceForCli(@NotNull Project project) {
065        return ServiceManager.getService(project, CliLightClassGenerationSupport.class);
066    }
067
068    private BindingTrace trace;
069
070    public CliLightClassGenerationSupport() {
071    }
072
073    @NotNull
074    public BindingTrace getTrace() {
075        if (trace == null) {
076            trace = new BindingTraceContext();
077        }
078        return trace;
079    }
080
081    public void newBindingTrace() {
082        assert ApplicationManager.getApplication().isUnitTestMode() : "Mutating project service's state shouldn't happen other than in tests";
083        trace = null;
084    }
085
086    @NotNull
087    @Override
088    public LightClassConstructionContext analyzeRelevantCode(@NotNull Collection<JetFile> files) {
089        return new LightClassConstructionContext(getTrace().getBindingContext(), null);
090    }
091
092    @NotNull
093    @Override
094    public Collection<JetClassOrObject> findClassOrObjectDeclarations(@NotNull FqName fqName, @NotNull GlobalSearchScope searchScope) {
095        ClassDescriptor classDescriptor = getTrace().get(BindingContext.FQNAME_TO_CLASS_DESCRIPTOR, fqName);
096        if (classDescriptor != null) {
097            PsiElement element = BindingContextUtils.classDescriptorToDeclaration(trace.getBindingContext(), classDescriptor);
098            if (element != null && PsiSearchScopeUtil.isInScope(searchScope, element)) {
099                return Collections.singletonList((JetClassOrObject) element);
100            }
101        }
102
103        if (JvmAbi.isClassObjectFqName(fqName)) {
104            Collection<JetClassOrObject> parentClasses = findClassOrObjectDeclarations(fqName.parent(), searchScope);
105            return ContainerUtil.mapNotNull(parentClasses,
106                                            new Function<JetClassOrObject, JetClassOrObject>() {
107                                                @Override
108                                                public JetClassOrObject fun(JetClassOrObject classOrObject) {
109                                                    if (classOrObject instanceof JetClass) {
110                                                        JetClass jetClass = (JetClass) classOrObject;
111                                                        JetClassObject classObject = jetClass.getClassObject();
112                                                        if (classObject != null) {
113                                                            return classObject.getObjectDeclaration();
114                                                        }
115                                                    }
116                                                    return null;
117                                                }
118                                            });
119        }
120
121        return Collections.emptyList();
122    }
123
124    @NotNull
125    @Override
126    public Collection<JetFile> findFilesForPackage(@NotNull FqName fqName, @NotNull final GlobalSearchScope searchScope) {
127        NamespaceDescriptor namespaceDescriptor = getTrace().get(BindingContext.FQNAME_TO_NAMESPACE_DESCRIPTOR, fqName);
128        if (namespaceDescriptor != null) {
129            Collection<JetFile> files = getTrace().get(BindingContext.NAMESPACE_TO_FILES, namespaceDescriptor);
130            if (files != null) {
131                return Collections2.filter(files, new Predicate<JetFile>() {
132                    @Override
133                    public boolean apply(JetFile input) {
134                        return PsiSearchScopeUtil.isInScope(searchScope, input);
135                    }
136                });
137            }
138        }
139        return Collections.emptyList();
140    }
141
142    @NotNull
143    @Override
144    public Collection<JetClassOrObject> findClassOrObjectDeclarationsInPackage(
145            @NotNull FqName packageFqName, @NotNull GlobalSearchScope searchScope
146    ) {
147        Collection<JetFile> files = findFilesForPackage(packageFqName, searchScope);
148        List<JetClassOrObject> result = new SmartList<JetClassOrObject>();
149        for (JetFile file : files) {
150            for (JetDeclaration declaration : file.getDeclarations()) {
151                if (declaration instanceof JetClassOrObject) {
152                    result.add((JetClassOrObject) declaration);
153                }
154            }
155        }
156        return result;
157    }
158
159    @Override
160    public boolean packageExists(
161            @NotNull FqName fqName, @NotNull GlobalSearchScope scope
162    ) {
163        return getTrace().get(BindingContext.FQNAME_TO_NAMESPACE_DESCRIPTOR, fqName) != null;
164    }
165
166    @NotNull
167    @Override
168    public Collection<FqName> getSubPackages(@NotNull FqName fqn, @NotNull GlobalSearchScope scope) {
169        NamespaceDescriptor namespaceDescriptor = getTrace().get(BindingContext.FQNAME_TO_NAMESPACE_DESCRIPTOR, fqn);
170        if (namespaceDescriptor == null) return Collections.emptyList();
171
172        Collection<DeclarationDescriptor> allDescriptors = namespaceDescriptor.getMemberScope().getAllDescriptors();
173        return ContainerUtil.mapNotNull(allDescriptors, new Function<DeclarationDescriptor, FqName>() {
174            @Override
175            public FqName fun(DeclarationDescriptor input) {
176                if (input instanceof NamespaceDescriptor) {
177                    return DescriptorUtils.getFQName(input).toSafe();
178                }
179                return null;
180            }
181        });
182    }
183
184    @Nullable
185    @Override
186    public PsiClass getPsiClass(@NotNull JetClassOrObject classOrObject) {
187        return  KotlinLightClassForExplicitDeclaration.create(classOrObject.getManager(), classOrObject);
188    }
189}