View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.bcel.generic;
20  
21  import java.io.ByteArrayInputStream;
22  import java.io.ByteArrayOutputStream;
23  import java.io.DataInput;
24  import java.io.DataInputStream;
25  import java.io.DataOutputStream;
26  import java.io.IOException;
27  import java.util.ArrayList;
28  import java.util.List;
29  import java.util.stream.Collectors;
30  
31  import org.apache.bcel.classfile.AnnotationEntry;
32  import org.apache.bcel.classfile.Attribute;
33  import org.apache.bcel.classfile.ConstantUtf8;
34  import org.apache.bcel.classfile.ElementValuePair;
35  import org.apache.bcel.classfile.RuntimeInvisibleAnnotations;
36  import org.apache.bcel.classfile.RuntimeInvisibleParameterAnnotations;
37  import org.apache.bcel.classfile.RuntimeVisibleAnnotations;
38  import org.apache.bcel.classfile.RuntimeVisibleParameterAnnotations;
39  import org.apache.commons.lang3.ArrayUtils;
40  import org.apache.commons.lang3.stream.Streams;
41  
42  /**
43   * @since 6.0
44   */
45  public class AnnotationEntryGen {
46  
47      static final AnnotationEntryGen[] EMPTY_ARRAY = {};
48  
49      /**
50       * Converts a list of AnnotationGen objects into a set of attributes that can be attached to the class file.
51       *
52       * @param cp The constant pool gen where we can create the necessary name refs
53       * @param annotationEntryGens An array of AnnotationGen objects
54       */
55      static Attribute[] getAnnotationAttributes(final ConstantPoolGen cp, final AnnotationEntryGen[] annotationEntryGens) {
56          if (ArrayUtils.isEmpty(annotationEntryGens)) {
57              return Attribute.EMPTY_ARRAY;
58          }
59  
60          try {
61              int countVisible = 0;
62              int countInvisible = 0;
63  
64              // put the annotations in the right output stream
65              for (final AnnotationEntryGen a : annotationEntryGens) {
66                  if (a.isRuntimeVisible()) {
67                      countVisible++;
68                  } else {
69                      countInvisible++;
70                  }
71              }
72  
73              final ByteArrayOutputStream rvaBytes = new ByteArrayOutputStream();
74              final ByteArrayOutputStream riaBytes = new ByteArrayOutputStream();
75              try (DataOutputStream rvaDos = new DataOutputStream(rvaBytes); DataOutputStream riaDos = new DataOutputStream(riaBytes)) {
76  
77                  rvaDos.writeShort(countVisible);
78                  riaDos.writeShort(countInvisible);
79  
80                  // put the annotations in the right output stream
81                  for (final AnnotationEntryGen a : annotationEntryGens) {
82                      if (a.isRuntimeVisible()) {
83                          a.dump(rvaDos);
84                      } else {
85                          a.dump(riaDos);
86                      }
87                  }
88              }
89  
90              final byte[] rvaData = rvaBytes.toByteArray();
91              final byte[] riaData = riaBytes.toByteArray();
92  
93              int rvaIndex = -1;
94              int riaIndex = -1;
95  
96              if (rvaData.length > 2) {
97                  rvaIndex = cp.addUtf8("RuntimeVisibleAnnotations");
98              }
99              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 }