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