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.project.Project;
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.jet.codegen.ClassBuilderFactories;
025    import org.jetbrains.jet.codegen.PackageCodegen;
026    import org.jetbrains.jet.codegen.state.GenerationState;
027    import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
028    import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
029    import org.jetbrains.jet.lang.psi.*;
030    import org.jetbrains.jet.lang.resolve.BindingContext;
031    import org.jetbrains.jet.lang.resolve.BindingContextUtils;
032    import org.jetbrains.jet.lang.resolve.BindingTrace;
033    import org.jetbrains.jet.lang.resolve.java.JvmAbi;
034    import org.jetbrains.jet.lang.resolve.java.JvmClassName;
035    import org.jetbrains.jet.lang.resolve.name.FqName;
036    import org.jetbrains.jet.lang.resolve.name.Name;
037    import org.jetbrains.org.objectweb.asm.Type;
038    
039    import java.util.ArrayList;
040    import java.util.Collection;
041    
042    import static org.jetbrains.jet.lang.resolve.BindingContextUtils.descriptorToDeclaration;
043    import static org.jetbrains.jet.lang.resolve.java.PackageClassUtils.getPackageClassFqName;
044    
045    public final class PsiCodegenPredictor {
046        private PsiCodegenPredictor() {
047        }
048    
049        public static boolean checkPredictedNameFromPsi(
050                @NotNull BindingContext bindingContext, @NotNull DeclarationDescriptor descriptor, @Nullable Type nameFromDescriptors
051        ) {
052            PsiElement element = descriptorToDeclaration(bindingContext, descriptor);
053            if (element instanceof JetDeclaration) {
054                String classNameFromPsi = getPredefinedJvmInternalName((JetDeclaration) element);
055                assert classNameFromPsi == null || Type.getObjectType(classNameFromPsi).equals(nameFromDescriptors) :
056                        String.format("Invalid algorithm for getting qualified name from psi! Predicted: %s, actual %s\n" +
057                                      "Element: %s", classNameFromPsi, nameFromDescriptors, element.getText());
058            }
059    
060            return true;
061        }
062    
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                FqName packageFqName = declaration.getContainingJetFile().getPackageFqName();
086    
087                if (declaration instanceof JetNamedFunction) {
088                    JvmClassName packageClass = JvmClassName.byFqNameWithoutInnerClasses(getPackageClassFqName(packageFqName));
089                    Name name = ((JetNamedFunction) declaration).getNameAsName();
090                    return name == null ? null : packageClass.getInternalName() + "$" + name.asString();
091                }
092    
093                parentInternalName = JvmClassName.byFqNameWithoutInnerClasses(packageFqName).getInternalName();
094            }
095    
096            if (declaration instanceof JetClassObject) {
097                // Get parent and assign Class object prefix
098                return parentInternalName + JvmAbi.CLASS_OBJECT_SUFFIX;
099            }
100    
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            Name name = ((JetNamedDeclaration) declaration).getNameAsName();
109            if (name == null) {
110                return null;
111            }
112    
113            if (declaration instanceof JetNamedFunction) {
114                if (!(parentDeclaration instanceof JetClass || parentDeclaration instanceof JetObjectDeclaration)) {
115                    // Can't generate predefined name for internal functions
116                    return null;
117                }
118            }
119    
120            // NOTE: looks like a bug - for class in getter of top level property class name will be $propertyName$ClassName but not
121            // PackageClassName$propertyName$ClassName
122            if (declaration instanceof JetProperty) {
123                return parentInternalName + "$" + name.asString();
124            }
125    
126            if (parentInternalName.isEmpty()) {
127                return name.asString();
128            }
129    
130            return parentInternalName + (parentDeclaration == null ? "/" : "$") + name.asString();
131        }
132    
133        @Nullable
134        public static JetFile getFileForPackagePartName(@NotNull Collection<JetFile> allPackageFiles, @NotNull JvmClassName className) {
135            for (JetFile file : allPackageFiles) {
136                String internalName = PackageCodegen.getPackagePartInternalName(file);
137                JvmClassName jvmClassName = JvmClassName.byInternalName(internalName);
138                if (jvmClassName.equals(className)) {
139                    return file;
140                }
141            }
142            return null;
143        }
144    
145        @Nullable
146        public static JetFile getFileForCodegenNamedClass(
147                @NotNull BindingContext context,
148                @NotNull Collection<JetFile> allPackageFiles,
149                @NotNull String classInternalName
150        ) {
151            Project project = allPackageFiles.iterator().next().getProject();
152            GenerationState state =
153                    new GenerationState(project, ClassBuilderFactories.THROW_EXCEPTION, context, new ArrayList<JetFile>(allPackageFiles));
154            state.beforeCompile();
155    
156            BindingTrace trace = state.getBindingTrace();
157            for (ClassDescriptor classDescriptor : trace.getKeys(CodegenBinding.ASM_TYPE)) {
158                Type type = trace.get(CodegenBinding.ASM_TYPE, classDescriptor);
159                if (type != null && classInternalName.equals(type.getInternalName())) {
160                    return BindingContextUtils.getContainingFile(trace.getBindingContext(), classDescriptor);
161                }
162            }
163    
164            return null;
165        }
166    }