View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.bcel.classfile;
20  
21  import java.io.ByteArrayInputStream;
22  import java.io.DataInput;
23  import java.io.DataOutputStream;
24  import java.io.IOException;
25  import java.nio.charset.StandardCharsets;
26  import java.util.Objects;
27  
28  import org.apache.bcel.Const;
29  import org.apache.bcel.util.Args;
30  
31  /**
32   * This class is derived from <em>Attribute</em> and represents a reference to a GJ attribute.
33   *
34   * @see Attribute
35   */
36  public final class Signature extends Attribute {
37  
38      /**
39       * Extends ByteArrayInputStream to make 'unreading' chars possible.
40       */
41      private static final class MyByteArrayInputStream extends ByteArrayInputStream {
42  
43          MyByteArrayInputStream(final String data) {
44              super(data.getBytes(StandardCharsets.UTF_8));
45          }
46  
47          String getData() {
48              return new String(buf, StandardCharsets.UTF_8);
49          }
50  
51          void unread() {
52              if (pos > 0) {
53                  pos--;
54              }
55          }
56      }
57  
58      private static boolean identStart(final int ch) {
59          return ch == 'T' || ch == 'L';
60      }
61  
62      // @since 6.0 is no longer final
63      public static boolean isActualParameterList(final String s) {
64          return s.startsWith("L") && s.endsWith(">;");
65      }
66  
67      // @since 6.0 is no longer final
68      public static boolean isFormalParameterList(final String s) {
69          return s.startsWith("<") && s.indexOf(':') > 0;
70      }
71  
72      private static void matchGJIdent(final MyByteArrayInputStream in, final StringBuilder buf) {
73          int ch;
74          matchIdent(in, buf);
75          ch = in.read();
76          if (ch == '<' || ch == '(') { // Parameterized or method
77              // System.out.println("Enter <");
78              buf.append((char) ch);
79              matchGJIdent(in, buf);
80              while ((ch = in.read()) != '>' && ch != ')') { // List of parameters
81                  if (ch == -1) {
82                      throw new IllegalArgumentException("Illegal signature: " + in.getData() + " reaching EOF");
83                  }
84                  // System.out.println("Still no >");
85                  buf.append(", ");
86                  in.unread();
87                  matchGJIdent(in, buf); // Recursive call
88              }
89              // System.out.println("Exit >");
90              buf.append((char) ch);
91          } else {
92              in.unread();
93          }
94          ch = in.read();
95          if (identStart(ch)) {
96              in.unread();
97              matchGJIdent(in, buf);
98          } else if (ch == ')') {
99              in.unread();
100         } else if (ch != ';') {
101             throw new IllegalArgumentException("Illegal signature: " + in.getData() + " read " + (char) ch);
102         }
103     }
104 
105     private static void matchIdent(final MyByteArrayInputStream in, final StringBuilder buf) {
106         int ch;
107         if ((ch = in.read()) == -1) {
108             throw new IllegalArgumentException("Illegal signature: " + in.getData() + " no ident, reaching EOF");
109         }
110         // System.out.println("return from ident:" + (char) ch);
111         if (!identStart(ch)) {
112             final StringBuilder buf2 = new StringBuilder();
113             int count = 1;
114             while (Character.isJavaIdentifierPart((char) ch)) {
115                 buf2.append((char) ch);
116                 count++;
117                 ch = in.read();
118             }
119             if (ch == ':') { // Ok, formal parameter
120                 final int skipExpected = "Ljava/lang/Object".length();
121                 final long skipActual = in.skip(skipExpected);
122                 if (skipActual != skipExpected) {
123                     throw new IllegalStateException(String.format("Unexpected skip: expected=%,d, actual=%,d", skipExpected, skipActual));
124                 }
125                 buf.append(buf2);
126                 ch = in.read();
127                 in.unread();
128                 // System.out.println("so far:" + buf2 + ":next:" +(char) ch);
129             } else {
130                 for (int i = 0; i < count; i++) {
131                     in.unread();
132                 }
133             }
134             return;
135         }
136         final StringBuilder buf2 = new StringBuilder();
137         ch = in.read();
138         do {
139             buf2.append((char) ch);
140             ch = in.read();
141             // System.out.println("within ident:"+ (char) ch);
142         } while (ch != -1 && (Character.isJavaIdentifierPart((char) ch) || ch == '/'));
143         buf.append(Utility.pathToPackage(buf2.toString()));
144         // System.out.println("regular return ident:"+ (char) ch + ":" + buf2);
145         if (ch != -1) {
146             in.unread();
147         }
148     }
149 
150     public static String translate(final String s) {
151         // System.out.println("Sig:" + s);
152         final StringBuilder buf = new StringBuilder();
153         matchGJIdent(new MyByteArrayInputStream(s), buf);
154         return buf.toString();
155     }
156 
157     private int signatureIndex;
158 
159     /**
160      * Constructs object from file stream.
161      *
162      * @param nameIndex Index in constant pool to CONSTANT_Utf8
163      * @param length Content length in bytes
164      * @param input Input stream
165      * @param constantPool Array of constants
166      * @throws IOException if an I/O error occurs.
167      */
168     Signature(final int nameIndex, final int length, final DataInput input, final ConstantPool constantPool) throws IOException {
169         this(nameIndex, length, input.readUnsignedShort(), constantPool);
170     }
171 
172     /**
173      * @param nameIndex Index in constant pool to CONSTANT_Utf8
174      * @param length Content length in bytes
175      * @param signatureIndex Index in constant pool to CONSTANT_Utf8
176      * @param constantPool Array of constants
177      */
178     public Signature(final int nameIndex, final int length, final int signatureIndex, final ConstantPool constantPool) {
179         super(Const.ATTR_SIGNATURE, nameIndex, Args.require(length, 2, "Signature length attribute"), constantPool);
180         this.signatureIndex = signatureIndex;
181         // validate:
182         Objects.requireNonNull(constantPool.getConstantUtf8(signatureIndex), "constantPool.getConstantUtf8(signatureIndex)");
183     }
184 
185     /**
186      * Initialize from another object. Note that both objects use the same references (shallow copy). Use clone() for a
187      * physical copy.
188      *
189      * @param c Source to copy.
190      */
191     public Signature(final Signature c) {
192         this(c.getNameIndex(), c.getLength(), c.getSignatureIndex(), c.getConstantPool());
193     }
194 
195     /**
196      * Called by objects that are traversing the nodes of the tree implicitly defined by the contents of a Java class.
197      * I.e., the hierarchy of methods, fields, attributes, etc. spawns a tree of objects.
198      *
199      * @param v Visitor object
200      */
201     @Override
202     public void accept(final Visitor v) {
203         // System.err.println("Visiting non-standard Signature object");
204         v.visitSignature(this);
205     }
206 
207     /**
208      * @return deep copy of this attribute
209      */
210     @Override
211     public Attribute copy(final ConstantPool constantPool) {
212         return (Attribute) clone();
213     }
214 
215     /**
216      * Dump source file attribute to file stream in binary format.
217      *
218      * @param file Output file stream
219      * @throws IOException if an I/O error occurs.
220      */
221     @Override
222     public void dump(final DataOutputStream file) throws IOException {
223         super.dump(file);
224         file.writeShort(signatureIndex);
225     }
226 
227     /**
228      * @return GJ signature.
229      */
230     public String getSignature() {
231         return super.getConstantPool().getConstantUtf8(signatureIndex).getBytes();
232     }
233 
234     /**
235      * @return Index in constant pool of source file name.
236      */
237     public int getSignatureIndex() {
238         return signatureIndex;
239     }
240 
241     /**
242      * @param signatureIndex the index info the constant pool of this signature
243      */
244     public void setSignatureIndex(final int signatureIndex) {
245         this.signatureIndex = signatureIndex;
246     }
247 
248     /**
249      * @return String representation
250      */
251     @Override
252     public String toString() {
253         return "Signature: " + getSignature();
254     }
255 }