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