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   * Generates annotation entries.
44   *
45   * @since 6.0
46   */
47  public class AnnotationEntryGen {
48  
49      static final AnnotationEntryGen[] EMPTY_ARRAY = {};
50  
51      /**
52       * Converts a list of AnnotationGen objects into a set of attributes that can be attached to the class file.
53       *
54       * @param cp The constant pool gen where we can create the necessary name refs.
55       * @param annotationEntryGens An array of AnnotationGen objects.
56       */
57      static Attribute[] getAnnotationAttributes(final ConstantPoolGen cp, final AnnotationEntryGen[] annotationEntryGens) {
58          if (ArrayUtils.isEmpty(annotationEntryGens)) {
59              return Attribute.EMPTY_ARRAY;
60          }
61  
62          try {
63              int countVisible = 0;
64              int countInvisible = 0;
65  
66              // put the annotations in the right output stream
67              for (final AnnotationEntryGen a : annotationEntryGens) {
68                  if (a.isRuntimeVisible()) {
69                      countVisible++;
70                  } else {
71                      countInvisible++;
72                  }
73              }
74  
75              final ByteArrayOutputStream rvaBytes = new ByteArrayOutputStream();
76              final ByteArrayOutputStream riaBytes = new ByteArrayOutputStream();
77              try (DataOutputStream rvaDos = new DataOutputStream(rvaBytes); DataOutputStream riaDos = new DataOutputStream(riaBytes)) {
78  
79                  rvaDos.writeShort(countVisible);
80                  riaDos.writeShort(countInvisible);
81  
82                  // put the annotations in the right output stream
83                  for (final AnnotationEntryGen a : annotationEntryGens) {
84                      if (a.isRuntimeVisible()) {
85                          a.dump(rvaDos);
86                      } else {
87                          a.dump(riaDos);
88                      }
89                  }
90              }
91  
92              final byte[] rvaData = rvaBytes.toByteArray();
93              final byte[] riaData = riaBytes.toByteArray();
94  
95              int rvaIndex = -1;
96              int riaIndex = -1;
97  
98              if (rvaData.length > 2) {
99                  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 }