    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.generic;
    020
    021import java.io.ByteArrayInputStream;
    022import java.io.ByteArrayOutputStream;
    023import java.io.DataInput;
    024import java.io.DataInputStream;
    025import java.io.DataOutputStream;
    026import java.io.IOException;
    027import java.util.ArrayList;
    028import java.util.List;
    029import java.util.stream.Collectors;
    030
    031import org.apache.bcel.classfile.AnnotationEntry;
    032import org.apache.bcel.classfile.Attribute;
    033import org.apache.bcel.classfile.ConstantUtf8;
    034import org.apache.bcel.classfile.ElementValuePair;
    035import org.apache.bcel.classfile.RuntimeInvisibleAnnotations;
    036import org.apache.bcel.classfile.RuntimeInvisibleParameterAnnotations;
    037import org.apache.bcel.classfile.RuntimeVisibleAnnotations;
    038import org.apache.bcel.classfile.RuntimeVisibleParameterAnnotations;
    039import org.apache.commons.lang3.ArrayUtils;
    040import org.apache.commons.lang3.stream.Streams;
    041
    042/**
    043 * Generates annotation entries.
    044 *
    045 * @since 6.0
    046 */
    047public class AnnotationEntryGen {
    048
    049    static final AnnotationEntryGen[] EMPTY_ARRAY = {};
    050
    051    /**
    052     * Converts a list of AnnotationGen objects into a set of attributes that can be attached to the class file.
    053     *
    054     * @param cp The constant pool gen where we can create the necessary name refs.
    055     * @param annotationEntryGens An array of AnnotationGen objects.
    056     */
    057    static Attribute[] getAnnotationAttributes(final ConstantPoolGen cp, final AnnotationEntryGen[] annotationEntryGens) {
    058        if (ArrayUtils.isEmpty(annotationEntryGens)) {
    059            return Attribute.EMPTY_ARRAY;
    060        }
    061
    062        try {
    063            int countVisible = 0;
    064            int countInvisible = 0;
    065
    066            // put the annotations in the right output stream
    067            for (final AnnotationEntryGen a : annotationEntryGens) {
    068                if (a.isRuntimeVisible()) {
    069                    countVisible++;
    070                } else {
    071                    countInvisible++;
    072                }
    073            }
    074
    075            final ByteArrayOutputStream rvaBytes = new ByteArrayOutputStream();
    076            final ByteArrayOutputStream riaBytes = new ByteArrayOutputStream();
    077            try (DataOutputStream rvaDos = new DataOutputStream(rvaBytes); DataOutputStream riaDos = new DataOutputStream(riaBytes)) {
    078
    079                rvaDos.writeShort(countVisible);
    080                riaDos.writeShort(countInvisible);
    081
    082                // put the annotations in the right output stream
    083                for (final AnnotationEntryGen a : annotationEntryGens) {
    084                    if (a.isRuntimeVisible()) {
    085                        a.dump(rvaDos);
    086                    } else {
    087                        a.dump(riaDos);
    088                    }
    089                }
    090            }
    091
    092            final byte[] rvaData = rvaBytes.toByteArray();
    093            final byte[] riaData = riaBytes.toByteArray();
    094
    095            int rvaIndex = -1;
    096            int riaIndex = -1;
    097
    098            if (rvaData.length > 2) {
    099                rvaIndex = cp.addUtf8("RuntimeVisibleAnnotations");
    100            }
    101            if (riaData.length > 2) {
    102                riaIndex = cp.addUtf8("RuntimeInvisibleAnnotations");
    103            }
    104
    105            final List<Attribute> newAttributes = new ArrayList<>();
    106            if (rvaData.length > 2) {
    107                newAttributes
    108                    .add(new RuntimeVisibleAnnotations(rvaIndex, rvaData.length, new DataInputStream(new ByteArrayInputStream(rvaData)), cp.getConstantPool()));
    109            }
    110            if (riaData.length > 2) {
    111                newAttributes.add(
    112                    new RuntimeInvisibleAnnotations(riaIndex, riaData.length, new DataInputStream(new ByteArrayInputStream(riaData)), cp.getConstantPool()));
    113            }
    114
    115            return newAttributes.toArray(Attribute.EMPTY_ARRAY);
    116        } catch (final IOException e) {
    117            System.err.println("IOException whilst processing annotations");
    118            e.printStackTrace();
    119        }
    120        return null;
    121    }
    122
    123    /**
    124     * Annotations against a class are stored in one of four attribute kinds: - RuntimeVisibleParameterAnnotations -
    125     * RuntimeInvisibleParameterAnnotations
    126     */
    127    static Attribute[] getParameterAnnotationAttributes(final ConstantPoolGen cp,
    128        final List<AnnotationEntryGen>[] /* Array of lists, array size depends on #params */ vec) {
    129        final int[] visCount = new int[vec.length];
    130        int totalVisCount = 0;
    131        final int[] invisCount = new int[vec.length];
    132        int totalInvisCount = 0;
    133        try {
    134            for (int i = 0; i < vec.length; i++) {
    135                if (vec[i] != null) {
    136                    for (final AnnotationEntryGen element : vec[i]) {
    137                        if (element.isRuntimeVisible()) {
    138                            visCount[i]++;
    139                            totalVisCount++;
    140                        } else {
    141                            invisCount[i]++;
    142                            totalInvisCount++;
    143                        }
    144                    }
    145                }
    146            }
    147            // Lets do the visible ones
    148            final ByteArrayOutputStream rvaBytes = new ByteArrayOutputStream();
    149            try (DataOutputStream rvaDos = new DataOutputStream(rvaBytes)) {
    150                rvaDos.writeByte(vec.length); // First goes number of parameters
    151                for (int i = 0; i < vec.length; i++) {
    152                    rvaDos.writeShort(visCount[i]);
    153                    if (visCount[i] > 0) {
    154                        for (final AnnotationEntryGen element : vec[i]) {
    155                            if (element.isRuntimeVisible()) {
    156                                element.dump(rvaDos);
    157                            }
    158                        }
    159                    }
    160                }
    161            }
    162            // Lets do the invisible ones
    163            final ByteArrayOutputStream riaBytes = new ByteArrayOutputStream();
    164            try (DataOutputStream riaDos = new DataOutputStream(riaBytes)) {
    165                riaDos.writeByte(vec.length); // First goes number of parameters
    166                for (int i = 0; i < vec.length; i++) {
    167                    riaDos.writeShort(invisCount[i]);
    168                    if (invisCount[i] > 0) {
    169                        for (final AnnotationEntryGen element : vec[i]) {
    170                            if (!element.isRuntimeVisible()) {
    171                                element.dump(riaDos);
    172                            }
    173                        }
    174                    }
    175                }
    176            }
    177            final byte[] rvaData = rvaBytes.toByteArray();
    178            final byte[] riaData = riaBytes.toByteArray();
    179            int rvaIndex = -1;
    180            int riaIndex = -1;
    181            if (totalVisCount > 0) {
    182                rvaIndex = cp.addUtf8("RuntimeVisibleParameterAnnotations");
    183            }
    184            if (totalInvisCount > 0) {
    185                riaIndex = cp.addUtf8("RuntimeInvisibleParameterAnnotations");
    186            }
    187            final List<Attribute> newAttributes = new ArrayList<>();
    188            if (totalVisCount > 0) {
    189                newAttributes.add(new RuntimeVisibleParameterAnnotations(rvaIndex, rvaData.length, new DataInputStream(new ByteArrayInputStream(rvaData)),
    190                    cp.getConstantPool()));
    191            }
    192            if (totalInvisCount > 0) {
    193                newAttributes.add(new RuntimeInvisibleParameterAnnotations(riaIndex, riaData.length, new DataInputStream(new ByteArrayInputStream(riaData)),
    194                    cp.getConstantPool()));
    195            }
    196            return newAttributes.toArray(Attribute.EMPTY_ARRAY);
    197        } catch (final IOException e) {
    198            System.err.println("IOException whilst processing parameter annotations");
    199            e.printStackTrace();
    200        }
    201        return null;
    202    }
    203
    204    /**
    205     * Reads an AnnotationEntryGen from a DataInput.
    206     *
    207     * @param dis the data input stream.
    208     * @param cpool the constant pool generator.
    209     * @param b whether the annotation is runtime visible.
    210     * @return the annotation entry generator.
    211     * @throws IOException if an I/O error occurs.
    212     */
    213    public static AnnotationEntryGen read(final DataInput dis, final ConstantPoolGen cpool, final boolean b) throws IOException {
    214        final AnnotationEntryGen a = new AnnotationEntryGen(cpool);
    215        a.typeIndex = dis.readUnsignedShort();
    216        final int elemValuePairCount = dis.readUnsignedShort();
    217        for (int i = 0; i < elemValuePairCount; i++) {
    218            final int nidx = dis.readUnsignedShort();
    219            a.addElementNameValuePair(new ElementValuePairGen(nidx, ElementValueGen.readElementValue(dis, cpool), cpool));
    220        }
    221        a.isRuntimeVisible(b);
    222        return a;
    223    }
    224
    225    private int typeIndex;
    226
    227    private List<ElementValuePairGen> evs;
    228
    229    private final ConstantPoolGen cpool;
    230
    231    private boolean isRuntimeVisible;
    232
    233    /**
    234     * Here we are taking a fixed annotation of type Annotation and building a modifiable AnnotationGen object. If the pool
    235     * passed in is for a different class file, then copyPoolEntries should have been passed as true as that will force us
    236     * to do a deep copy of the annotation and move the cpool entries across. We need to copy the type and the element name
    237     * value pairs and the visibility.
    238     *
    239     * @param a the annotation entry.
    240     * @param cpool the constant pool generator.
    241     * @param copyPoolEntries whether to copy pool entries.
    242     */
    243    public AnnotationEntryGen(final AnnotationEntry a, final ConstantPoolGen cpool, final boolean copyPoolEntries) {
    244        this.cpool = cpool;
    245        if (copyPoolEntries) {
    246            typeIndex = cpool.addUtf8(a.getAnnotationType());
    247        } else {
    248            typeIndex = a.getAnnotationTypeIndex();
    249        }
    250        isRuntimeVisible = a.isRuntimeVisible();
    251        evs = copyValues(a.getElementValuePairs(), cpool, copyPoolEntries);
    252    }
    253
    254    private AnnotationEntryGen(final ConstantPoolGen cpool) {
    255        this.cpool = cpool;
    256    }
    257
    258    /**
    259     * Constructs an AnnotationEntryGen.
    260     *
    261     * @param type the object type.
    262     * @param elements the element value pairs.
    263     * @param vis whether the annotation is visible.
    264     * @param cpool the constant pool generator.
    265     */
    266    public AnnotationEntryGen(final ObjectType type, final List<ElementValuePairGen> elements, final boolean vis, final ConstantPoolGen cpool) {
    267        this.cpool = cpool;
    268        this.typeIndex = cpool.addUtf8(type.getSignature());
    269        evs = elements;
    270        isRuntimeVisible = vis;
    271    }
    272
    273    /**
    274     * Adds an element name value pair.
    275     *
    276     * @param evp the element value pair generator.
    277     */
    278    public void addElementNameValuePair(final ElementValuePairGen evp) {
    279        if (evs == null) {
    280            evs = new ArrayList<>();
    281        }
    282        evs.add(evp);
    283    }
    284
    285    private List<ElementValuePairGen> copyValues(final ElementValuePair[] in, final ConstantPoolGen cpool, final boolean copyPoolEntries) {
    286        return Streams.of(in).map(nvp -> new ElementValuePairGen(nvp, cpool, copyPoolEntries)).collect(Collectors.toList());
    287    }
    288
    289    /**
    290     * Dumps this annotation entry to a DataOutputStream.
    291     *
    292     * @param dos the data output stream.
    293     * @throws IOException if an I/O error occurs.
    294     */
    295    public void dump(final DataOutputStream dos) throws IOException {
    296        dos.writeShort(typeIndex); // u2 index of type name in cpool
    297        dos.writeShort(evs.size()); // u2 element_value pair count
    298        for (final ElementValuePairGen envp : evs) {
    299            envp.dump(dos);
    300        }
    301    }
    302
    303    /**
    304     * Retrieves an immutable version of this AnnotationGen.
    305     *
    306     * @return an immutable version of this AnnotationGen.
    307     */
    308    public AnnotationEntry getAnnotation() {
    309        final AnnotationEntry a = new AnnotationEntry(typeIndex, cpool.getConstantPool(), isRuntimeVisible);
    310        for (final ElementValuePairGen element : evs) {
    311            a.addElementNameValuePair(element.getElementNameValuePair());
    312        }
    313        return a;
    314    }
    315
    316    /**
    317     * Gets the type index.
    318     *
    319     * @return the type index.
    320     */
    321    public int getTypeIndex() {
    322        return typeIndex;
    323    }
    324
    325    /**
    326     * Gets the type name.
    327     *
    328     * @return the type name.
    329     */
    330    public final String getTypeName() {
    331        return getTypeSignature(); // BCELBUG: Should I use this instead?
    332        // Utility.signatureToString(getTypeSignature());
    333    }
    334
    335    /**
    336     * Gets the type signature.
    337     *
    338     * @return the type signature.
    339     */
    340    public final String getTypeSignature() {
    341        // ConstantClass c = (ConstantClass) cpool.getConstant(typeIndex);
    342        final ConstantUtf8 utf8 = (ConstantUtf8) cpool.getConstant(typeIndex/* c.getNameIndex() */);
    343        return utf8.getBytes();
    344    }
    345
    346    /**
    347     * Returns list of ElementNameValuePair objects.
    348     *
    349     * @return list of ElementNameValuePair objects.
    350     */
    351    public List<ElementValuePairGen> getValues() {
    352        return evs;
    353    }
    354
    355    /**
    356     * Gets whether this annotation is runtime visible.
    357     *
    358     * @return true if this annotation is runtime visible.
    359     */
    360    public boolean isRuntimeVisible() {
    361        return isRuntimeVisible;
    362    }
    363
    364    private void isRuntimeVisible(final boolean b) {
    365        isRuntimeVisible = b;
    366    }
    367
    368    /**
    369     * Returns a short string representation of this annotation.
    370     *
    371     * @return a short string representation of this annotation.
    372     */
    373    public String toShortString() {
    374        final StringBuilder s = new StringBuilder();
    375        s.append("@").append(getTypeName()).append("(");
    376        for (int i = 0; i < evs.size(); i++) {
    377            s.append(evs.get(i));
    378            if (i + 1 < evs.size()) {
    379                s.append(",");
    380            }
    381        }
    382        s.append(")");
    383        return s.toString();
    384    }
    385
    386    @Override
    387    public String toString() {
    388        final StringBuilder s = new StringBuilder(32); // CHECKSTYLE IGNORE MagicNumber
    389        s.append("AnnotationGen:[").append(getTypeName()).append(" #").append(evs.size()).append(" {");
    390        for (int i = 0; i < evs.size(); i++) {
    391            s.append(evs.get(i));
    392            if (i + 1 < evs.size()) {
    393                s.append(",");
    394            }
    395        }
    396        s.append("}]");
    397        return s.toString();
    398    }
    399
    400}