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