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.List;
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 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 }