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 017package org.jetbrains.jet.codegen.binding; 018 019import com.intellij.openapi.util.Ref; 020import com.intellij.psi.PsiElement; 021import com.intellij.psi.util.PsiTreeUtil; 022import org.jetbrains.annotations.NotNull; 023import org.jetbrains.annotations.Nullable; 024import org.jetbrains.jet.codegen.NamespaceCodegen; 025import org.jetbrains.jet.lang.descriptors.ClassDescriptor; 026import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; 027import org.jetbrains.jet.lang.psi.*; 028import org.jetbrains.jet.lang.resolve.BindingContext; 029import org.jetbrains.jet.lang.resolve.BindingContextUtils; 030import org.jetbrains.jet.lang.resolve.BindingTrace; 031import org.jetbrains.jet.lang.resolve.DelegatingBindingTrace; 032import org.jetbrains.jet.lang.resolve.java.JvmAbi; 033import org.jetbrains.jet.lang.resolve.java.JvmClassName; 034import org.jetbrains.jet.lang.resolve.java.PackageClassUtils; 035import org.jetbrains.jet.lang.resolve.name.FqName; 036import org.jetbrains.jet.lang.resolve.name.Name; 037import org.jetbrains.jet.util.slicedmap.WritableSlice; 038 039import java.util.List; 040 041import static org.jetbrains.jet.lang.resolve.BindingContextUtils.descriptorToDeclaration; 042 043public 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 List<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 List<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}