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}