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