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.AsmUtil;
025    import org.jetbrains.jet.codegen.ClassBuilderFactories;
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.descriptors.ModuleDescriptor;
030    import org.jetbrains.jet.lang.psi.*;
031    import org.jetbrains.jet.lang.resolve.BindingContext;
032    import org.jetbrains.jet.lang.resolve.BindingTrace;
033    import org.jetbrains.jet.lang.resolve.DescriptorToSourceUtils;
034    import org.jetbrains.jet.lang.resolve.java.JvmAbi;
035    import org.jetbrains.jet.lang.resolve.java.JvmClassName;
036    import org.jetbrains.jet.lang.resolve.java.PackageClassUtils;
037    import org.jetbrains.jet.lang.resolve.kotlin.PackagePartClassUtils;
038    import org.jetbrains.jet.lang.resolve.name.FqName;
039    import org.jetbrains.jet.lang.resolve.name.Name;
040    import org.jetbrains.org.objectweb.asm.Type;
041    
042    import java.util.ArrayList;
043    import java.util.Collection;
044    
045    import static org.jetbrains.jet.lang.resolve.DescriptorToSourceUtils.descriptorToDeclaration;
046    
047    public final class PsiCodegenPredictor {
048        private PsiCodegenPredictor() {
049        }
050    
051        public static boolean checkPredictedNameFromPsi(@NotNull DeclarationDescriptor descriptor, @Nullable Type nameFromDescriptors) {
052            PsiElement element = descriptorToDeclaration(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 = JetStubbedPsiUtil.getContainingDeclaration(declaration);
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                    Name name = ((JetNamedFunction) declaration).getNameAsName();
089                    return name == null ? null : PackageClassUtils.getPackageClassInternalName(packageFqName) + "$" + name.asString();
090                }
091    
092                parentInternalName = AsmUtil.internalNameByFqNameWithoutInnerClasses(packageFqName);
093            }
094    
095            if (declaration instanceof JetClassObject) {
096                // Get parent and assign Class object prefix
097                return parentInternalName + JvmAbi.CLASS_OBJECT_SUFFIX;
098            }
099    
100            if (!PsiTreeUtil.instanceOf(declaration, JetClass.class, JetObjectDeclaration.class, JetNamedFunction.class, JetProperty.class) ||
101                    declaration instanceof JetEnumEntry) {
102                // Other subclasses are not valid for class name prediction.
103                // For example EnumEntry, JetFunctionLiteral
104                return null;
105            }
106    
107            Name name = ((JetNamedDeclaration) declaration).getNameAsName();
108            if (name == null) {
109                return null;
110            }
111    
112            if (declaration instanceof JetNamedFunction) {
113                if (!(parentDeclaration instanceof JetClass || parentDeclaration instanceof JetObjectDeclaration)) {
114                    // Can't generate predefined name for internal functions
115                    return null;
116                }
117            }
118    
119            // NOTE: looks like a bug - for class in getter of top level property class name will be $propertyName$ClassName but not
120            // PackageClassName$propertyName$ClassName
121            if (declaration instanceof JetProperty) {
122                return parentInternalName + "$" + name.asString();
123            }
124    
125            if (parentInternalName.isEmpty()) {
126                return name.asString();
127            }
128    
129            return parentInternalName + (parentDeclaration == null ? "/" : "$") + name.asString();
130        }
131    
132        @Nullable
133        public static JetFile getFileForPackagePartName(@NotNull Collection<JetFile> allPackageFiles, @NotNull JvmClassName className) {
134            for (JetFile file : allPackageFiles) {
135                String internalName = PackagePartClassUtils.getPackagePartInternalName(file);
136                JvmClassName jvmClassName = JvmClassName.byInternalName(internalName);
137                if (jvmClassName.equals(className)) {
138                    return file;
139                }
140            }
141            return null;
142        }
143    
144        @Nullable
145        public static JetFile getFileForCodegenNamedClass(
146                @NotNull ModuleDescriptor module,
147                @NotNull BindingContext context,
148                @NotNull Collection<JetFile> allPackageFiles,
149                @NotNull String classInternalName
150        ) {
151            Project project = allPackageFiles.iterator().next().getProject();
152            GenerationState state = new GenerationState(project, ClassBuilderFactories.THROW_EXCEPTION, module, context,
153                                                        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 DescriptorToSourceUtils.getContainingFile(classDescriptor);
161                }
162            }
163    
164            return null;
165        }
166    }