    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}