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