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.inline;
018
019 import com.intellij.openapi.components.ServiceManager;
020 import com.intellij.openapi.project.Project;
021 import com.intellij.openapi.vfs.VirtualFile;
022 import com.intellij.psi.PsiElement;
023 import com.intellij.psi.PsiFile;
024 import org.jetbrains.annotations.NotNull;
025 import org.jetbrains.annotations.Nullable;
026 import org.jetbrains.asm4.*;
027 import org.jetbrains.asm4.tree.MethodNode;
028 import org.jetbrains.jet.codegen.PackageCodegen;
029 import org.jetbrains.jet.codegen.binding.CodegenBinding;
030 import org.jetbrains.jet.codegen.context.CodegenContext;
031 import org.jetbrains.jet.codegen.context.PackageContext;
032 import org.jetbrains.jet.codegen.state.GenerationState;
033 import org.jetbrains.jet.codegen.state.JetTypeMapper;
034 import org.jetbrains.jet.descriptors.serialization.JavaProtoBuf;
035 import org.jetbrains.jet.descriptors.serialization.ProtoBuf;
036 import org.jetbrains.jet.descriptors.serialization.descriptors.DeserializedSimpleFunctionDescriptor;
037 import org.jetbrains.jet.lang.descriptors.*;
038 import org.jetbrains.jet.lang.resolve.BindingContextUtils;
039 import org.jetbrains.jet.lang.resolve.DescriptorUtils;
040 import org.jetbrains.jet.lang.resolve.java.JvmAbi;
041 import org.jetbrains.jet.lang.resolve.java.PackageClassUtils;
042 import org.jetbrains.jet.lang.resolve.kotlin.DeserializedResolverUtils;
043 import org.jetbrains.jet.lang.resolve.kotlin.VirtualFileFinder;
044 import org.jetbrains.jet.lang.resolve.name.FqName;
045 import org.jetbrains.jet.lang.resolve.name.Name;
046
047 import java.io.IOException;
048 import java.io.InputStream;
049 import java.util.Arrays;
050
051 import static org.jetbrains.jet.lang.resolve.DescriptorUtils.getFqName;
052 import static org.jetbrains.jet.lang.resolve.DescriptorUtils.isTrait;
053
054 public class InlineCodegenUtil {
055
056 public final static int API = Opcodes.ASM4;
057
058 public final static String INVOKE = "invoke";
059
060 @Nullable
061 public static MethodNode getMethodNode(
062 InputStream classData,
063 final String methodName,
064 final String methodDescriptor
065 ) throws ClassNotFoundException, IOException {
066 ClassReader cr = new ClassReader(classData);
067 final MethodNode[] methodNode = new MethodNode[1];
068 cr.accept(new ClassVisitor(API) {
069
070 @Override
071 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
072 if (methodName.equals(name) && methodDescriptor.equals(desc)) {
073 return methodNode[0] = new MethodNode(access, name, desc, signature, exceptions);
074 }
075 return null;
076 }
077 }, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
078
079 return methodNode[0];
080 }
081
082
083 @NotNull
084 public static VirtualFile getVirtualFileForCallable(@NotNull DeserializedSimpleFunctionDescriptor deserializedDescriptor, @NotNull GenerationState state) {
085 VirtualFile file;
086 DeclarationDescriptor parentDeclaration = deserializedDescriptor.getContainingDeclaration();
087 if (parentDeclaration instanceof PackageFragmentDescriptor) {
088 ProtoBuf.Callable proto = deserializedDescriptor.getFunctionProto();
089 if (!proto.hasExtension(JavaProtoBuf.implClassName)) {
090 throw new IllegalStateException("Function in namespace should have implClassName property in proto: " + deserializedDescriptor);
091 }
092 Name name = deserializedDescriptor.getNameResolver().getName(proto.getExtension(JavaProtoBuf.implClassName));
093 FqName packagePartFqName =
094 PackageClassUtils.getPackageClassFqName(((PackageFragmentDescriptor) parentDeclaration).getFqName()).parent().child(
095 name);
096 file = findVirtualFileWithHeader(state.getProject(), packagePartFqName);
097 } else {
098 file = findVirtualFileContainingDescriptor(state.getProject(), deserializedDescriptor);
099 }
100
101 if (file == null) {
102 throw new IllegalStateException("Couldn't find declaration file for " + deserializedDescriptor.getName());
103 }
104
105 return file;
106 }
107
108 @Nullable
109 public static VirtualFile findVirtualFileWithHeader(@NotNull Project project, @NotNull FqName containerFqName) {
110 VirtualFileFinder fileFinder = ServiceManager.getService(project, VirtualFileFinder.class);
111 return fileFinder.findVirtualFileWithHeader(containerFqName);
112 }
113
114 @Nullable
115 public static VirtualFile findVirtualFile(@NotNull Project project, @NotNull String internalName) {
116 VirtualFileFinder fileFinder = ServiceManager.getService(project, VirtualFileFinder.class);
117 return fileFinder.findVirtualFile(internalName);
118 }
119
120 //TODO: navigate to inner classes
121 @Nullable
122 public static FqName getContainerFqName(@NotNull DeclarationDescriptor referencedDescriptor) {
123 ClassOrPackageFragmentDescriptor
124 containerDescriptor = DescriptorUtils.getParentOfType(referencedDescriptor, ClassOrPackageFragmentDescriptor.class, false);
125 if (containerDescriptor instanceof PackageFragmentDescriptor) {
126 return PackageClassUtils.getPackageClassFqName(getFqName(containerDescriptor).toSafe());
127 }
128 if (containerDescriptor instanceof ClassDescriptor) {
129 FqName fqName = DeserializedResolverUtils.kotlinFqNameToJavaFqName(getFqName(containerDescriptor));
130 if (isTrait(containerDescriptor)) {
131 return fqName.parent().child(Name.identifier(fqName.shortName() + JvmAbi.TRAIT_IMPL_SUFFIX));
132 }
133 return fqName;
134 }
135 return null;
136 }
137
138 public static String getInlineName(@NotNull CodegenContext codegenContext, @NotNull JetTypeMapper typeMapper) {
139 return getInlineName(codegenContext, codegenContext.getContextDescriptor(), typeMapper);
140 }
141
142 private static String getInlineName(@NotNull CodegenContext codegenContext, @NotNull DeclarationDescriptor currentDescriptor, @NotNull JetTypeMapper typeMapper) {
143 if (currentDescriptor instanceof PackageFragmentDescriptor) {
144 PsiFile file = getContainingFile(codegenContext, typeMapper);
145
146 Type packagePartType;
147 if (file == null) {
148 //in case package fragment clinit
149 assert codegenContext instanceof PackageContext : "Expected package context but " + codegenContext;
150 packagePartType = ((PackageContext) codegenContext).getPackagePartType();
151 } else {
152 packagePartType =
153 PackageCodegen.getPackagePartType(PackageClassUtils.getPackageClassFqName(getFqName(currentDescriptor).toSafe()),
154 file.getVirtualFile());
155 }
156
157 if (packagePartType == null) {
158 DeclarationDescriptor contextDescriptor = codegenContext.getContextDescriptor();
159 //noinspection ConstantConditions
160 throw new RuntimeException("Couldn't find declaration for " + contextDescriptor.getContainingDeclaration().getName() + "." + contextDescriptor.getName() );
161 }
162
163 return packagePartType.getInternalName();
164 }
165 else if (currentDescriptor instanceof ClassifierDescriptor) {
166 Type type = typeMapper.mapType((ClassifierDescriptor) currentDescriptor);
167 return type.getInternalName();
168 } else if (currentDescriptor instanceof FunctionDescriptor) {
169 ClassDescriptor descriptor =
170 typeMapper.getBindingContext().get(CodegenBinding.CLASS_FOR_FUNCTION, (FunctionDescriptor) currentDescriptor);
171 if (descriptor != null) {
172 Type type = typeMapper.mapType(descriptor);
173 return type.getInternalName();
174 }
175 }
176
177 //TODO: add suffix for special case
178 String suffix = currentDescriptor.getName().isSpecial() ? "" : currentDescriptor.getName().asString();
179
180 //noinspection ConstantConditions
181 return getInlineName(codegenContext, currentDescriptor.getContainingDeclaration(), typeMapper) + "$" + suffix;
182 }
183
184 @Nullable
185 private static VirtualFile findVirtualFileContainingDescriptor(
186 @NotNull Project project,
187 @NotNull DeclarationDescriptor referencedDescriptor
188 ) {
189 FqName containerFqName = getContainerFqName(referencedDescriptor);
190 if (containerFqName == null) {
191 return null;
192 }
193 return findVirtualFileWithHeader(project, containerFqName);
194 }
195
196
197 public static boolean isInvokeOnLambda(String owner, String name) {
198 if (!INVOKE.equals(name)) {
199 return false;
200 }
201
202 for (String prefix : Arrays.asList("kotlin/Function", "kotlin/ExtensionFunction")) {
203 if (owner.startsWith(prefix)) {
204 String suffix = owner.substring(prefix.length());
205 if (isInteger(suffix)) {
206 return true;
207 }
208 }
209 }
210 return false;
211 }
212
213 public static boolean isLambdaConstructorCall(@NotNull String internalName, @NotNull String methodName) {
214 return "<init>".equals(methodName) && isLambdaClass(internalName);
215 }
216
217 public static boolean isLambdaClass(String internalName) {
218 String shortName = getLastNamePart(internalName);
219 int index = shortName.lastIndexOf("$");
220
221 if (index < 0) {
222 return false;
223 }
224
225 String suffix = shortName.substring(index + 1);
226 return isInteger(suffix);
227 }
228
229 @NotNull
230 private static String getLastNamePart(@NotNull String internalName) {
231 int index = internalName.lastIndexOf("/");
232 return index < 0 ? internalName : internalName.substring(index + 1);
233 }
234
235 @Nullable
236 public static PsiFile getContainingFile(CodegenContext codegenContext, JetTypeMapper typeMapper) {
237 DeclarationDescriptor contextDescriptor = codegenContext.getContextDescriptor();
238 PsiElement psiElement = BindingContextUtils.descriptorToDeclaration(typeMapper.getBindingContext(), contextDescriptor);
239 if (psiElement != null) {
240 return psiElement.getContainingFile();
241 }
242 return null;
243 }
244
245 @NotNull
246 public static MaxCalcNode wrapWithMaxLocalCalc(@NotNull MethodNode methodNode) {
247 return new MaxCalcNode(methodNode);
248 }
249
250 private static boolean isInteger(@NotNull String string) {
251 if (string.isEmpty()) {
252 return false;
253 }
254
255 for (int i = 0; i < string.length(); i++) {
256 if (!Character.isDigit(string.charAt(i))) {
257 return false;
258 }
259 }
260
261 return true;
262 }
263 }