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;
024import java.util.Objects;
025
026import org.apache.bcel.Const;
027import org.apache.bcel.util.Args;
028
029/**
030 * This class is derived from <em>Attribute</em> and represents a reference to a GJ attribute.
031 *
032 * @see Attribute
033 */
034public final class Signature extends Attribute {
035
036    /**
037     * Extends ByteArrayInputStream to make 'unreading' chars possible.
038     */
039    private static final class MyByteArrayInputStream extends ByteArrayInputStream {
040
041        MyByteArrayInputStream(final String data) {
042            super(data.getBytes(StandardCharsets.UTF_8));
043        }
044
045        String getData() {
046            return new String(buf, StandardCharsets.UTF_8);
047        }
048
049        void unread() {
050            if (pos > 0) {
051                pos--;
052            }
053        }
054    }
055
056    private static boolean identStart(final int ch) {
057        return ch == 'T' || ch == 'L';
058    }
059
060    // @since 6.0 is no longer final
061    public static boolean isActualParameterList(final String s) {
062        return s.startsWith("L") && s.endsWith(">;");
063    }
064
065    // @since 6.0 is no longer final
066    public static boolean isFormalParameterList(final String s) {
067        return s.startsWith("<") && s.indexOf(':') > 0;
068    }
069
070    private static void matchGJIdent(final MyByteArrayInputStream in, final StringBuilder buf) {
071        int ch;
072        matchIdent(in, buf);
073        ch = in.read();
074        if (ch == '<' || ch == '(') { // Parameterized or method
075            // System.out.println("Enter <");
076            buf.append((char) ch);
077            matchGJIdent(in, buf);
078            while ((ch = in.read()) != '>' && ch != ')') { // List of parameters
079                if (ch == -1) {
080                    throw new IllegalArgumentException("Illegal signature: " + in.getData() + " reaching EOF");
081                }
082                // System.out.println("Still no >");
083                buf.append(", ");
084                in.unread();
085                matchGJIdent(in, buf); // Recursive call
086            }
087            // System.out.println("Exit >");
088            buf.append((char) ch);
089        } else {
090            in.unread();
091        }
092        ch = in.read();
093        if (identStart(ch)) {
094            in.unread();
095            matchGJIdent(in, buf);
096        } else if (ch == ')') {
097            in.unread();
098        } else if (ch != ';') {
099            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}