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.signature;
018    
019    import com.intellij.util.containers.Stack;
020    import org.jetbrains.annotations.NotNull;
021    import org.jetbrains.annotations.Nullable;
022    import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterKind;
023    import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterSignature;
024    import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature;
025    import org.jetbrains.org.objectweb.asm.Type;
026    import org.jetbrains.org.objectweb.asm.commons.Method;
027    import org.jetbrains.org.objectweb.asm.signature.SignatureVisitor;
028    import org.jetbrains.org.objectweb.asm.signature.SignatureWriter;
029    import org.jetbrains.org.objectweb.asm.util.CheckSignatureAdapter;
030    import org.jetbrains.kotlin.name.Name;
031    import org.jetbrains.kotlin.types.Variance;
032    
033    import java.util.ArrayList;
034    import java.util.List;
035    
036    public class BothSignatureWriter {
037        public enum Mode {
038            METHOD(CheckSignatureAdapter.METHOD_SIGNATURE),
039            CLASS(CheckSignatureAdapter.CLASS_SIGNATURE),
040            TYPE(CheckSignatureAdapter.TYPE_SIGNATURE);
041    
042            private final int asmType;
043    
044            Mode(int asmType) {
045                this.asmType = asmType;
046            }
047        }
048    
049        private final SignatureWriter signatureWriter = new SignatureWriter();
050        private final SignatureVisitor signatureVisitor;
051    
052        private final List<JvmMethodParameterSignature> kotlinParameterTypes = new ArrayList<JvmMethodParameterSignature>();
053    
054        private int jvmCurrentTypeArrayLevel;
055        private Type jvmCurrentType;
056        private Type jvmReturnType;
057    
058        private JvmMethodParameterKind currentParameterKind;
059    
060        private boolean generic = false;
061    
062        public BothSignatureWriter(@NotNull Mode mode) {
063            this.signatureVisitor = new CheckSignatureAdapter(mode.asmType, signatureWriter);
064        }
065    
066        private final Stack<SignatureVisitor> visitors = new Stack<SignatureVisitor>();
067    
068        private void push(SignatureVisitor visitor) {
069            visitors.push(visitor);
070        }
071    
072        private void pop() {
073            visitors.pop();
074        }
075    
076        private SignatureVisitor signatureVisitor() {
077            return !visitors.isEmpty() ? visitors.peek() : signatureVisitor;
078        }
079    
080        /**
081         * Shortcut
082         */
083        public void writeAsmType(Type asmType) {
084            switch (asmType.getSort()) {
085                case Type.OBJECT:
086                    writeClassBegin(asmType);
087                    writeClassEnd();
088                    return;
089                case Type.ARRAY:
090                    writeArrayType();
091                    writeAsmType(asmType.getElementType());
092                    writeArrayEnd();
093                    return;
094                default:
095                    signatureVisitor().visitBaseType(asmType.getDescriptor().charAt(0));
096                    writeAsmType0(asmType);
097            }
098        }
099    
100        private String makeArrayPrefix() {
101            StringBuilder sb = new StringBuilder();
102            for (int i = 0; i < jvmCurrentTypeArrayLevel; ++i) {
103                sb.append('[');
104            }
105            return sb.toString();
106        }
107    
108        private void writeAsmType0(Type type) {
109            if (jvmCurrentType == null) {
110                jvmCurrentType = Type.getType(makeArrayPrefix() + type.getDescriptor());
111            }
112        }
113    
114        public void writeClassBegin(Type asmType) {
115            signatureVisitor().visitClassType(asmType.getInternalName());
116            writeAsmType0(asmType);
117        }
118    
119        public void writeClassEnd() {
120            signatureVisitor().visitEnd();
121        }
122    
123        public void writeArrayType() {
124            push(signatureVisitor().visitArrayType());
125            if (jvmCurrentType == null) {
126                ++jvmCurrentTypeArrayLevel;
127            }
128        }
129    
130        public void writeArrayEnd() {
131            pop();
132        }
133    
134        private static char toJvmVariance(@NotNull Variance variance) {
135            switch (variance) {
136                case INVARIANT: return '=';
137                case IN_VARIANCE: return '-';
138                case OUT_VARIANCE: return '+';
139                default: throw new IllegalStateException("Unknown variance: " + variance);
140            }
141        }
142    
143        public void writeTypeArgument(@NotNull Variance projectionKind) {
144            push(signatureVisitor().visitTypeArgument(toJvmVariance(projectionKind)));
145    
146            generic = true;
147        }
148    
149        public void writeUnboundedWildcard() {
150            signatureVisitor().visitTypeArgument();
151    
152            generic = true;
153        }
154    
155        public void writeTypeArgumentEnd() {
156            pop();
157        }
158    
159        public void writeTypeVariable(Name name, Type asmType) {
160            signatureVisitor().visitTypeVariable(name.asString());
161            generic = true;
162            writeAsmType0(asmType);
163        }
164    
165        public void writeFormalTypeParameter(String name) {
166            signatureVisitor().visitFormalTypeParameter(name);
167    
168            generic = true;
169        }
170    
171        public void writeClassBound() {
172            push(signatureVisitor().visitClassBound());
173        }
174    
175        public void writeClassBoundEnd() {
176            pop();
177        }
178    
179        public void writeInterfaceBound() {
180            push(signatureVisitor().visitInterfaceBound());
181        }
182    
183        public void writeInterfaceBoundEnd() {
184            pop();
185        }
186    
187        public void writeParametersStart() {
188            // hacks
189            jvmCurrentType = null;
190            jvmCurrentTypeArrayLevel = 0;
191        }
192    
193        public void writeParameterType(JvmMethodParameterKind parameterKind) {
194            // This magic mimics the behavior of javac that enum constructor have these synthetic parameters in erased signature, but doesn't
195            // have them in generic signature. IDEA, javac and their friends rely on this behavior.
196            if (parameterKind.isSkippedInGenericSignature()) {
197                generic = true;
198    
199                // pushing dummy visitor, because we don't want these parameters to appear in generic JVM signature
200                push(new SignatureWriter());
201            }
202            else {
203                push(signatureVisitor().visitParameterType());
204            }
205    
206            this.currentParameterKind = parameterKind;
207        }
208    
209        public void writeParameterTypeEnd() {
210            pop();
211    
212            kotlinParameterTypes.add(new JvmMethodParameterSignature(jvmCurrentType, currentParameterKind));
213    
214            currentParameterKind = null;
215            jvmCurrentType = null;
216            jvmCurrentTypeArrayLevel = 0;
217        }
218    
219        public void writeReturnType() {
220            push(signatureVisitor().visitReturnType());
221        }
222    
223        public void writeReturnTypeEnd() {
224            pop();
225    
226            jvmReturnType = jvmCurrentType;
227            jvmCurrentType = null;
228            jvmCurrentTypeArrayLevel = 0;
229        }
230    
231        public void writeSuperclass() {
232            push(signatureVisitor().visitSuperclass());
233        }
234    
235        public void writeSuperclassEnd() {
236            pop();
237        }
238    
239        public void writeInterface() {
240            push(signatureVisitor().visitInterface());
241        }
242    
243        public void writeInterfaceEnd() {
244            pop();
245        }
246    
247    
248        @Nullable
249        public String makeJavaGenericSignature() {
250            return generic ? signatureWriter.toString() : null;
251        }
252    
253        @NotNull
254        public JvmMethodSignature makeJvmMethodSignature(@NotNull String name) {
255            List<Type> types = new ArrayList<Type>(kotlinParameterTypes.size());
256            for (JvmMethodParameterSignature parameter : kotlinParameterTypes) {
257                types.add(parameter.getAsmType());
258            }
259            Method asmMethod = new Method(name, jvmReturnType, types.toArray(new Type[types.size()]));
260            return new JvmMethodSignature(asmMethod, makeJavaGenericSignature(), kotlinParameterTypes);
261        }
262    
263    
264        @Override
265        public String toString() {
266            return signatureWriter.toString();
267        }
268    }
269