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.PackageCodegen;
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         * @return null if no prediction can be done.
063         */
064        @Nullable
065        public static String getPredefinedJvmInternalName(@NotNull JetDeclaration declaration) {
066            // TODO: Method won't work for declarations inside class objects
067            // TODO: Method won't give correct class name for traits implementations
068    
069            JetDeclaration parentDeclaration = PsiTreeUtil.getParentOfType(declaration, JetDeclaration.class);
070            if (parentDeclaration instanceof JetClassObject) {
071                assert declaration instanceof JetObjectDeclaration : "Only object declarations can be children of JetClassObject: " + declaration;
072                return getPredefinedJvmInternalName(parentDeclaration);
073            }
074    
075            String parentInternalName;
076            if (parentDeclaration != null) {
077                parentInternalName = getPredefinedJvmInternalName(parentDeclaration);
078                if (parentInternalName == null) {
079                    return null;
080                }
081            }
082            else {
083                String packageName = ((JetFile) declaration.getContainingFile()).getPackageName();
084                if (packageName == null) {
085                    return null;
086                }
087    
088                if (declaration instanceof JetNamedFunction) {
089                    JvmClassName packageClass = JvmClassName.byFqNameWithoutInnerClasses(getPackageClassFqName(new FqName(packageName)));
090                    Name name = ((JetNamedFunction) declaration).getNameAsName();
091                    return name == null ? null : packageClass.getInternalName() + "$" + name.asString();
092                }
093    
094                parentInternalName = JvmClassName.byFqNameWithoutInnerClasses(packageName).getInternalName();
095            }
096    
097            if (declaration instanceof JetClassObject) {
098                // Get parent and assign Class object prefix
099                return parentInternalName + JvmAbi.CLASS_OBJECT_SUFFIX;
100            }
101    
102            if (!PsiTreeUtil.instanceOf(declaration, JetClass.class, JetObjectDeclaration.class, JetNamedFunction.class, JetProperty.class) ||
103                    declaration instanceof JetEnumEntry) {
104                // Other subclasses are not valid for class name prediction.
105                // For example EnumEntry, JetFunctionLiteral
106                return null;
107            }
108    
109            Name name = ((JetNamedDeclaration) declaration).getNameAsName();
110            if (name == null) {
111                return null;
112            }
113    
114            if (declaration instanceof JetNamedFunction) {
115                if (!(parentDeclaration instanceof JetClass || parentDeclaration instanceof JetObjectDeclaration)) {
116                    // Can't generate predefined name for internal functions
117                    return null;
118                }
119            }
120    
121            // NOTE: looks like a bug - for class in getter of top level property class name will be $propertyName$ClassName but not
122            // PackageClassName$propertyName$ClassName
123            if (declaration instanceof JetProperty) {
124                return parentInternalName + "$" + name.asString();
125            }
126    
127            if (parentInternalName.isEmpty()) {
128                return name.asString();
129            }
130    
131            return parentInternalName + (parentDeclaration == null ? "/" : "$") + name.asString();
132        }
133    
134        @Nullable
135        public static JetFile getFileForPackagePartName(@NotNull Collection<JetFile> allPackageFiles, @NotNull JvmClassName className) {
136            for (JetFile file : allPackageFiles) {
137                String internalName = PackageCodegen.getPackagePartInternalName(file);
138                JvmClassName jvmClassName = JvmClassName.byInternalName(internalName);
139                if (jvmClassName.equals(className)) {
140                    return file;
141                }
142            }
143            return null;
144        }
145    
146        @Nullable
147        public static JetFile getFileForCodegenNamedClass(
148                @NotNull BindingContext context,
149                @NotNull Collection<JetFile> allPackageFiles,
150                @NotNull final String classInternalName
151        ) {
152            final Ref<DeclarationDescriptor> resultingDescriptor = Ref.create();
153    
154            DelegatingBindingTrace trace = new DelegatingBindingTrace(context, "trace in PsiCodegenPredictor") {
155                @Override
156                public <K, V> void record(WritableSlice<K, V> slice, K key, V value) {
157                    super.record(slice, key, value);
158                    if (slice == CodegenBinding.ASM_TYPE && key instanceof DeclarationDescriptor && value instanceof Type) {
159                        if (classInternalName.equals(((Type) value).getInternalName())) {
160                            resultingDescriptor.set((DeclarationDescriptor) key);
161                        }
162                    }
163                }
164            };
165    
166            CodegenBinding.initTrace(trace, allPackageFiles);
167    
168            return resultingDescriptor.isNull() ? null
169                   : BindingContextUtils.getContainingFile(trace.getBindingContext(), resultingDescriptor.get());
170        }
171    }