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    }