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