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