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.serialization;
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.GenerationState;
023    import org.jetbrains.kotlin.descriptors.*;
024    import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor;
025    import org.jetbrains.kotlin.load.java.JvmAbi;
026    import org.jetbrains.kotlin.load.java.lazy.types.RawTypeCapabilities;
027    import org.jetbrains.kotlin.name.ClassId;
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.jvm.ClassMapperLite;
033    import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf;
034    import org.jetbrains.kotlin.types.KotlinType;
035    import org.jetbrains.org.objectweb.asm.Type;
036    import org.jetbrains.org.objectweb.asm.commons.Method;
037    
038    import static org.jetbrains.kotlin.codegen.serialization.JvmSerializationBindings.*;
039    
040    public class JvmSerializerExtension extends SerializerExtension {
041        private final JvmSerializationBindings bindings;
042        private final StringTable stringTable;
043        private final AnnotationSerializer annotationSerializer;
044        private final boolean useTypeTable;
045        private final String moduleName;
046    
047        public JvmSerializerExtension(@NotNull JvmSerializationBindings bindings, @NotNull GenerationState state) {
048            this.bindings = bindings;
049            this.stringTable = new JvmStringTable(state.getTypeMapper());
050            this.annotationSerializer = new AnnotationSerializer(stringTable);
051            this.useTypeTable = state.getUseTypeTableInSerializer();
052            this.moduleName = state.getModuleName();
053        }
054    
055        @NotNull
056        @Override
057        public StringTable getStringTable() {
058            return stringTable;
059        }
060    
061        @Override
062        public boolean shouldUseTypeTable() {
063            return useTypeTable;
064        }
065    
066        @Override
067        public void serializeClass(@NotNull ClassDescriptor descriptor, @NotNull ProtoBuf.Class.Builder proto) {
068            if (!moduleName.equals(JvmAbi.DEFAULT_MODULE_NAME)) {
069                proto.setExtension(JvmProtoBuf.classModuleName, stringTable.getStringIndex(moduleName));
070            }
071        }
072    
073        @Override
074        public void serializePackage(@NotNull ProtoBuf.Package.Builder proto) {
075            if (!moduleName.equals(JvmAbi.DEFAULT_MODULE_NAME)) {
076                proto.setExtension(JvmProtoBuf.packageModuleName, stringTable.getStringIndex(moduleName));
077            }
078        }
079    
080        @Override
081        public void serializeType(@NotNull KotlinType type, @NotNull ProtoBuf.Type.Builder proto) {
082            // TODO: don't store type annotations in our binary metadata on Java 8, use *TypeAnnotations attributes instead
083            for (AnnotationDescriptor annotation : type.getAnnotations()) {
084                proto.addExtension(JvmProtoBuf.typeAnnotation, annotationSerializer.serializeAnnotation(annotation));
085            }
086    
087            if (type.getCapabilities() instanceof RawTypeCapabilities) {
088                proto.setExtension(JvmProtoBuf.isRaw, true);
089            }
090        }
091    
092        @Override
093        public void serializeTypeParameter(
094                @NotNull TypeParameterDescriptor typeParameter, @NotNull ProtoBuf.TypeParameter.Builder proto
095        ) {
096            for (AnnotationDescriptor annotation : typeParameter.getAnnotations()) {
097                proto.addExtension(JvmProtoBuf.typeParameterAnnotation, annotationSerializer.serializeAnnotation(annotation));
098            }
099        }
100    
101        @Override
102        public void serializeConstructor(@NotNull ConstructorDescriptor descriptor, @NotNull ProtoBuf.Constructor.Builder proto) {
103            Method method = bindings.get(METHOD_FOR_FUNCTION, descriptor);
104            if (method != null) {
105                JvmProtoBuf.JvmMethodSignature signature = new SignatureSerializer().methodSignature(descriptor, method);
106                if (signature != null) {
107                    proto.setExtension(JvmProtoBuf.constructorSignature, signature);
108                }
109            }
110        }
111    
112        @Override
113        public void serializeFunction(@NotNull FunctionDescriptor descriptor, @NotNull ProtoBuf.Function.Builder proto) {
114            Method method = bindings.get(METHOD_FOR_FUNCTION, descriptor);
115            if (method != null) {
116                JvmProtoBuf.JvmMethodSignature signature = new SignatureSerializer().methodSignature(descriptor, method);
117                if (signature != null) {
118                    proto.setExtension(JvmProtoBuf.methodSignature, signature);
119                }
120            }
121        }
122    
123        @Override
124        public void serializeProperty(@NotNull PropertyDescriptor descriptor, @NotNull ProtoBuf.Property.Builder proto) {
125            SignatureSerializer signatureSerializer = new SignatureSerializer();
126    
127            PropertyGetterDescriptor getter = descriptor.getGetter();
128            PropertySetterDescriptor setter = descriptor.getSetter();
129            Method getterMethod = getter == null ? null : bindings.get(METHOD_FOR_FUNCTION, getter);
130            Method setterMethod = setter == null ? null : bindings.get(METHOD_FOR_FUNCTION, setter);
131    
132            Pair<Type, String> field = bindings.get(FIELD_FOR_PROPERTY, descriptor);
133            Method syntheticMethod = bindings.get(SYNTHETIC_METHOD_FOR_PROPERTY, descriptor);
134    
135            JvmProtoBuf.JvmPropertySignature signature = signatureSerializer.propertySignature(
136                    descriptor,
137                    field != null ? field.second : null,
138                    field != null ? field.first.getDescriptor() : null,
139                    syntheticMethod != null ? signatureSerializer.methodSignature(null, syntheticMethod) : null,
140                    getterMethod != null ? signatureSerializer.methodSignature(null, getterMethod) : null,
141                    setterMethod != null ? signatureSerializer.methodSignature(null, setterMethod) : null
142            );
143    
144            proto.setExtension(JvmProtoBuf.propertySignature, signature);
145        }
146    
147        private class SignatureSerializer {
148            @Nullable
149            public JvmProtoBuf.JvmMethodSignature methodSignature(@Nullable FunctionDescriptor descriptor, @NotNull Method method) {
150                JvmProtoBuf.JvmMethodSignature.Builder builder = JvmProtoBuf.JvmMethodSignature.newBuilder();
151                if (descriptor == null || !descriptor.getName().asString().equals(method.getName())) {
152                    builder.setName(stringTable.getStringIndex(method.getName()));
153                }
154                if (descriptor == null || requiresSignature(descriptor, method.getDescriptor())) {
155                    builder.setDesc(stringTable.getStringIndex(method.getDescriptor()));
156                }
157                return builder.hasName() || builder.hasDesc() ? builder.build() : null;
158            }
159    
160            // We don't write those signatures which can be trivially reconstructed from already serialized data
161            // TODO: make JvmStringTable implement NameResolver and use JvmProtoBufUtil#getJvmMethodSignature instead
162            private boolean requiresSignature(@NotNull FunctionDescriptor descriptor, @NotNull String desc) {
163                StringBuilder sb = new StringBuilder();
164                sb.append("(");
165                ReceiverParameterDescriptor receiverParameter = descriptor.getExtensionReceiverParameter();
166                if (receiverParameter != null) {
167                    String receiverDesc = mapTypeDefault(receiverParameter.getValue().getType());
168                    if (receiverDesc == null) return true;
169                    sb.append(receiverDesc);
170                }
171    
172                for (ValueParameterDescriptor valueParameter : descriptor.getValueParameters()) {
173                    String paramDesc = mapTypeDefault(valueParameter.getType());
174                    if (paramDesc == null) return true;
175                    sb.append(paramDesc);
176                }
177    
178                sb.append(")");
179    
180                KotlinType returnType = descriptor.getReturnType();
181                String returnTypeDesc = returnType == null ? "V" : mapTypeDefault(returnType);
182                if (returnTypeDesc == null) return true;
183                sb.append(returnTypeDesc);
184    
185                return !sb.toString().equals(desc);
186            }
187    
188            private boolean requiresSignature(@NotNull PropertyDescriptor descriptor, @NotNull String desc) {
189                return !desc.equals(mapTypeDefault(descriptor.getType()));
190            }
191    
192            @Nullable
193            private String mapTypeDefault(@NotNull KotlinType type) {
194                ClassifierDescriptor classifier = type.getConstructor().getDeclarationDescriptor();
195                if (!(classifier instanceof ClassDescriptor)) return null;
196                ClassId classId = classId((ClassDescriptor) classifier);
197                return classId == null ? null : ClassMapperLite.mapClass(classId);
198            }
199    
200            @Nullable
201            private ClassId classId(@NotNull ClassDescriptor descriptor) {
202                DeclarationDescriptor container = descriptor.getContainingDeclaration();
203                if (container instanceof PackageFragmentDescriptor) {
204                    return ClassId.topLevel(((PackageFragmentDescriptor) container).getFqName().child(descriptor.getName()));
205                }
206                else if (container instanceof ClassDescriptor) {
207                    ClassId outerClassId = classId((ClassDescriptor) container);
208                    return outerClassId == null ? null : outerClassId.createNestedClassId(descriptor.getName());
209                }
210                else {
211                    return null;
212                }
213            }
214    
215            @NotNull
216            public JvmProtoBuf.JvmPropertySignature propertySignature(
217                    @NotNull PropertyDescriptor descriptor,
218                    @Nullable String fieldName,
219                    @Nullable String fieldDesc,
220                    @Nullable JvmProtoBuf.JvmMethodSignature syntheticMethod,
221                    @Nullable JvmProtoBuf.JvmMethodSignature getter,
222                    @Nullable JvmProtoBuf.JvmMethodSignature setter
223            ) {
224                JvmProtoBuf.JvmPropertySignature.Builder signature = JvmProtoBuf.JvmPropertySignature.newBuilder();
225    
226                if (fieldDesc != null) {
227                    assert fieldName != null : "Field name shouldn't be null when there's a field type: " + fieldDesc;
228                    signature.setField(fieldSignature(descriptor, fieldName, fieldDesc));
229                }
230    
231                if (syntheticMethod != null) {
232                    signature.setSyntheticMethod(syntheticMethod);
233                }
234    
235                if (getter != null) {
236                    signature.setGetter(getter);
237                }
238                if (setter != null) {
239                    signature.setSetter(setter);
240                }
241    
242                return signature.build();
243            }
244    
245            @NotNull
246            public JvmProtoBuf.JvmFieldSignature fieldSignature(
247                    @NotNull PropertyDescriptor descriptor,
248                    @NotNull String name,
249                    @NotNull String desc
250            ) {
251                JvmProtoBuf.JvmFieldSignature.Builder builder = JvmProtoBuf.JvmFieldSignature.newBuilder();
252                if (!descriptor.getName().asString().equals(name)) {
253                    builder.setName(stringTable.getStringIndex(name));
254                }
255                if (requiresSignature(descriptor, desc)) {
256                    builder.setDesc(stringTable.getStringIndex(desc));
257                }
258                return builder.build();
259            }
260        }
261    }