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