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