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.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.asm4.Type;
023    import org.jetbrains.asm4.commons.Method;
024    import org.jetbrains.asm4.signature.SignatureVisitor;
025    import org.jetbrains.asm4.signature.SignatureWriter;
026    import org.jetbrains.asm4.util.CheckSignatureAdapter;
027    import org.jetbrains.jet.lang.resolve.java.AsmTypeConstants;
028    import org.jetbrains.jet.lang.resolve.name.Name;
029    import org.jetbrains.jet.lang.types.Variance;
030    
031    import java.util.ArrayList;
032    import java.util.List;
033    
034    public class BothSignatureWriter {
035        public enum Mode {
036            METHOD(CheckSignatureAdapter.METHOD_SIGNATURE),
037            CLASS(CheckSignatureAdapter.CLASS_SIGNATURE),
038            TYPE(CheckSignatureAdapter.TYPE_SIGNATURE);
039    
040            private final int asmType;
041    
042            Mode(int asmType) {
043                this.asmType = asmType;
044            }
045        }
046    
047        private final SignatureWriter signatureWriter = new SignatureWriter();
048        private final SignatureVisitor signatureVisitor;
049    
050        private final List<JvmMethodParameterSignature> kotlinParameterTypes = new ArrayList<JvmMethodParameterSignature>();
051    
052        private int jvmCurrentTypeArrayLevel;
053        private Type jvmCurrentType;
054        private Type jvmReturnType;
055    
056        private JvmMethodParameterKind currentParameterKind;
057    
058        private final boolean needGenerics;
059    
060        private boolean generic = false;
061    
062        public BothSignatureWriter(Mode mode, boolean needGenerics) {
063            this.needGenerics = needGenerics;
064    
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        public void writeNothing() {
103            signatureVisitor().visitBaseType('V');
104            writeAsmType0(Type.VOID_TYPE);
105        }
106    
107        public void writeNullableNothing() {
108            signatureVisitor().visitClassType("java/lang/Object");
109            signatureVisitor().visitEnd();
110            writeAsmType0(AsmTypeConstants.OBJECT_TYPE);
111        }
112    
113        private String makeArrayPrefix() {
114            StringBuilder sb = new StringBuilder();
115            for (int i = 0; i < jvmCurrentTypeArrayLevel; ++i) {
116                sb.append('[');
117            }
118            return sb.toString();
119        }
120    
121        private void writeAsmType0(Type type) {
122            if (jvmCurrentType == null) {
123                jvmCurrentType = Type.getType(makeArrayPrefix() + type.getDescriptor());
124            }
125        }
126    
127        public void writeClassBegin(Type asmType) {
128            signatureVisitor().visitClassType(asmType.getInternalName());
129            writeAsmType0(asmType);
130        }
131    
132        public void writeClassEnd() {
133            signatureVisitor().visitEnd();
134        }
135    
136        public void writeArrayType() {
137            push(signatureVisitor().visitArrayType());
138            if (jvmCurrentType == null) {
139                ++jvmCurrentTypeArrayLevel;
140            }
141        }
142    
143        public void writeArrayEnd() {
144            pop();
145        }
146    
147        private static char toJvmVariance(@NotNull Variance variance) {
148            switch (variance) {
149                case INVARIANT: return '=';
150                case IN_VARIANCE: return '-';
151                case OUT_VARIANCE: return '+';
152                default: throw new IllegalStateException("Unknown variance: " + variance);
153            }
154        }
155    
156        public void writeTypeArgument(@NotNull Variance projectionKind) {
157            push(signatureVisitor().visitTypeArgument(toJvmVariance(projectionKind)));
158    
159            generic = true;
160        }
161    
162        public void writeTypeArgumentEnd() {
163            pop();
164        }
165    
166        public void writeTypeVariable(Name name, Type asmType) {
167            signatureVisitor().visitTypeVariable(name.asString());
168            generic = true;
169            writeAsmType0(asmType);
170        }
171    
172        public void writeFormalTypeParameter(String name) {
173            signatureVisitor().visitFormalTypeParameter(name);
174    
175            generic = true;
176        }
177    
178        public void writeClassBound() {
179            push(signatureVisitor().visitClassBound());
180        }
181    
182        public void writeClassBoundEnd() {
183            pop();
184        }
185    
186        public void writeInterfaceBound() {
187            push(signatureVisitor().visitInterfaceBound());
188        }
189    
190        public void writeInterfaceBoundEnd() {
191            pop();
192        }
193    
194        public void writeParametersStart() {
195            // hacks
196            jvmCurrentType = null;
197            jvmCurrentTypeArrayLevel = 0;
198        }
199    
200        public void writeParameterType(JvmMethodParameterKind parameterKind) {
201            // This magic mimics the behavior of javac that enum constructor have these synthetic parameters in erased signature, but doesn't
202            // have them in generic signature. IDEA relies on this behavior.
203            if (parameterKind == JvmMethodParameterKind.ENUM_NAME || parameterKind == JvmMethodParameterKind.ENUM_ORDINAL) {
204                generic = true;
205    
206                // pushing dummy visitor, because we don't want these parameters to appear in generic JVM signature
207                push(new SignatureWriter());
208            }
209            else {
210                push(signatureVisitor().visitParameterType());
211            }
212    
213            this.currentParameterKind = parameterKind;
214        }
215    
216        public void writeParameterTypeEnd() {
217            pop();
218    
219            kotlinParameterTypes.add(new JvmMethodParameterSignature(jvmCurrentType, currentParameterKind));
220    
221            currentParameterKind = null;
222            jvmCurrentType = null;
223            jvmCurrentTypeArrayLevel = 0;
224        }
225    
226        public void writeReturnType() {
227            push(signatureVisitor().visitReturnType());
228        }
229    
230        public void writeReturnTypeEnd() {
231            pop();
232    
233            jvmReturnType = jvmCurrentType;
234            jvmCurrentType = null;
235            jvmCurrentTypeArrayLevel = 0;
236        }
237    
238        public void writeSuperclass() {
239            push(signatureVisitor().visitSuperclass());
240        }
241    
242        public void writeSuperclassEnd() {
243            pop();
244        }
245    
246        public void writeInterface() {
247            push(signatureVisitor().visitInterface());
248        }
249    
250        public void writeInterfaceEnd() {
251            pop();
252        }
253    
254    
255        @NotNull
256        private Method makeAsmMethod(String name) {
257            List<Type> jvmParameterTypes = new ArrayList<Type>(kotlinParameterTypes.size());
258            for (JvmMethodParameterSignature p : kotlinParameterTypes) {
259                jvmParameterTypes.add(p.getAsmType());
260            }
261            return new Method(name, jvmReturnType, jvmParameterTypes.toArray(new Type[jvmParameterTypes.size()]));
262        }
263    
264        @Nullable
265        public String makeJavaGenericSignature() {
266            return generic ? signatureWriter.toString() : null;
267        }
268    
269        @NotNull
270        public JvmMethodSignature makeJvmMethodSignature(String name) {
271            return new JvmMethodSignature(
272                    makeAsmMethod(name),
273                    needGenerics ? makeJavaGenericSignature() : null,
274                    kotlinParameterTypes
275            );
276        }
277    }
278