001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 *  Unless required by applicable law or agreed to in writing, software
012 *  distributed under the License is distributed on an "AS IS" BASIS,
013 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 *  See the License for the specific language governing permissions and
015 *  limitations under the License.
016 */
017package org.apache.bcel.classfile;
018
019import java.io.ByteArrayInputStream;
020import java.io.DataInput;
021import java.io.DataOutputStream;
022import java.io.IOException;
023import java.nio.charset.StandardCharsets;
024
025import org.apache.bcel.Const;
026
027/**
028 * This class is derived from <em>Attribute</em> and represents a reference to a GJ attribute.
029 *
030 * @see Attribute
031 */
032public final class Signature extends Attribute {
033
034    /**
035     * Extends ByteArrayInputStream to make 'unreading' chars possible.
036     */
037    private static final class MyByteArrayInputStream extends ByteArrayInputStream {
038
039        MyByteArrayInputStream(final String data) {
040            super(data.getBytes(StandardCharsets.UTF_8));
041        }
042
043        String getData() {
044            return new String(buf, StandardCharsets.UTF_8);
045        }
046
047        void unread() {
048            if (pos > 0) {
049                pos--;
050            }
051        }
052    }
053
054    private static boolean identStart(final int ch) {
055        return ch == 'T' || ch == 'L';
056    }
057
058    // @since 6.0 is no longer final
059    public static boolean isActualParameterList(final String s) {
060        return s.startsWith("L") && s.endsWith(">;");
061    }
062
063    // @since 6.0 is no longer final
064    public static boolean isFormalParameterList(final String s) {
065        return s.startsWith("<") && s.indexOf(':') > 0;
066    }
067
068    private static void matchGJIdent(final MyByteArrayInputStream in, final StringBuilder buf) {
069        int ch;
070        matchIdent(in, buf);
071        ch = in.read();
072        if (ch == '<' || ch == '(') { // Parameterized or method
073            // System.out.println("Enter <");
074            buf.append((char) ch);
075            matchGJIdent(in, buf);
076            while ((ch = in.read()) != '>' && ch != ')') { // List of parameters
077                if (ch == -1) {
078                    throw new IllegalArgumentException("Illegal signature: " + in.getData() + " reaching EOF");
079                }
080                // System.out.println("Still no >");
081                buf.append(", ");
082                in.unread();
083                matchGJIdent(in, buf); // Recursive call
084            }
085            // System.out.println("Exit >");
086            buf.append((char) ch);
087        } else {
088            in.unread();
089        }
090        ch = in.read();
091        if (identStart(ch)) {
092            in.unread();
093            matchGJIdent(in, buf);
094        } else if (ch == ')') {
095            in.unread();
096        } else if (ch != ';') {
097            throw new IllegalArgumentException("Illegal signature: " + in.getData() + " read " + (char) ch);
098        }
099    }
100
101    private static void matchIdent(final MyByteArrayInputStream in, final StringBuilder buf) {
102        int ch;
103        if ((ch = in.read()) == -1) {
104            throw new IllegalArgumentException("Illegal signature: " + in.getData() + " no ident, reaching EOF");
105        }
106        // System.out.println("return from ident:" + (char)ch);
107        if (!identStart(ch)) {
108            final StringBuilder buf2 = new StringBuilder();
109            int count = 1;
110            while (Character.isJavaIdentifierPart((char) ch)) {
111                buf2.append((char) ch);
112                count++;
113                ch = in.read();
114            }
115            if (ch == ':') { // Ok, formal parameter
116                final int skipExpected = "Ljava/lang/Object".length();
117                final long skipActual = in.skip(skipExpected);
118                if (skipActual != skipExpected) {
119                    throw new IllegalStateException(String.format("Unexpected skip: expected=%,d, actual=%,d", skipExpected, skipActual));
120                }
121                buf.append(buf2);
122                ch = in.read();
123                in.unread();
124                // System.out.println("so far:" + buf2 + ":next:" +(char)ch);
125            } else {
126                for (int i = 0; i < count; i++) {
127                    in.unread();
128                }
129            }
130            return;
131        }
132        final StringBuilder buf2 = new StringBuilder();
133        ch = in.read();
134        do {
135            buf2.append((char) ch);
136            ch = in.read();
137            // System.out.println("within ident:"+ (char)ch);
138        } while (ch != -1 && (Character.isJavaIdentifierPart((char) ch) || ch == '/'));
139        buf.append(Utility.pathToPackage(buf2.toString()));
140        // System.out.println("regular return ident:"+ (char)ch + ":" + buf2);
141        if (ch != -1) {
142            in.unread();
143        }
144    }
145
146    public static String translate(final String s) {
147        // System.out.println("Sig:" + s);
148        final StringBuilder buf = new StringBuilder();
149        matchGJIdent(new MyByteArrayInputStream(s), buf);
150        return buf.toString();
151    }
152
153    private int signatureIndex;
154
155    /**
156     * Construct object from file stream.
157     *
158     * @param nameIndex Index in constant pool to CONSTANT_Utf8
159     * @param length Content length in bytes
160     * @param input Input stream
161     * @param constantPool Array of constants
162     * @throws IOException if an I/O error occurs.
163     */
164    Signature(final int nameIndex, final int length, final DataInput input, final ConstantPool constantPool) throws IOException {
165        this(nameIndex, length, input.readUnsignedShort(), constantPool);
166    }
167
168    /**
169     * @param nameIndex Index in constant pool to CONSTANT_Utf8
170     * @param length Content length in bytes
171     * @param signatureIndex Index in constant pool to CONSTANT_Utf8
172     * @param constantPool Array of constants
173     */
174    public Signature(final int nameIndex, final int length, final int signatureIndex, final ConstantPool constantPool) {
175        super(Const.ATTR_SIGNATURE, nameIndex, length, constantPool);
176        this.signatureIndex = signatureIndex;
177    }
178
179    /**
180     * Initialize from another object. Note that both objects use the same references (shallow copy). Use clone() for a
181     * physical copy.
182     */
183    public Signature(final Signature c) {
184        this(c.getNameIndex(), c.getLength(), c.getSignatureIndex(), c.getConstantPool());
185    }
186
187    /**
188     * Called by objects that are traversing the nodes of the tree implicitly defined by the contents of a Java class.
189     * I.e., the hierarchy of methods, fields, attributes, etc. spawns a tree of objects.
190     *
191     * @param v Visitor object
192     */
193    @Override
194    public void accept(final Visitor v) {
195        // System.err.println("Visiting non-standard Signature object");
196        v.visitSignature(this);
197    }
198
199    /**
200     * @return deep copy of this attribute
201     */
202    @Override
203    public Attribute copy(final ConstantPool constantPool) {
204        return (Attribute) clone();
205    }
206
207    /**
208     * Dump source file attribute to file stream in binary format.
209     *
210     * @param file Output file stream
211     * @throws IOException if an I/O error occurs.
212     */
213    @Override
214    public void dump(final DataOutputStream file) throws IOException {
215        super.dump(file);
216        file.writeShort(signatureIndex);
217    }
218
219    /**
220     * @return GJ signature.
221     */
222    public String getSignature() {
223        return super.getConstantPool().getConstantUtf8(signatureIndex).getBytes();
224    }
225
226    /**
227     * @return Index in constant pool of source file name.
228     */
229    public int getSignatureIndex() {
230        return signatureIndex;
231    }
232
233    /**
234     * @param signatureIndex the index info the constant pool of this signature
235     */
236    public void setSignatureIndex(final int signatureIndex) {
237        this.signatureIndex = signatureIndex;
238    }
239
240    /**
241     * @return String representation
242     */
243    @Override
244    public String toString() {
245        final String s = getSignature();
246        return "Signature: " + s;
247    }
248}