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