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