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;
018    
019    import org.jetbrains.annotations.NotNull;
020    import org.jetbrains.jet.codegen.context.CodegenContext;
021    import org.jetbrains.jet.codegen.state.GenerationState;
022    import org.jetbrains.jet.codegen.state.JetTypeMapper;
023    import org.jetbrains.jet.lang.descriptors.*;
024    import org.jetbrains.jet.lang.descriptors.impl.ClassDescriptorImpl;
025    import org.jetbrains.jet.lang.psi.JetFile;
026    import org.jetbrains.jet.lang.resolve.DescriptorUtils;
027    import org.jetbrains.jet.lang.resolve.java.PackageClassUtils;
028    import org.jetbrains.jet.lang.resolve.java.descriptor.JavaClassDescriptor;
029    import org.jetbrains.jet.lang.resolve.kotlin.PackagePartClassUtils;
030    import org.jetbrains.jet.lang.resolve.name.FqName;
031    import org.jetbrains.jet.lang.resolve.name.Name;
032    import org.jetbrains.jet.lang.types.JetType;
033    import org.jetbrains.org.objectweb.asm.MethodVisitor;
034    import org.jetbrains.org.objectweb.asm.Type;
035    import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter;
036    
037    import java.util.Collections;
038    
039    import static org.jetbrains.jet.codegen.AsmUtil.*;
040    import static org.jetbrains.jet.lang.resolve.java.AsmTypeConstants.OBJECT_TYPE;
041    import static org.jetbrains.jet.lang.resolve.java.JvmAnnotationNames.KotlinSyntheticClass;
042    import static org.jetbrains.jet.lang.resolve.java.diagnostics.DiagnosticsPackage.OtherOrigin;
043    import static org.jetbrains.org.objectweb.asm.Opcodes.*;
044    
045    public class SamWrapperCodegen {
046        private static final String FUNCTION_FIELD_NAME = "function";
047    
048        private final GenerationState state;
049        private final JetTypeMapper typeMapper;
050        private final SamType samType;
051        private final MemberCodegen<?> parentCodegen;
052    
053        public SamWrapperCodegen(@NotNull GenerationState state, @NotNull SamType samType, @NotNull MemberCodegen<?> parentCodegen) {
054            this.state = state;
055            this.typeMapper = state.getTypeMapper();
056            this.samType = samType;
057            this.parentCodegen = parentCodegen;
058        }
059    
060        public Type genWrapper(@NotNull JetFile file) {
061            // Name for generated class, in form of whatever$1
062            FqName fqName = getWrapperName(file);
063            Type asmType = asmTypeByFqNameWithoutInnerClasses(fqName);
064    
065            // e.g. (T, T) -> Int
066            JetType functionType = samType.getKotlinFunctionType();
067    
068            ClassDescriptor classDescriptor = new ClassDescriptorImpl(
069                    samType.getJavaClassDescriptor().getContainingDeclaration(),
070                    fqName.shortName(),
071                    Modality.FINAL,
072                    Collections.singleton(samType.getType()),
073                    SourceElement.NO_SOURCE
074            );
075            // e.g. compare(T, T)
076            SimpleFunctionDescriptor erasedInterfaceFunction = samType.getAbstractMethod().getOriginal().copy(
077                    classDescriptor,
078                    Modality.FINAL,
079                    Visibilities.PUBLIC,
080                    CallableMemberDescriptor.Kind.SYNTHESIZED,
081                    /*copyOverrides=*/ false
082            );
083    
084            ClassBuilder cv = state.getFactory().newVisitor(OtherOrigin(erasedInterfaceFunction), asmType, file);
085            cv.defineClass(file,
086                           V1_6,
087                           ACC_FINAL,
088                           asmType.getInternalName(),
089                           null,
090                           OBJECT_TYPE.getInternalName(),
091                           new String[]{ typeMapper.mapType(samType.getType()).getInternalName() }
092            );
093            cv.visitSource(file.getName(), null);
094    
095            writeKotlinSyntheticClassAnnotation(cv, KotlinSyntheticClass.Kind.SAM_WRAPPER);
096    
097            // e.g. ASM type for Function2
098            Type functionAsmType = typeMapper.mapType(functionType);
099    
100            cv.newField(OtherOrigin(erasedInterfaceFunction),
101                        ACC_SYNTHETIC | ACC_PRIVATE | ACC_FINAL,
102                        FUNCTION_FIELD_NAME,
103                        functionAsmType.getDescriptor(),
104                        null,
105                        null);
106    
107            generateConstructor(asmType, functionAsmType, cv);
108            generateMethod(asmType, functionAsmType, cv, erasedInterfaceFunction, functionType);
109    
110            cv.done();
111    
112            return asmType;
113        }
114    
115        private void generateConstructor(Type ownerType, Type functionType, ClassBuilder cv) {
116            MethodVisitor mv = cv.newMethod(OtherOrigin(samType.getJavaClassDescriptor()),
117                                            NO_FLAG_PACKAGE_PRIVATE, "<init>", Type.getMethodDescriptor(Type.VOID_TYPE, functionType), null, null);
118    
119            if (state.getClassBuilderMode() == ClassBuilderMode.FULL) {
120                mv.visitCode();
121                InstructionAdapter iv = new InstructionAdapter(mv);
122    
123                // super constructor
124                iv.load(0, OBJECT_TYPE);
125                iv.invokespecial(OBJECT_TYPE.getInternalName(), "<init>", "()V", false);
126    
127                // save parameter to field
128                iv.load(0, OBJECT_TYPE);
129                iv.load(1, functionType);
130                iv.putfield(ownerType.getInternalName(), FUNCTION_FIELD_NAME, functionType.getDescriptor());
131    
132                iv.visitInsn(RETURN);
133                FunctionCodegen.endVisit(iv, "constructor of SAM wrapper", null);
134            }
135        }
136    
137        private void generateMethod(
138                Type ownerType,
139                Type functionType,
140                ClassBuilder cv,
141                SimpleFunctionDescriptor erasedInterfaceFunction,
142                JetType functionJetType
143        ) {
144            // using static context to avoid creating ClassDescriptor and everything else
145            FunctionCodegen codegen = new FunctionCodegen(CodegenContext.STATIC.intoClass(
146                    (ClassDescriptor) erasedInterfaceFunction.getContainingDeclaration(), OwnerKind.IMPLEMENTATION, state), cv, state, parentCodegen);
147    
148            FunctionDescriptor invokeFunction = functionJetType.getMemberScope()
149                    .getFunctions(Name.identifier("invoke")).iterator().next().getOriginal();
150            StackValue functionField = StackValue.field(functionType, ownerType, FUNCTION_FIELD_NAME, false);
151            codegen.genDelegate(erasedInterfaceFunction, invokeFunction, functionField);
152        }
153    
154        @NotNull
155        private FqName getWrapperName(@NotNull JetFile containingFile) {
156            FqName packageClassFqName = PackageClassUtils.getPackageClassFqName(containingFile.getPackageFqName());
157            JavaClassDescriptor descriptor = samType.getJavaClassDescriptor();
158            String shortName = packageClassFqName.shortName().asString() + "$sam$" + descriptor.getName().asString() + "$" +
159                       Integer.toHexString(
160                               PackagePartClassUtils.getPathHashCode(containingFile.getVirtualFile()) * 31 +
161                               DescriptorUtils.getFqNameSafe(descriptor).hashCode()
162                       );
163            return packageClassFqName.parent().child(Name.identifier(shortName));
164        }
165    }