    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}