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