    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 *
    017 */
    018package org.apache.bcel.generic;
    019
    020import java.io.ByteArrayInputStream;
    021import java.io.ByteArrayOutputStream;
    022import java.io.DataInput;
    023import java.io.DataInputStream;
    024import java.io.DataOutputStream;
    025import java.io.IOException;
    026import java.util.ArrayList;
    027import java.util.List;
    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;
    037
    038/**
    039 * @since 6.0
    040 */
    041public class AnnotationEntryGen {
    042
    043    static final AnnotationEntryGen[] EMPTY_ARRAY = {};
    044
    045    /**
    046     * Converts a list of AnnotationGen objects into a set of attributes that can be attached to the class file.
    047     *
    048     * @param cp The constant pool gen where we can create the necessary name refs
    049     * @param annotationEntryGens An array of AnnotationGen objects
    050     */
    051    static Attribute[] getAnnotationAttributes(final ConstantPoolGen cp, final AnnotationEntryGen[] annotationEntryGens) {
    052        if (annotationEntryGens.length == 0) {
    053            return Attribute.EMPTY_ARRAY;
    054        }
    055
    056        try {
    057            int countVisible = 0;
    058            int countInvisible = 0;
    059
    060            // put the annotations in the right output stream
    061            for (final AnnotationEntryGen a : annotationEntryGens) {
    062                if (a.isRuntimeVisible()) {
    063                    countVisible++;
    064                } else {
    065                    countInvisible++;
    066                }
    067            }
    068
    069            final ByteArrayOutputStream rvaBytes = new ByteArrayOutputStream();
    070            final ByteArrayOutputStream riaBytes = new ByteArrayOutputStream();
    071            try (DataOutputStream rvaDos = new DataOutputStream(rvaBytes); DataOutputStream riaDos = new DataOutputStream(riaBytes)) {
    072
    073                rvaDos.writeShort(countVisible);
    074                riaDos.writeShort(countInvisible);
    075
    076                // put the annotations in the right output stream
    077                for (final AnnotationEntryGen a : annotationEntryGens) {
    078                    if (a.isRuntimeVisible()) {
    079                        a.dump(rvaDos);
    080                    } else {
    081                        a.dump(riaDos);
    082                    }
    083                }
    084            }
    085
    086            final byte[] rvaData = rvaBytes.toByteArray();
    087            final byte[] riaData = riaBytes.toByteArray();
    088
    089            int rvaIndex = -1;
    090            int riaIndex = -1;
    091
    092            if (rvaData.length > 2) {
    093                rvaIndex = cp.addUtf8("RuntimeVisibleAnnotations");
    094            }
    095            if (riaData.length > 2) {
    096                riaIndex = cp.addUtf8("RuntimeInvisibleAnnotations");
    097            }
    098
    099            final List<Attribute> newAttributes = new ArrayList<>();
    100            if (rvaData.length > 2) {
    101                newAttributes
    102                    .add(new RuntimeVisibleAnnotations(rvaIndex, rvaData.length, new DataInputStream(new ByteArrayInputStream(rvaData)), cp.getConstantPool()));
    103            }
    104            if (riaData.length > 2) {
    105                newAttributes.add(
    106                    new RuntimeInvisibleAnnotations(riaIndex, riaData.length, new DataInputStream(new ByteArrayInputStream(riaData)), cp.getConstantPool()));
    107            }
    108
    109            return newAttributes.toArray(Attribute.EMPTY_ARRAY);
    110        } catch (final IOException e) {
    111            System.err.println("IOException whilst processing annotations");
    112            e.printStackTrace();
    113        }
    114        return null;
    115    }
    116
    117    /**
    118     * Annotations against a class are stored in one of four attribute kinds: - RuntimeVisibleParameterAnnotations -
    119     * RuntimeInvisibleParameterAnnotations
    120     */
    121    static Attribute[] getParameterAnnotationAttributes(final ConstantPoolGen cp,
    122        final List<AnnotationEntryGen>[] /* Array of lists, array size depends on #params */ vec) {
    123        final int[] visCount = new int[vec.length];
    124        int totalVisCount = 0;
    125        final int[] invisCount = new int[vec.length];
    126        int totalInvisCount = 0;
    127        try {
    128            for (int i = 0; i < vec.length; i++) {
    129                if (vec[i] != null) {
    130                    for (final AnnotationEntryGen element : vec[i]) {
    131                        if (element.isRuntimeVisible()) {
    132                            visCount[i]++;
    133                            totalVisCount++;
    134                        } else {
    135                            invisCount[i]++;
    136                            totalInvisCount++;
    137                        }
    138                    }
    139                }
    140            }
    141            // Lets do the visible ones
    142            final ByteArrayOutputStream rvaBytes = new ByteArrayOutputStream();
    143            try (DataOutputStream rvaDos = new DataOutputStream(rvaBytes)) {
    144                rvaDos.writeByte(vec.length); // First goes number of parameters
    145                for (int i = 0; i < vec.length; i++) {
    146                    rvaDos.writeShort(visCount[i]);
    147                    if (visCount[i] > 0) {
    148                        for (final AnnotationEntryGen element : vec[i]) {
    149                            if (element.isRuntimeVisible()) {
    150                                element.dump(rvaDos);
    151                            }
    152                        }
    153                    }
    154                }
    155            }
    156            // Lets do the invisible ones
    157            final ByteArrayOutputStream riaBytes = new ByteArrayOutputStream();
    158            try (DataOutputStream riaDos = new DataOutputStream(riaBytes)) {
    159                riaDos.writeByte(vec.length); // First goes number of parameters
    160                for (int i = 0; i < vec.length; i++) {
    161                    riaDos.writeShort(invisCount[i]);
    162                    if (invisCount[i] > 0) {
    163                        for (final AnnotationEntryGen element : vec[i]) {
    164                            if (!element.isRuntimeVisible()) {
    165                                element.dump(riaDos);
    166                            }
    167                        }
    168                    }
    169                }
    170            }
    171            final byte[] rvaData = rvaBytes.toByteArray();
    172            final byte[] riaData = riaBytes.toByteArray();
    173            int rvaIndex = -1;
    174            int riaIndex = -1;
    175            if (totalVisCount > 0) {
    176                rvaIndex = cp.addUtf8("RuntimeVisibleParameterAnnotations");
    177            }
    178            if (totalInvisCount > 0) {
    179                riaIndex = cp.addUtf8("RuntimeInvisibleParameterAnnotations");
    180            }
    181            final List<Attribute> newAttributes = new ArrayList<>();
    182            if (totalVisCount > 0) {
    183                newAttributes.add(new RuntimeVisibleParameterAnnotations(rvaIndex, rvaData.length, new DataInputStream(new ByteArrayInputStream(rvaData)),
    184                    cp.getConstantPool()));
    185            }
    186            if (totalInvisCount > 0) {
    187                newAttributes.add(new RuntimeInvisibleParameterAnnotations(riaIndex, riaData.length, new DataInputStream(new ByteArrayInputStream(riaData)),
    188                    cp.getConstantPool()));
    189            }
    190            return newAttributes.toArray(Attribute.EMPTY_ARRAY);
    191        } catch (final IOException e) {
    192            System.err.println("IOException whilst processing parameter annotations");
    193            e.printStackTrace();
    194        }
    195        return null;
    196    }
    197
    198    public static AnnotationEntryGen read(final DataInput dis, final ConstantPoolGen cpool, final boolean b) throws IOException {
    199        final AnnotationEntryGen a = new AnnotationEntryGen(cpool);
    200        a.typeIndex = dis.readUnsignedShort();
    201        final int elemValuePairCount = dis.readUnsignedShort();
    202        for (int i = 0; i < elemValuePairCount; i++) {
    203            final int nidx = dis.readUnsignedShort();
    204            a.addElementNameValuePair(new ElementValuePairGen(nidx, ElementValueGen.readElementValue(dis, cpool), cpool));
    205        }
    206        a.isRuntimeVisible(b);
    207        return a;
    208    }
    209
    210    private int typeIndex;
    211
    212    private List<ElementValuePairGen> evs;
    213
    214    private final ConstantPoolGen cpool;
    215
    216    private boolean isRuntimeVisible;
    217
    218    /**
    219     * Here we are taking a fixed annotation of type Annotation and building a modifiable AnnotationGen object. If the pool
    220     * passed in is for a different class file, then copyPoolEntries should have been passed as true as that will force us
    221     * to do a deep copy of the annotation and move the cpool entries across. We need to copy the type and the element name
    222     * value pairs and the visibility.
    223     */
    224    public AnnotationEntryGen(final AnnotationEntry a, final ConstantPoolGen cpool, final boolean copyPoolEntries) {
    225        this.cpool = cpool;
    226        if (copyPoolEntries) {
    227            typeIndex = cpool.addUtf8(a.getAnnotationType());
    228        } else {
    229            typeIndex = a.getAnnotationTypeIndex();
    230        }
    231        isRuntimeVisible = a.isRuntimeVisible();
    232        evs = copyValues(a.getElementValuePairs(), cpool, copyPoolEntries);
    233    }
    234
    235    private AnnotationEntryGen(final ConstantPoolGen cpool) {
    236        this.cpool = cpool;
    237    }
    238
    239    public AnnotationEntryGen(final ObjectType type, final List<ElementValuePairGen> elements, final boolean vis, final ConstantPoolGen cpool) {
    240        this.cpool = cpool;
    241        this.typeIndex = cpool.addUtf8(type.getSignature());
    242        evs = elements;
    243        isRuntimeVisible = vis;
    244    }
    245
    246    public void addElementNameValuePair(final ElementValuePairGen evp) {
    247        if (evs == null) {
    248            evs = new ArrayList<>();
    249        }
    250        evs.add(evp);
    251    }
    252
    253    private List<ElementValuePairGen> copyValues(final ElementValuePair[] in, final ConstantPoolGen cpool, final boolean copyPoolEntries) {
    254        final List<ElementValuePairGen> out = new ArrayList<>();
    255        for (final ElementValuePair nvp : in) {
    256            out.add(new ElementValuePairGen(nvp, cpool, copyPoolEntries));
    257        }
    258        return out;
    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    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}