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.jet.codegen.NamespaceCodegen;
025    import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
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.java.PackageClassUtils;
035    import org.jetbrains.jet.lang.resolve.name.FqName;
036    import org.jetbrains.jet.lang.resolve.name.Name;
037    import org.jetbrains.jet.util.slicedmap.WritableSlice;
038    
039    import java.util.Collection;
040    
041    import static org.jetbrains.jet.lang.resolve.BindingContextUtils.descriptorToDeclaration;
042    
043    public final class PsiCodegenPredictor {
044        private PsiCodegenPredictor() {
045        }
046    
047        public static boolean checkPredictedNameFromPsi(
048                @NotNull BindingTrace bindingTrace, @NotNull DeclarationDescriptor descriptor, JvmClassName nameFromDescriptors
049        ) {
050            PsiElement element = descriptorToDeclaration(bindingTrace.getBindingContext(), descriptor);
051            if (element instanceof JetDeclaration) {
052                JvmClassName classNameFromPsi = getPredefinedJvmClassName((JetDeclaration) element);
053                assert classNameFromPsi == null || 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        @Nullable
062        public static JvmClassName getPredefinedJvmClassName(@NotNull JetFile jetFile, boolean withNamespace) {
063            String packageName = jetFile.getPackageName();
064            if (packageName == null) {
065                return null;
066            }
067    
068            JvmClassName packageJvmName = JvmClassName.byFqNameWithoutInnerClasses(packageName);
069            return !withNamespace ? packageJvmName : addPackageClass(packageJvmName);
070        }
071    
072        /**
073         * TODO: Finish this method for all cases. Now it's only used and tested in JetLightClass.
074         *
075         * @return null if no prediction can be done.
076         */
077        @Nullable
078        public static JvmClassName getPredefinedJvmClassName(@NotNull JetDeclaration declaration) {
079            // TODO: Method won't work for declarations inside class objects
080            // TODO: Method won't give correct class name for traits implementations
081    
082            JetDeclaration parentDeclaration = PsiTreeUtil.getParentOfType(declaration, JetDeclaration.class);
083            if (parentDeclaration instanceof JetClassObject) {
084                assert declaration instanceof JetObjectDeclaration : "Only object declarations can be children of JetClassObject: " + declaration;
085                return getPredefinedJvmClassName(parentDeclaration);
086            }
087    
088            JvmClassName parentClassName = parentDeclaration != null ?
089                                           getPredefinedJvmClassName(parentDeclaration) :
090                                           getPredefinedJvmClassName((JetFile) declaration.getContainingFile(), false);
091            if (parentClassName == null) {
092                return null;
093            }
094    
095            if (declaration instanceof JetClassObject) {
096                // Get parent and assign Class object prefix
097                return JvmClassName.byInternalName(parentClassName.getInternalName() + JvmAbi.CLASS_OBJECT_SUFFIX);
098            }
099    
100            if (declaration instanceof JetNamedDeclaration) {
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                JetNamedDeclaration namedDeclaration = (JetNamedDeclaration) declaration;
109                Name name = namedDeclaration.getNameAsName();
110                if (name == null) {
111                    return null;
112                }
113    
114                FqName fqName = parentClassName.getFqName();
115    
116                if (declaration instanceof JetNamedFunction) {
117                    if (parentDeclaration == null) {
118                        JvmClassName packageClass = addPackageClass(parentClassName);
119                        return JvmClassName.byInternalName(packageClass.getInternalName() + "$" + name.asString());
120                    }
121    
122                    if (!(parentDeclaration instanceof JetClass || parentDeclaration instanceof JetObjectDeclaration)) {
123                        // Can't generate predefined name for internal functions
124                        return null;
125                    }
126                }
127    
128                // NOTE: looks like a bug - for class in getter of top level property class name will be $propertyName$ClassName but not
129                // namespace$propertyName$ClassName
130                if (declaration instanceof JetProperty) {
131                    return JvmClassName.byInternalName(parentClassName.getInternalName() + "$" + name.asString());
132                }
133    
134                if (fqName.isRoot()) {
135                    return JvmClassName.byInternalName(name.asString());
136                }
137    
138                return JvmClassName.byInternalName(parentDeclaration == null ?
139                                                   parentClassName.getInternalName() + "/" + name.asString() :
140                                                   parentClassName.getInternalName() + "$" + name.asString());
141            }
142    
143            return null;
144        }
145    
146        private static JvmClassName addPackageClass(JvmClassName packageName) {
147            FqName name = packageName.getFqName();
148            String packageClassName = PackageClassUtils.getPackageClassName(name);
149            return name.isRoot() ?
150                   JvmClassName.byFqNameWithoutInnerClasses(packageClassName) :
151                   JvmClassName.byInternalName(packageName.getInternalName() + "/" + packageClassName);
152        }
153    
154        public static boolean checkPredictedClassNameForFun(
155                BindingContext bindingContext, @NotNull DeclarationDescriptor descriptor,
156                ClassDescriptor classDescriptor
157        ) {
158            PsiElement element = descriptorToDeclaration(bindingContext, descriptor);
159            PsiElement classDeclaration = descriptorToDeclaration(bindingContext, classDescriptor);
160            if (element instanceof JetNamedFunction && classDeclaration instanceof JetDeclaration) {
161                JvmClassName classNameFromPsi = getPredefinedJvmClassName((JetDeclaration) classDeclaration);
162                JvmClassName classNameForFun = getPredefinedJvmClassNameForFun((JetNamedFunction) element);
163                assert classNameForFun == null || classNameForFun.equals(classNameFromPsi) : "Invalid algorithm for getting enclosing method name!";
164            }
165    
166            return true;
167        }
168    
169        @Nullable
170        public static JvmClassName getPredefinedJvmClassNameForFun(@NotNull JetNamedFunction function) {
171            PsiElement parent = function.getParent();
172            if (parent instanceof JetFile) {
173                return getPredefinedJvmClassName((JetFile) parent, true);
174            }
175    
176            @SuppressWarnings("unchecked")
177            JetClass containingClass = PsiTreeUtil.getParentOfType(function, JetClass.class, true, JetDeclaration.class);
178            if (containingClass != null) {
179                return getPredefinedJvmClassName(containingClass);
180            }
181    
182            @SuppressWarnings("unchecked")
183            JetObjectDeclaration objectDeclaration = PsiTreeUtil.getParentOfType(function, JetObjectDeclaration.class, true, JetDeclaration.class);
184            if (objectDeclaration != null) {
185                if (objectDeclaration.getParent() instanceof JetClassObject) {
186                    return getPredefinedJvmClassName((JetClassObject) objectDeclaration.getParent());
187                }
188    
189                return getPredefinedJvmClassName(objectDeclaration);
190            }
191    
192            return null;
193        }
194    
195        @Nullable
196        public static JetFile getFileForNamespacePartName(@NotNull Collection<JetFile> allNamespaceFiles, @NotNull JvmClassName className) {
197            for (JetFile file : allNamespaceFiles) {
198                String internalName = NamespaceCodegen.getNamespacePartInternalName(file);
199                JvmClassName jvmClassName = JvmClassName.byInternalName(internalName);
200                if (jvmClassName.equals(className)) {
201                    return file;
202                }
203            }
204            return null;
205        }
206    
207        @Nullable
208        public static JetFile getFileForCodegenNamedClass(
209                @NotNull BindingContext context,
210                @NotNull Collection<JetFile> allNamespaceFiles,
211                @NotNull final JvmClassName className
212        ) {
213            final Ref<DeclarationDescriptor> resultingDescriptor = Ref.create();
214    
215            DelegatingBindingTrace trace = new DelegatingBindingTrace(context, "trace in PsiCodegenPredictor") {
216                @Override
217                public <K, V> void record(WritableSlice<K, V> slice, K key, V value) {
218                    super.record(slice, key, value);
219                    if (slice == CodegenBinding.FQN && key instanceof DeclarationDescriptor) {
220                        if (className.equals(value)) {
221                            resultingDescriptor.set((DeclarationDescriptor) key);
222                        }
223                    }
224                }
225            };
226    
227            CodegenBinding.initTrace(trace, allNamespaceFiles);
228    
229            return resultingDescriptor.isNull() ? null
230                   : BindingContextUtils.getContainingFile(trace.getBindingContext(), resultingDescriptor.get());
231        }
232    }