    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}