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