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.codegen.binding;
018
019import com.intellij.openapi.util.Ref;
020import com.intellij.psi.PsiElement;
021import com.intellij.psi.util.PsiTreeUtil;
022import org.jetbrains.annotations.NotNull;
023import org.jetbrains.annotations.Nullable;
024import org.jetbrains.jet.codegen.NamespaceCodegen;
025import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
026import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
027import org.jetbrains.jet.lang.psi.*;
028import org.jetbrains.jet.lang.resolve.BindingContext;
029import org.jetbrains.jet.lang.resolve.BindingContextUtils;
030import org.jetbrains.jet.lang.resolve.BindingTrace;
031import org.jetbrains.jet.lang.resolve.DelegatingBindingTrace;
032import org.jetbrains.jet.lang.resolve.java.JvmAbi;
033import org.jetbrains.jet.lang.resolve.java.JvmClassName;
034import org.jetbrains.jet.lang.resolve.java.PackageClassUtils;
035import org.jetbrains.jet.lang.resolve.name.FqName;
036import org.jetbrains.jet.lang.resolve.name.Name;
037import org.jetbrains.jet.util.slicedmap.WritableSlice;
038
039import java.util.List;
040
041import static org.jetbrains.jet.lang.resolve.BindingContextUtils.descriptorToDeclaration;
042
043public final class PsiCodegenPredictor {
044    private PsiCodegenPredictor() {
045    }
046
047    public static boolean checkPredictedNameFromPsi(
048            @NotNull BindingTrace bindingTrace, @NotNull DeclarationDescriptor descriptor, JvmClassName nameFromDescriptors
049    ) {
050        PsiElement element = descriptorToDeclaration(bindingTrace.getBindingContext(), descriptor);
051        if (element instanceof JetDeclaration) {
052            JvmClassName classNameFromPsi = getPredefinedJvmClassName((JetDeclaration) element);
053            assert classNameFromPsi == null || classNameFromPsi.equals(nameFromDescriptors) :
054                    String.format("Invalid algorithm for getting qualified name from psi! Predicted: %s, actual %s\n" +
055                                  "Element: %s", classNameFromPsi, nameFromDescriptors, element.getText());
056        }
057
058        return true;
059    }
060
061    @Nullable
062    public static JvmClassName getPredefinedJvmClassName(@NotNull JetFile jetFile, boolean withNamespace) {
063        String packageName = jetFile.getPackageName();
064        if (packageName == null) {
065            return null;
066        }
067
068        JvmClassName packageJvmName = JvmClassName.byFqNameWithoutInnerClasses(packageName);
069        return !withNamespace ? packageJvmName : addPackageClass(packageJvmName);
070    }
071
072    /**
073     * TODO: Finish this method for all cases. Now it's only used and tested in JetLightClass.
074     *
075     * @return null if no prediction can be done.
076     */
077    @Nullable
078    public static JvmClassName getPredefinedJvmClassName(@NotNull JetDeclaration declaration) {
079        // TODO: Method won't work for declarations inside class objects
080        // TODO: Method won't give correct class name for traits implementations
081
082        JetDeclaration parentDeclaration = PsiTreeUtil.getParentOfType(declaration, JetDeclaration.class);
083        if (parentDeclaration instanceof JetClassObject) {
084            assert declaration instanceof JetObjectDeclaration : "Only object declarations can be children of JetClassObject: " + declaration;
085            return getPredefinedJvmClassName(parentDeclaration);
086        }
087
088        JvmClassName parentClassName = parentDeclaration != null ?
089                                       getPredefinedJvmClassName(parentDeclaration) :
090                                       getPredefinedJvmClassName((JetFile) declaration.getContainingFile(), false);
091        if (parentClassName == null) {
092            return null;
093        }
094
095        if (declaration instanceof JetClassObject) {
096            // Get parent and assign Class object prefix
097            return JvmClassName.byInternalName(parentClassName.getInternalName() + JvmAbi.CLASS_OBJECT_SUFFIX);
098        }
099
100        if (declaration instanceof JetNamedDeclaration) {
101            if (!PsiTreeUtil.instanceOf(declaration, JetClass.class, JetObjectDeclaration.class, JetNamedFunction.class, JetProperty.class) ||
102                    declaration instanceof JetEnumEntry) {
103                // Other subclasses are not valid for class name prediction.
104                // For example EnumEntry, JetFunctionLiteral
105                return null;
106            }
107
108            JetNamedDeclaration namedDeclaration = (JetNamedDeclaration) declaration;
109            Name name = namedDeclaration.getNameAsName();
110            if (name == null) {
111                return null;
112            }
113
114            FqName fqName = parentClassName.getFqName();
115
116            if (declaration instanceof JetNamedFunction) {
117                if (parentDeclaration == null) {
118                    JvmClassName packageClass = addPackageClass(parentClassName);
119                    return JvmClassName.byInternalName(packageClass.getInternalName() + "$" + name.asString());
120                }
121
122                if (!(parentDeclaration instanceof JetClass || parentDeclaration instanceof JetObjectDeclaration)) {
123                    // Can't generate predefined name for internal functions
124                    return null;
125                }
126            }
127
128            // NOTE: looks like a bug - for class in getter of top level property class name will be $propertyName$ClassName but not
129            // namespace$propertyName$ClassName
130            if (declaration instanceof JetProperty) {
131                return JvmClassName.byInternalName(parentClassName.getInternalName() + "$" + name.asString());
132            }
133
134            if (fqName.isRoot()) {
135                return JvmClassName.byInternalName(name.asString());
136            }
137
138            return JvmClassName.byInternalName(parentDeclaration == null ?
139                                               parentClassName.getInternalName() + "/" + name.asString() :
140                                               parentClassName.getInternalName() + "$" + name.asString());
141        }
142
143        return null;
144    }
145
146    private static JvmClassName addPackageClass(JvmClassName packageName) {
147        FqName name = packageName.getFqName();
148        String packageClassName = PackageClassUtils.getPackageClassName(name);
149        return name.isRoot() ?
150               JvmClassName.byFqNameWithoutInnerClasses(packageClassName) :
151               JvmClassName.byInternalName(packageName.getInternalName() + "/" + packageClassName);
152    }
153
154    public static boolean checkPredictedClassNameForFun(
155            BindingContext bindingContext, @NotNull DeclarationDescriptor descriptor,
156            ClassDescriptor classDescriptor
157    ) {
158        PsiElement element = descriptorToDeclaration(bindingContext, descriptor);
159        PsiElement classDeclaration = descriptorToDeclaration(bindingContext, classDescriptor);
160        if (element instanceof JetNamedFunction && classDeclaration instanceof JetDeclaration) {
161            JvmClassName classNameFromPsi = getPredefinedJvmClassName((JetDeclaration) classDeclaration);
162            JvmClassName classNameForFun = getPredefinedJvmClassNameForFun((JetNamedFunction) element);
163            assert classNameForFun == null || classNameForFun.equals(classNameFromPsi) : "Invalid algorithm for getting enclosing method name!";
164        }
165
166        return true;
167    }
168
169    @Nullable
170    public static JvmClassName getPredefinedJvmClassNameForFun(@NotNull JetNamedFunction function) {
171        PsiElement parent = function.getParent();
172        if (parent instanceof JetFile) {
173            return getPredefinedJvmClassName((JetFile) parent, true);
174        }
175
176        @SuppressWarnings("unchecked")
177        JetClass containingClass = PsiTreeUtil.getParentOfType(function, JetClass.class, true, JetDeclaration.class);
178        if (containingClass != null) {
179            return getPredefinedJvmClassName(containingClass);
180        }
181
182        @SuppressWarnings("unchecked")
183        JetObjectDeclaration objectDeclaration = PsiTreeUtil.getParentOfType(function, JetObjectDeclaration.class, true, JetDeclaration.class);
184        if (objectDeclaration != null) {
185            if (objectDeclaration.getParent() instanceof JetClassObject) {
186                return getPredefinedJvmClassName((JetClassObject) objectDeclaration.getParent());
187            }
188
189            return getPredefinedJvmClassName(objectDeclaration);
190        }
191
192        return null;
193    }
194
195    @Nullable
196    public static JetFile getFileForNamespacePartName(@NotNull List<JetFile> allNamespaceFiles, @NotNull JvmClassName className) {
197        for (JetFile file : allNamespaceFiles) {
198            String internalName = NamespaceCodegen.getNamespacePartInternalName(file);
199            JvmClassName jvmClassName = JvmClassName.byInternalName(internalName);
200            if (jvmClassName.equals(className)) {
201                return file;
202            }
203        }
204        return null;
205    }
206
207    @Nullable
208    public static JetFile getFileForCodegenNamedClass(
209            @NotNull BindingContext context,
210            @NotNull List<JetFile> allNamespaceFiles,
211            @NotNull final JvmClassName className
212    ) {
213        final Ref<DeclarationDescriptor> resultingDescriptor = Ref.create();
214
215        DelegatingBindingTrace trace = new DelegatingBindingTrace(context, "trace in PsiCodegenPredictor") {
216            @Override
217            public <K, V> void record(WritableSlice<K, V> slice, K key, V value) {
218                super.record(slice, key, value);
219                if (slice == CodegenBinding.FQN && key instanceof DeclarationDescriptor) {
220                    if (className.equals(value)) {
221                        resultingDescriptor.set((DeclarationDescriptor) key);
222                    }
223                }
224            }
225        };
226
227        CodegenBinding.initTrace(trace, allNamespaceFiles);
228
229        return resultingDescriptor.isNull() ? null
230               : BindingContextUtils.getContainingFile(trace.getBindingContext(), resultingDescriptor.get());
231    }
232}