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