001// ASM: a very small and fast Java bytecode manipulation framework
002// Copyright (c) 2000-2011 INRIA, France Telecom
003// All rights reserved.
004//
005// Redistribution and use in source and binary forms, with or without
006// modification, are permitted provided that the following conditions
007// are met:
008// 1. Redistributions of source code must retain the above copyright
009//    notice, this list of conditions and the following disclaimer.
010// 2. Redistributions in binary form must reproduce the above copyright
011//    notice, this list of conditions and the following disclaimer in the
012//    documentation and/or other materials provided with the distribution.
013// 3. Neither the name of the copyright holders nor the names of its
014//    contributors may be used to endorse or promote products derived from
015//    this software without specific prior written permission.
016//
017// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
018// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
019// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
020// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
021// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
022// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
023// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
024// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
025// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
026// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
027// THE POSSIBILITY OF SUCH DAMAGE.
028package io.ebean.enhance.asm.signature;
029
030import io.ebean.enhance.asm.ClassVisitor;
031import io.ebean.enhance.asm.MethodVisitor;
032
033/**
034 * A parser for signature literals, as defined in the Java Virtual Machine Specification (JVMS), to
035 * visit them with a SignatureVisitor.
036 *
037 * @see <a href="https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.9.1">JVMS
038 *     4.7.9.1</a>
039 * @author Thomas Hallgren
040 * @author Eric Bruneton
041 */
042public class SignatureReader {
043
044  /** The JVMS signature to be read. */
045  private final String signatureValue;
046
047  /**
048   * Constructs a {@link SignatureReader} for the given signature.
049   *
050   * @param signature A <i>JavaTypeSignature</i>, <i>ClassSignature</i> or <i>MethodSignature</i>.
051   */
052  public SignatureReader(final String signature) {
053    this.signatureValue = signature;
054  }
055
056  /**
057   * Makes the given visitor visit the signature of this {@link SignatureReader}. This signature is
058   * the one specified in the constructor (see {@link #SignatureReader}). This method is intended to
059   * be called on a {@link SignatureReader} that was created using a <i>ClassSignature</i> (such as
060   * the <code>signature</code> parameter of the {@link ClassVisitor#visit}
061   * method) or a <i>MethodSignature</i> (such as the <code>signature</code> parameter of the {@link
062   * ClassVisitor#visitMethod} method).
063   *
064   * @param signatureVistor the visitor that must visit this signature.
065   */
066  public void accept(final SignatureVisitor signatureVistor) {
067    String signature = this.signatureValue;
068    int length = signature.length();
069    int offset; // Current offset in the parsed signature (parsed from left to right).
070    char currentChar; // The signature character at 'offset', or just before.
071
072    // If the signature starts with '<', it starts with TypeParameters, i.e. a formal type parameter
073    // identifier, followed by one or more pair ':',ReferenceTypeSignature (for its class bound and
074    // interface bounds).
075    if (signature.charAt(0) == '<') {
076      // Invariant: offset points to the second character of a formal type parameter name at the
077      // beginning of each iteration of the loop below.
078      offset = 2;
079      do {
080        // The formal type parameter name is everything between offset - 1 and the first ':'.
081        int classBoundStartOffset = signature.indexOf(':', offset);
082        signatureVistor.visitFormalTypeParameter(
083            signature.substring(offset - 1, classBoundStartOffset));
084
085        // If the character after the ':' class bound marker is not the start of a
086        // ReferenceTypeSignature, it means the class bound is empty (which is a valid case).
087        offset = classBoundStartOffset + 1;
088        currentChar = signature.charAt(offset);
089        if (currentChar == 'L' || currentChar == '[' || currentChar == 'T') {
090          offset = parseType(signature, offset, signatureVistor.visitClassBound());
091        }
092
093        // While the character after the class bound or after the last parsed interface bound
094        // is ':', we need to parse another interface bound.
095        while ((currentChar = signature.charAt(offset++)) == ':') {
096          offset = parseType(signature, offset, signatureVistor.visitInterfaceBound());
097        }
098
099        // At this point a TypeParameter has been fully parsed, and we need to parse the next one
100        // (note that currentChar is now the first character of the next TypeParameter, and that
101        // offset points to the second character), unless the character just after this
102        // TypeParameter signals the end of the TypeParameters.
103      } while (currentChar != '>');
104    } else {
105      offset = 0;
106    }
107
108    // If the (optional) TypeParameters is followed by '(' this means we are parsing a
109    // MethodSignature, which has JavaTypeSignature type inside parentheses, followed by a Result
110    // type and optional ThrowsSignature types.
111    if (signature.charAt(offset) == '(') {
112      offset++;
113      while (signature.charAt(offset) != ')') {
114        offset = parseType(signature, offset, signatureVistor.visitParameterType());
115      }
116      // Use offset + 1 to skip ')'.
117      offset = parseType(signature, offset + 1, signatureVistor.visitReturnType());
118      while (offset < length) {
119        // Use offset + 1 to skip the first character of a ThrowsSignature, i.e. '^'.
120        offset = parseType(signature, offset + 1, signatureVistor.visitExceptionType());
121      }
122    } else {
123      // Otherwise we are parsing a ClassSignature (by hypothesis on the method input), which has
124      // one or more ClassTypeSignature for the super class and the implemented interfaces.
125      offset = parseType(signature, offset, signatureVistor.visitSuperclass());
126      while (offset < length) {
127        offset = parseType(signature, offset, signatureVistor.visitInterface());
128      }
129    }
130  }
131
132  /**
133   * Makes the given visitor visit the signature of this {@link SignatureReader}. This signature is
134   * the one specified in the constructor (see {@link #SignatureReader}). This method is intended to
135   * be called on a {@link SignatureReader} that was created using a <i>JavaTypeSignature</i>, such
136   * as the <code>signature</code> parameter of the {@link
137   * ClassVisitor#visitField} or {@link
138   * MethodVisitor#visitLocalVariable} methods.
139   *
140   * @param signatureVisitor the visitor that must visit this signature.
141   */
142  public void acceptType(final SignatureVisitor signatureVisitor) {
143    parseType(signatureValue, 0, signatureVisitor);
144  }
145
146  /**
147   * Parses a JavaTypeSignature and makes the given visitor visit it.
148   *
149   * @param signature a string containing the signature that must be parsed.
150   * @param startOffset index of the first character of the signature to parsed.
151   * @param signatureVisitor the visitor that must visit this signature.
152   * @return the index of the first character after the parsed signature.
153   */
154  private static int parseType(
155      final String signature, final int startOffset, final SignatureVisitor signatureVisitor) {
156    int offset = startOffset; // Current offset in the parsed signature.
157    char currentChar = signature.charAt(offset++); // The signature character at 'offset'.
158
159    // Switch based on the first character of the JavaTypeSignature, which indicates its kind.
160    switch (currentChar) {
161      case 'Z':
162      case 'C':
163      case 'B':
164      case 'S':
165      case 'I':
166      case 'F':
167      case 'J':
168      case 'D':
169      case 'V':
170        // Case of a BaseType or a VoidDescriptor.
171        signatureVisitor.visitBaseType(currentChar);
172        return offset;
173
174      case '[':
175        // Case of an ArrayTypeSignature, a '[' followed by a JavaTypeSignature.
176        return parseType(signature, offset, signatureVisitor.visitArrayType());
177
178      case 'T':
179        // Case of TypeVariableSignature, an identifier between 'T' and ';'.
180        int endOffset = signature.indexOf(';', offset);
181        signatureVisitor.visitTypeVariable(signature.substring(offset, endOffset));
182        return endOffset + 1;
183
184      case 'L':
185        // Case of a ClassTypeSignature, which ends with ';'.
186        // These signatures have a main class type followed by zero or more inner class types
187        // (separated by '.'). Each can have type arguments, inside '<' and '>'.
188        int start = offset; // The start offset of the currently parsed main or inner class name.
189        boolean visited = false; // Whether the currently parsed class name has been visited.
190        boolean inner = false; // Whether we are currently parsing an inner class type.
191        // Parses the signature, one character at a time.
192        while (true) {
193          currentChar = signature.charAt(offset++);
194          if (currentChar == '.' || currentChar == ';') {
195            // If a '.' or ';' is encountered, this means we have fully parsed the main class name
196            // or an inner class name. This name may already have been visited it is was followed by
197            // type arguments between '<' and '>'. If not, we need to visit it here.
198            if (!visited) {
199              String name = signature.substring(start, offset - 1);
200              if (inner) {
201                signatureVisitor.visitInnerClassType(name);
202              } else {
203                signatureVisitor.visitClassType(name);
204              }
205            }
206            // If we reached the end of the ClassTypeSignature return, otherwise start the parsing
207            // of a new class name, which is necessarily an inner class name.
208            if (currentChar == ';') {
209              signatureVisitor.visitEnd();
210              break;
211            }
212            start = offset;
213            visited = false;
214            inner = true;
215          } else if (currentChar == '<') {
216            // If a '<' is encountered, this means we have fully parsed the main class name or an
217            // inner class name, and that we now need to parse TypeArguments. First, we need to
218            // visit the parsed class name.
219            String name = signature.substring(start, offset - 1);
220            if (inner) {
221              signatureVisitor.visitInnerClassType(name);
222            } else {
223              signatureVisitor.visitClassType(name);
224            }
225            visited = true;
226            // Now, parse the TypeArgument(s), one at a time.
227            while ((currentChar = signature.charAt(offset)) != '>') {
228              switch (currentChar) {
229                case '*':
230                  // Unbounded TypeArgument.
231                  ++offset;
232                  signatureVisitor.visitTypeArgument();
233                  break;
234                case '+':
235                case '-':
236                  // Extends or Super TypeArgument. Use offset + 1 to skip the '+' or '-'.
237                  offset =
238                      parseType(
239                          signature, offset + 1, signatureVisitor.visitTypeArgument(currentChar));
240                  break;
241                default:
242                  // Instanceof TypeArgument. The '=' is implicit.
243                  offset = parseType(signature, offset, signatureVisitor.visitTypeArgument('='));
244                  break;
245              }
246            }
247          }
248        }
249        return offset;
250
251      default:
252        throw new IllegalArgumentException();
253    }
254  }
255}