001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   https://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.bcel.classfile;
020
021import java.io.ByteArrayInputStream;
022import java.io.DataInput;
023import java.io.DataOutputStream;
024import java.io.IOException;
025import java.nio.charset.StandardCharsets;
026import java.util.Objects;
027
028import org.apache.bcel.Const;
029import org.apache.bcel.util.Args;
030
031/**
032 * This class is derived from <em>Attribute</em> and represents a reference to a GJ attribute.
033 *
034 * @see Attribute
035 */
036public final class Signature extends Attribute {
037
038    /**
039     * Extends ByteArrayInputStream to make 'unreading' chars possible.
040     */
041    private static final class MyByteArrayInputStream extends ByteArrayInputStream {
042
043        MyByteArrayInputStream(final String data) {
044            super(data.getBytes(StandardCharsets.UTF_8));
045        }
046
047        String getData() {
048            return new String(buf, StandardCharsets.UTF_8);
049        }
050
051        void unread() {
052            if (pos > 0) {
053                pos--;
054            }
055        }
056    }
057
058    private static boolean identStart(final int ch) {
059        return ch == 'T' || ch == 'L';
060    }
061
062    // @since 6.0 is no longer final
063    public static boolean isActualParameterList(final String s) {
064        return s.startsWith("L") && s.endsWith(">;");
065    }
066
067    // @since 6.0 is no longer final
068    public static boolean isFormalParameterList(final String s) {
069        return s.startsWith("<") && s.indexOf(':') > 0;
070    }
071
072    private static void matchGJIdent(final MyByteArrayInputStream in, final StringBuilder buf) {
073        int ch;
074        matchIdent(in, buf);
075        ch = in.read();
076        if (ch == '<' || ch == '(') { // Parameterized or method
077            // System.out.println("Enter <");
078            buf.append((char) ch);
079            matchGJIdent(in, buf);
080            while ((ch = in.read()) != '>' && ch != ')') { // List of parameters
081                if (ch == -1) {
082                    throw new IllegalArgumentException("Illegal signature: " + in.getData() + " reaching EOF");
083                }
084                // System.out.println("Still no >");
085                buf.append(", ");
086                in.unread();
087                matchGJIdent(in, buf); // Recursive call
088            }
089            // System.out.println("Exit >");
090            buf.append((char) ch);
091        } else {
092            in.unread();
093        }
094        ch = in.read();
095        if (identStart(ch)) {
096            in.unread();
097            matchGJIdent(in, buf);
098        } else if (ch == ')') {
099            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}