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.asm4.Type;
025 import org.jetbrains.jet.codegen.NamespaceCodegen;
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.name.FqName;
035 import org.jetbrains.jet.lang.resolve.name.Name;
036 import org.jetbrains.jet.util.slicedmap.WritableSlice;
037
038 import java.util.Collection;
039
040 import static org.jetbrains.jet.lang.resolve.BindingContextUtils.descriptorToDeclaration;
041 import static org.jetbrains.jet.lang.resolve.java.PackageClassUtils.getPackageClassFqName;
042
043 public final class PsiCodegenPredictor {
044 private PsiCodegenPredictor() {
045 }
046
047 public static boolean checkPredictedNameFromPsi(
048 @NotNull BindingTrace bindingTrace, @NotNull DeclarationDescriptor descriptor, @Nullable Type nameFromDescriptors
049 ) {
050 PsiElement element = descriptorToDeclaration(bindingTrace.getBindingContext(), descriptor);
051 if (element instanceof JetDeclaration) {
052 String classNameFromPsi = getPredefinedJvmInternalName((JetDeclaration) element);
053 assert classNameFromPsi == null || Type.getObjectType(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 /**
062 * TODO: Finish this method for all cases. Now it's only used and tested in JetLightClass.
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 = PsiTreeUtil.getParentOfType(declaration, JetDeclaration.class);
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 String packageName = ((JetFile) declaration.getContainingFile()).getPackageName();
086 if (packageName == null) {
087 return null;
088 }
089
090 if (declaration instanceof JetNamedFunction) {
091 JvmClassName packageClass = JvmClassName.byFqNameWithoutInnerClasses(getPackageClassFqName(new FqName(packageName)));
092 Name name = ((JetNamedFunction) declaration).getNameAsName();
093 return name == null ? null : packageClass.getInternalName() + "$" + name.asString();
094 }
095
096 parentInternalName = JvmClassName.byFqNameWithoutInnerClasses(packageName).getInternalName();
097 }
098
099 if (declaration instanceof JetClassObject) {
100 // Get parent and assign Class object prefix
101 return parentInternalName + JvmAbi.CLASS_OBJECT_SUFFIX;
102 }
103
104 if (!PsiTreeUtil.instanceOf(declaration, JetClass.class, JetObjectDeclaration.class, JetNamedFunction.class, JetProperty.class) ||
105 declaration instanceof JetEnumEntry) {
106 // Other subclasses are not valid for class name prediction.
107 // For example EnumEntry, JetFunctionLiteral
108 return null;
109 }
110
111 Name name = ((JetNamedDeclaration) declaration).getNameAsName();
112 if (name == null) {
113 return null;
114 }
115
116 if (declaration instanceof JetNamedFunction) {
117 if (!(parentDeclaration instanceof JetClass || parentDeclaration instanceof JetObjectDeclaration)) {
118 // Can't generate predefined name for internal functions
119 return null;
120 }
121 }
122
123 // NOTE: looks like a bug - for class in getter of top level property class name will be $propertyName$ClassName but not
124 // namespace$propertyName$ClassName
125 if (declaration instanceof JetProperty) {
126 return parentInternalName + "$" + name.asString();
127 }
128
129 if (parentInternalName.isEmpty()) {
130 return name.asString();
131 }
132
133 return parentInternalName + (parentDeclaration == null ? "/" : "$") + name.asString();
134 }
135
136 @Nullable
137 public static JetFile getFileForNamespacePartName(@NotNull Collection<JetFile> allNamespaceFiles, @NotNull JvmClassName className) {
138 for (JetFile file : allNamespaceFiles) {
139 String internalName = NamespaceCodegen.getNamespacePartInternalName(file);
140 JvmClassName jvmClassName = JvmClassName.byInternalName(internalName);
141 if (jvmClassName.equals(className)) {
142 return file;
143 }
144 }
145 return null;
146 }
147
148 @Nullable
149 public static JetFile getFileForCodegenNamedClass(
150 @NotNull BindingContext context,
151 @NotNull Collection<JetFile> allNamespaceFiles,
152 @NotNull final String classInternalName
153 ) {
154 final Ref<DeclarationDescriptor> resultingDescriptor = Ref.create();
155
156 DelegatingBindingTrace trace = new DelegatingBindingTrace(context, "trace in PsiCodegenPredictor") {
157 @Override
158 public <K, V> void record(WritableSlice<K, V> slice, K key, V value) {
159 super.record(slice, key, value);
160 if (slice == CodegenBinding.ASM_TYPE && key instanceof DeclarationDescriptor && value instanceof Type) {
161 if (classInternalName.equals(((Type) value).getInternalName())) {
162 resultingDescriptor.set((DeclarationDescriptor) key);
163 }
164 }
165 }
166 };
167
168 CodegenBinding.initTrace(trace, allNamespaceFiles);
169
170 return resultingDescriptor.isNull() ? null
171 : BindingContextUtils.getContainingFile(trace.getBindingContext(), resultingDescriptor.get());
172 }
173 }