    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}