001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * https://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.bcel.generic; 020 021import java.io.ByteArrayInputStream; 022import java.io.ByteArrayOutputStream; 023import java.io.DataInput; 024import java.io.DataInputStream; 025import java.io.DataOutputStream; 026import java.io.IOException; 027import java.util.ArrayList; 028import java.util.List; 029import java.util.stream.Collectors; 030 031import org.apache.bcel.classfile.AnnotationEntry; 032import org.apache.bcel.classfile.Attribute; 033import org.apache.bcel.classfile.ConstantUtf8; 034import org.apache.bcel.classfile.ElementValuePair; 035import org.apache.bcel.classfile.RuntimeInvisibleAnnotations; 036import org.apache.bcel.classfile.RuntimeInvisibleParameterAnnotations; 037import org.apache.bcel.classfile.RuntimeVisibleAnnotations; 038import org.apache.bcel.classfile.RuntimeVisibleParameterAnnotations; 039import org.apache.commons.lang3.ArrayUtils; 040import org.apache.commons.lang3.stream.Streams; 041 042/** 043 * Generates annotation entries. 044 * 045 * @since 6.0 046 */ 047public class AnnotationEntryGen { 048 049 static final AnnotationEntryGen[] EMPTY_ARRAY = {}; 050 051 /** 052 * Converts a list of AnnotationGen objects into a set of attributes that can be attached to the class file. 053 * 054 * @param cp The constant pool gen where we can create the necessary name refs. 055 * @param annotationEntryGens An array of AnnotationGen objects. 056 */ 057 static Attribute[] getAnnotationAttributes(final ConstantPoolGen cp, final AnnotationEntryGen[] annotationEntryGens) { 058 if (ArrayUtils.isEmpty(annotationEntryGens)) { 059 return Attribute.EMPTY_ARRAY; 060 } 061 062 try { 063 int countVisible = 0; 064 int countInvisible = 0; 065 066 // put the annotations in the right output stream 067 for (final AnnotationEntryGen a : annotationEntryGens) { 068 if (a.isRuntimeVisible()) { 069 countVisible++; 070 } else { 071 countInvisible++; 072 } 073 } 074 075 final ByteArrayOutputStream rvaBytes = new ByteArrayOutputStream(); 076 final ByteArrayOutputStream riaBytes = new ByteArrayOutputStream(); 077 try (DataOutputStream rvaDos = new DataOutputStream(rvaBytes); DataOutputStream riaDos = new DataOutputStream(riaBytes)) { 078 079 rvaDos.writeShort(countVisible); 080 riaDos.writeShort(countInvisible); 081 082 // put the annotations in the right output stream 083 for (final AnnotationEntryGen a : annotationEntryGens) { 084 if (a.isRuntimeVisible()) { 085 a.dump(rvaDos); 086 } else { 087 a.dump(riaDos); 088 } 089 } 090 } 091 092 final byte[] rvaData = rvaBytes.toByteArray(); 093 final byte[] riaData = riaBytes.toByteArray(); 094 095 int rvaIndex = -1; 096 int riaIndex = -1; 097 098 if (rvaData.length > 2) { 099 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}