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    
017    package org.jetbrains.jet.codegen.binding;
018    
019    import com.intellij.openapi.util.Ref;
020    import com.intellij.psi.PsiElement;
021    import com.intellij.psi.util.PsiTreeUtil;
022    import org.jetbrains.annotations.NotNull;
023    import org.jetbrains.annotations.Nullable;
024    import org.jetbrains.asm4.Type;
025    import org.jetbrains.jet.codegen.NamespaceCodegen;
026    import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
027    import org.jetbrains.jet.lang.psi.*;
028    import org.jetbrains.jet.lang.resolve.BindingContext;
029    import org.jetbrains.jet.lang.resolve.BindingContextUtils;
030    import org.jetbrains.jet.lang.resolve.BindingTrace;
031    import org.jetbrains.jet.lang.resolve.DelegatingBindingTrace;
032    import org.jetbrains.jet.lang.resolve.java.JvmAbi;
033    import org.jetbrains.jet.lang.resolve.java.JvmClassName;
034    import org.jetbrains.jet.lang.resolve.name.FqName;
035    import org.jetbrains.jet.lang.resolve.name.Name;
036    import org.jetbrains.jet.util.slicedmap.WritableSlice;
037    
038    import java.util.Collection;
039    
040    import static org.jetbrains.jet.lang.resolve.BindingContextUtils.descriptorToDeclaration;
041    import static org.jetbrains.jet.lang.resolve.java.PackageClassUtils.getPackageClassFqName;
042    
043    public final class PsiCodegenPredictor {
044        private PsiCodegenPredictor() {
045        }
046    
047        public static boolean checkPredictedNameFromPsi(
048                @NotNull BindingTrace bindingTrace, @NotNull DeclarationDescriptor descriptor, @Nullable Type nameFromDescriptors
049        ) {
050            PsiElement element = descriptorToDeclaration(bindingTrace.getBindingContext(), descriptor);
051            if (element instanceof JetDeclaration) {
052                String classNameFromPsi = getPredefinedJvmInternalName((JetDeclaration) element);
053                assert classNameFromPsi == null || Type.getObjectType(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        /**
062         * TODO: Finish this method for all cases. Now it's only used and tested in JetLightClass.
063         *
064         * @return null if no prediction can be done.
065         */
066        @Nullable
067        public static String getPredefinedJvmInternalName(@NotNull JetDeclaration declaration) {
068            // TODO: Method won't work for declarations inside class objects
069            // TODO: Method won't give correct class name for traits implementations
070    
071            JetDeclaration parentDeclaration = PsiTreeUtil.getParentOfType(declaration, JetDeclaration.class);
072            if (parentDeclaration instanceof JetClassObject) {
073                assert declaration instanceof JetObjectDeclaration : "Only object declarations can be children of JetClassObject: " + declaration;
074                return getPredefinedJvmInternalName(parentDeclaration);
075            }
076    
077            String parentInternalName;
078            if (parentDeclaration != null) {
079                parentInternalName = getPredefinedJvmInternalName(parentDeclaration);
080                if (parentInternalName == null) {
081                    return null;
082                }
083            }
084            else {
085                String packageName = ((JetFile) declaration.getContainingFile()).getPackageName();
086                if (packageName == null) {
087                    return null;
088                }
089    
090                if (declaration instanceof JetNamedFunction) {
091                    JvmClassName packageClass = JvmClassName.byFqNameWithoutInnerClasses(getPackageClassFqName(new FqName(packageName)));
092                    Name name = ((JetNamedFunction) declaration).getNameAsName();
093                    return name == null ? null : packageClass.getInternalName() + "$" + name.asString();
094                }
095    
096                parentInternalName = JvmClassName.byFqNameWithoutInnerClasses(packageName).getInternalName();
097            }
098    
099            if (declaration instanceof JetClassObject) {
100                // Get parent and assign Class object prefix
101                return parentInternalName + JvmAbi.CLASS_OBJECT_SUFFIX;
102            }
103    
104            if (!PsiTreeUtil.instanceOf(declaration, JetClass.class, JetObjectDeclaration.class, JetNamedFunction.class, JetProperty.class) ||
105                    declaration instanceof JetEnumEntry) {
106                // Other subclasses are not valid for class name prediction.
107                // For example EnumEntry, JetFunctionLiteral
108                return null;
109            }
110    
111            Name name = ((JetNamedDeclaration) declaration).getNameAsName();
112            if (name == null) {
113                return null;
114            }
115    
116            if (declaration instanceof JetNamedFunction) {
117                if (!(parentDeclaration instanceof JetClass || parentDeclaration instanceof JetObjectDeclaration)) {
118                    // Can't generate predefined name for internal functions
119                    return null;
120                }
121            }
122    
123            // NOTE: looks like a bug - for class in getter of top level property class name will be $propertyName$ClassName but not
124            // namespace$propertyName$ClassName
125            if (declaration instanceof JetProperty) {
126                return parentInternalName + "$" + name.asString();
127            }
128    
129            if (parentInternalName.isEmpty()) {
130                return name.asString();
131            }
132    
133            return parentInternalName + (parentDeclaration == null ? "/" : "$") + name.asString();
134        }
135    
136        @Nullable
137        public static JetFile getFileForNamespacePartName(@NotNull Collection<JetFile> allNamespaceFiles, @NotNull JvmClassName className) {
138            for (JetFile file : allNamespaceFiles) {
139                String internalName = NamespaceCodegen.getNamespacePartInternalName(file);
140                JvmClassName jvmClassName = JvmClassName.byInternalName(internalName);
141                if (jvmClassName.equals(className)) {
142                    return file;
143                }
144            }
145            return null;
146        }
147    
148        @Nullable
149        public static JetFile getFileForCodegenNamedClass(
150                @NotNull BindingContext context,
151                @NotNull Collection<JetFile> allNamespaceFiles,
152                @NotNull final String classInternalName
153        ) {
154            final Ref<DeclarationDescriptor> resultingDescriptor = Ref.create();
155    
156            DelegatingBindingTrace trace = new DelegatingBindingTrace(context, "trace in PsiCodegenPredictor") {
157                @Override
158                public <K, V> void record(WritableSlice<K, V> slice, K key, V value) {
159                    super.record(slice, key, value);
160                    if (slice == CodegenBinding.ASM_TYPE && key instanceof DeclarationDescriptor && value instanceof Type) {
161                        if (classInternalName.equals(((Type) value).getInternalName())) {
162                            resultingDescriptor.set((DeclarationDescriptor) key);
163                        }
164                    }
165                }
166            };
167    
168            CodegenBinding.initTrace(trace, allNamespaceFiles);
169    
170            return resultingDescriptor.isNull() ? null
171                   : BindingContextUtils.getContainingFile(trace.getBindingContext(), resultingDescriptor.get());
172        }
173    }