001    /*
002     * Copyright 2010-2015 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.kotlin.codegen;
018    
019    import com.intellij.openapi.util.Pair;
020    import org.jetbrains.annotations.NotNull;
021    import org.jetbrains.annotations.Nullable;
022    import org.jetbrains.kotlin.codegen.state.JetTypeMapper;
023    import org.jetbrains.kotlin.descriptors.*;
024    import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor;
025    import org.jetbrains.kotlin.load.kotlin.SignatureDeserializer;
026    import org.jetbrains.kotlin.name.FqName;
027    import org.jetbrains.kotlin.name.Name;
028    import org.jetbrains.kotlin.serialization.AnnotationSerializer;
029    import org.jetbrains.kotlin.serialization.ProtoBuf;
030    import org.jetbrains.kotlin.serialization.SerializerExtension;
031    import org.jetbrains.kotlin.serialization.StringTable;
032    import org.jetbrains.kotlin.serialization.deserialization.NameResolver;
033    import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedPropertyDescriptor;
034    import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedSimpleFunctionDescriptor;
035    import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf;
036    import org.jetbrains.kotlin.types.JetType;
037    import org.jetbrains.org.objectweb.asm.Type;
038    import org.jetbrains.org.objectweb.asm.commons.Method;
039    
040    import java.util.Arrays;
041    
042    import static org.jetbrains.kotlin.codegen.AsmUtil.shortNameByAsmType;
043    import static org.jetbrains.kotlin.codegen.JvmSerializationBindings.*;
044    
045    public class JvmSerializerExtension extends SerializerExtension {
046        private final JvmSerializationBindings bindings;
047        private final JetTypeMapper typeMapper;
048    
049        public JvmSerializerExtension(@NotNull JvmSerializationBindings bindings, @NotNull JetTypeMapper typeMapper) {
050            this.bindings = bindings;
051            this.typeMapper = typeMapper;
052        }
053    
054        @Override
055        public void serializeCallable(
056                @NotNull CallableMemberDescriptor callable,
057                @NotNull ProtoBuf.Callable.Builder proto,
058                @NotNull StringTable stringTable
059        ) {
060            saveSignature(callable, proto, stringTable);
061            saveImplClassName(callable, proto, stringTable);
062        }
063    
064        @Override
065        public void serializeValueParameter(
066                @NotNull ValueParameterDescriptor descriptor,
067                @NotNull ProtoBuf.Callable.ValueParameter.Builder proto,
068                @NotNull StringTable stringTable
069        ) {
070            Integer index = bindings.get(INDEX_FOR_VALUE_PARAMETER, descriptor);
071            if (index != null) {
072                proto.setExtension(JvmProtoBuf.index, index);
073            }
074        }
075    
076        @Override
077        public void serializeType(@NotNull JetType type, @NotNull ProtoBuf.Type.Builder proto, @NotNull StringTable stringTable) {
078            // TODO: don't store type annotations in our binary metadata on Java 8, use *TypeAnnotations attributes instead
079            for (AnnotationDescriptor annotation : type.getAnnotations()) {
080                proto.addExtension(JvmProtoBuf.typeAnnotation, AnnotationSerializer.INSTANCE$.serializeAnnotation(annotation, stringTable));
081            }
082        }
083    
084        @Override
085        @NotNull
086        public String getLocalClassName(@NotNull ClassDescriptor descriptor) {
087            return shortNameByAsmType(typeMapper.mapClass(descriptor));
088        }
089    
090        private void saveSignature(
091                @NotNull CallableMemberDescriptor callable,
092                @NotNull ProtoBuf.Callable.Builder proto,
093                @NotNull StringTable stringTable
094        ) {
095            SignatureSerializer signatureSerializer = new SignatureSerializer(stringTable);
096            if (callable instanceof FunctionDescriptor) {
097                JvmProtoBuf.JvmMethodSignature signature;
098                if (callable instanceof DeserializedSimpleFunctionDescriptor) {
099                    DeserializedSimpleFunctionDescriptor deserialized = (DeserializedSimpleFunctionDescriptor) callable;
100                    signature = signatureSerializer.copyMethodSignature(
101                            deserialized.getProto().getExtension(JvmProtoBuf.methodSignature), deserialized.getNameResolver());
102                }
103                else {
104                    Method method = bindings.get(METHOD_FOR_FUNCTION, (FunctionDescriptor) callable);
105                    signature = method != null ? signatureSerializer.methodSignature(method) : null;
106                }
107                if (signature != null) {
108                    proto.setExtension(JvmProtoBuf.methodSignature, signature);
109                }
110            }
111            else if (callable instanceof PropertyDescriptor) {
112                PropertyDescriptor property = (PropertyDescriptor) callable;
113    
114                PropertyGetterDescriptor getter = property.getGetter();
115                PropertySetterDescriptor setter = property.getSetter();
116                Method getterMethod = getter == null ? null : bindings.get(METHOD_FOR_FUNCTION, getter);
117                Method setterMethod = setter == null ? null : bindings.get(METHOD_FOR_FUNCTION, setter);
118    
119                Pair<Type, String> field = bindings.get(FIELD_FOR_PROPERTY, property);
120                Type fieldType;
121                String fieldName;
122                boolean isStaticInOuter;
123                Method syntheticMethod;
124                if (field != null) {
125                    fieldType = field.first;
126                    fieldName = field.second;
127                    isStaticInOuter = bindings.get(STATIC_FIELD_IN_OUTER_CLASS, property);
128                    syntheticMethod = null;
129                }
130                else {
131                    fieldType = null;
132                    fieldName = null;
133                    isStaticInOuter = false;
134                    syntheticMethod = bindings.get(SYNTHETIC_METHOD_FOR_PROPERTY, property);
135                }
136    
137                JvmProtoBuf.JvmPropertySignature signature;
138                if (callable instanceof DeserializedPropertyDescriptor) {
139                    DeserializedPropertyDescriptor deserializedCallable = (DeserializedPropertyDescriptor) callable;
140                    signature = signatureSerializer.copyPropertySignature(
141                            deserializedCallable.getProto().getExtension(JvmProtoBuf.propertySignature),
142                            deserializedCallable.getNameResolver()
143                    );
144                }
145                else {
146                    signature = signatureSerializer
147                            .propertySignature(fieldType, fieldName, isStaticInOuter, syntheticMethod, getterMethod, setterMethod);
148                }
149                proto.setExtension(JvmProtoBuf.propertySignature, signature);
150            }
151        }
152    
153        private void saveImplClassName(
154                @NotNull CallableMemberDescriptor callable,
155                @NotNull ProtoBuf.Callable.Builder proto,
156                @NotNull StringTable stringTable
157        ) {
158            String name = bindings.get(IMPL_CLASS_NAME_FOR_CALLABLE, callable);
159            if (name != null) {
160                proto.setExtension(JvmProtoBuf.implClassName, stringTable.getSimpleNameIndex(Name.identifier(name)));
161            }
162        }
163    
164        private static class SignatureSerializer {
165            private final StringTable stringTable;
166    
167            public SignatureSerializer(@NotNull StringTable stringTable) {
168                this.stringTable = stringTable;
169            }
170    
171            @NotNull
172            public JvmProtoBuf.JvmMethodSignature copyMethodSignature(
173                    @NotNull JvmProtoBuf.JvmMethodSignature signature,
174                    @NotNull NameResolver nameResolver
175            ) {
176                String method = new SignatureDeserializer(nameResolver).methodSignatureString(signature);
177                return methodSignature(getAsmMethod(method));
178            }
179    
180            @NotNull
181            public JvmProtoBuf.JvmMethodSignature methodSignature(@NotNull Method method) {
182                JvmProtoBuf.JvmMethodSignature.Builder signature = JvmProtoBuf.JvmMethodSignature.newBuilder();
183    
184                signature.setName(stringTable.getStringIndex(method.getName()));
185    
186                signature.setReturnType(type(method.getReturnType()));
187    
188                for (Type type : method.getArgumentTypes()) {
189                    signature.addParameterType(type(type));
190                }
191    
192                return signature.build();
193            }
194    
195            @NotNull
196            public JvmProtoBuf.JvmPropertySignature copyPropertySignature(
197                    @NotNull JvmProtoBuf.JvmPropertySignature signature,
198                    @NotNull NameResolver nameResolver
199            ) {
200                Type fieldType;
201                String fieldName;
202                boolean isStaticInOuter;
203                SignatureDeserializer signatureDeserializer = new SignatureDeserializer(nameResolver);
204                if (signature.hasField()) {
205                    JvmProtoBuf.JvmFieldSignature field = signature.getField();
206                    fieldType = Type.getType(signatureDeserializer.typeDescriptor(field.getType()));
207                    fieldName = nameResolver.getName(field.getName()).asString();
208                    isStaticInOuter = field.getIsStaticInOuter();
209                }
210                else {
211                    fieldType = null;
212                    fieldName = null;
213                    isStaticInOuter = false;
214                }
215    
216                Method syntheticMethod = signature.hasSyntheticMethod()
217                        ? getAsmMethod(signatureDeserializer.methodSignatureString(signature.getSyntheticMethod()))
218                        : null;
219    
220                Method getter = signature.hasGetter() ? getAsmMethod(signatureDeserializer.methodSignatureString(signature.getGetter())) : null;
221                Method setter = signature.hasSetter() ? getAsmMethod(signatureDeserializer.methodSignatureString(signature.getSetter())) : null;
222    
223                return propertySignature(fieldType, fieldName, isStaticInOuter, syntheticMethod, getter, setter);
224            }
225    
226            @NotNull
227            public JvmProtoBuf.JvmPropertySignature propertySignature(
228                    @Nullable Type fieldType,
229                    @Nullable String fieldName,
230                    boolean isStaticInOuter,
231                    @Nullable Method syntheticMethod,
232                    @Nullable Method getter,
233                    @Nullable Method setter
234            ) {
235                JvmProtoBuf.JvmPropertySignature.Builder signature = JvmProtoBuf.JvmPropertySignature.newBuilder();
236    
237                if (fieldType != null) {
238                    assert fieldName != null : "Field name shouldn't be null when there's a field type: " + fieldType;
239                    signature.setField(fieldSignature(fieldType, fieldName, isStaticInOuter));
240                }
241    
242                if (syntheticMethod != null) {
243                    signature.setSyntheticMethod(methodSignature(syntheticMethod));
244                }
245    
246                if (getter != null) {
247                    signature.setGetter(methodSignature(getter));
248                }
249                if (setter != null) {
250                    signature.setSetter(methodSignature(setter));
251                }
252    
253                return signature.build();
254            }
255    
256            @NotNull
257            public JvmProtoBuf.JvmFieldSignature fieldSignature(@NotNull Type type, @NotNull String name, boolean isStaticInOuter) {
258                JvmProtoBuf.JvmFieldSignature.Builder signature = JvmProtoBuf.JvmFieldSignature.newBuilder();
259                signature.setName(stringTable.getStringIndex(name));
260                signature.setType(type(type));
261                if (isStaticInOuter) {
262                    signature.setIsStaticInOuter(true);
263                }
264                return signature.build();
265            }
266    
267            @NotNull
268            public JvmProtoBuf.JvmType type(@NotNull Type givenType) {
269                JvmProtoBuf.JvmType.Builder builder = JvmProtoBuf.JvmType.newBuilder();
270    
271                Type type = givenType;
272                if (type.getSort() == Type.ARRAY) {
273                    builder.setArrayDimension(type.getDimensions());
274                    type = type.getElementType();
275                }
276    
277                if (type.getSort() == Type.OBJECT) {
278                    FqName fqName = internalNameToFqName(type.getInternalName());
279                    builder.setClassFqName(stringTable.getFqNameIndex(fqName));
280                }
281                else {
282                    builder.setPrimitiveType(JvmProtoBuf.JvmType.PrimitiveType.valueOf(type.getSort()));
283                }
284    
285                return builder.build();
286            }
287    
288            @NotNull
289            private static FqName internalNameToFqName(@NotNull String internalName) {
290                return FqName.fromSegments(Arrays.asList(internalName.split("/")));
291            }
292        }
293    
294        @NotNull
295        private static Method getAsmMethod(@NotNull String nameAndDesc) {
296            int indexOf = nameAndDesc.indexOf('(');
297            return new Method(nameAndDesc.substring(0, indexOf), nameAndDesc.substring(indexOf));
298        }
299    }