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