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.descriptors.ModuleDescriptor;
030    import org.jetbrains.jet.lang.psi.*;
031    import org.jetbrains.jet.lang.resolve.BindingContext;
032    import org.jetbrains.jet.lang.resolve.BindingContextUtils;
033    import org.jetbrains.jet.lang.resolve.BindingTrace;
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.name.FqName;
037    import org.jetbrains.jet.lang.resolve.name.Name;
038    import org.jetbrains.org.objectweb.asm.Type;
039    
040    import java.util.ArrayList;
041    import java.util.Collection;
042    
043    import static org.jetbrains.jet.lang.resolve.BindingContextUtils.descriptorToDeclaration;
044    import static org.jetbrains.jet.lang.resolve.java.PackageClassUtils.getPackageClassFqName;
045    
046    public final class PsiCodegenPredictor {
047        private PsiCodegenPredictor() {
048        }
049    
050        public static boolean checkPredictedNameFromPsi(
051                @NotNull BindingContext bindingContext, @NotNull DeclarationDescriptor descriptor, @Nullable Type nameFromDescriptors
052        ) {
053            PsiElement element = descriptorToDeclaration(bindingContext, descriptor);
054            if (element instanceof JetDeclaration) {
055                String classNameFromPsi = getPredefinedJvmInternalName((JetDeclaration) element);
056                assert classNameFromPsi == null || Type.getObjectType(classNameFromPsi).equals(nameFromDescriptors) :
057                        String.format("Invalid algorithm for getting qualified name from psi! Predicted: %s, actual %s\n" +
058                                      "Element: %s", classNameFromPsi, nameFromDescriptors, element.getText());
059            }
060    
061            return true;
062        }
063    
064        /**
065         * @return null if no prediction can be done.
066         */
067        @Nullable
068        public static String getPredefinedJvmInternalName(@NotNull JetDeclaration declaration) {
069            // TODO: Method won't work for declarations inside class objects
070            // TODO: Method won't give correct class name for traits implementations
071    
072            JetDeclaration parentDeclaration = JetStubbedPsiUtil.getContainingDeclaration(declaration);
073            if (parentDeclaration instanceof JetClassObject) {
074                assert declaration instanceof JetObjectDeclaration : "Only object declarations can be children of JetClassObject: " + declaration;
075                return getPredefinedJvmInternalName(parentDeclaration);
076            }
077    
078            String parentInternalName;
079            if (parentDeclaration != null) {
080                parentInternalName = getPredefinedJvmInternalName(parentDeclaration);
081                if (parentInternalName == null) {
082                    return null;
083                }
084            }
085            else {
086                FqName packageFqName = declaration.getContainingJetFile().getPackageFqName();
087    
088                if (declaration instanceof JetNamedFunction) {
089                    JvmClassName packageClass = JvmClassName.byFqNameWithoutInnerClasses(getPackageClassFqName(packageFqName));
090                    Name name = ((JetNamedFunction) declaration).getNameAsName();
091                    return name == null ? null : packageClass.getInternalName() + "$" + name.asString();
092                }
093    
094                parentInternalName = JvmClassName.byFqNameWithoutInnerClasses(packageFqName).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 ModuleDescriptor module,
149                @NotNull BindingContext context,
150                @NotNull Collection<JetFile> allPackageFiles,
151                @NotNull String classInternalName
152        ) {
153            Project project = allPackageFiles.iterator().next().getProject();
154            GenerationState state = new GenerationState(project, ClassBuilderFactories.THROW_EXCEPTION, module, context,
155                                                        new ArrayList<JetFile>(allPackageFiles));
156            state.beforeCompile();
157    
158            BindingTrace trace = state.getBindingTrace();
159            for (ClassDescriptor classDescriptor : trace.getKeys(CodegenBinding.ASM_TYPE)) {
160                Type type = trace.get(CodegenBinding.ASM_TYPE, classDescriptor);
161                if (type != null && classInternalName.equals(type.getInternalName())) {
162                    return BindingContextUtils.getContainingFile(trace.getBindingContext(), classDescriptor);
163                }
164            }
165    
166            return null;
167        }
168    }