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