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.bcel.util.Args; 040import org.apache.commons.lang3.ArrayUtils; 041import org.apache.commons.lang3.stream.Streams; 042 043/** 044 * Generates annotation entries. 045 * 046 * @since 6.0 047 */ 048public class AnnotationEntryGen { 049 050 static final AnnotationEntryGen[] EMPTY_ARRAY = {}; 051 052 /** 053 * Gets the attributes for an array of AnnotationEntryGen objects so they can be attached to the class file. 054 * 055 * @param cp The constant pool gen where we can create the necessary name refs. 056 * @param annotationEntryGens An array of AnnotationGen objects. 057 */ 058 static Attribute[] getAnnotationAttributes(final ConstantPoolGen cp, final AnnotationEntryGen[] annotationEntryGens) { 059 if (ArrayUtils.isEmpty(annotationEntryGens)) { 060 return Attribute.EMPTY_ARRAY; 061 } 062 063 try { 064 int countVisible = 0; 065 int countInvisible = 0; 066 067 // put the annotations in the right output stream 068 for (final AnnotationEntryGen a : annotationEntryGens) { 069 if (a.isRuntimeVisible()) { 070 countVisible++; 071 } else { 072 countInvisible++; 073 } 074 } 075 076 final ByteArrayOutputStream rvaBytes = new ByteArrayOutputStream(); 077 final ByteArrayOutputStream riaBytes = new ByteArrayOutputStream(); 078 try (DataOutputStream rvaDos = new DataOutputStream(rvaBytes); DataOutputStream riaDos = new DataOutputStream(riaBytes)) { 079 080 rvaDos.writeShort(countVisible); 081 riaDos.writeShort(countInvisible); 082 083 // put the annotations in the right output stream 084 for (final AnnotationEntryGen a : annotationEntryGens) { 085 if (a.isRuntimeVisible()) { 086 a.dump(rvaDos); 087 } else { 088 a.dump(riaDos); 089 } 090 } 091 } 092 093 final byte[] rvaData = rvaBytes.toByteArray(); 094 final byte[] riaData = riaBytes.toByteArray(); 095 096 int rvaIndex = -1; 097 int riaIndex = -1; 098 099 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 while processing annotations"); 119 e.printStackTrace(); 120 } 121 return null; 122 } 123 124 /** 125 * Gets the runtime-visible and runtime-invisible parameter annotation attributes. 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(Args.requireU1(vec.length, "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(Args.requireU1(vec.length, "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 while 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 Thrown 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 Thrown 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(Args.requireU2(evs.size(), "evs.size()")); // u2 element_value pair count 298 for (final ElementValuePairGen envp : evs) { 299 envp.dump(dos); 300 } 301 } 302 303 /** 304 * Gets 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 * Gets the list of ElementNameValuePair objects. 348 * 349 * @return list of ElementNameValuePair objects. 350 */ 351 public List<ElementValuePairGen> getValues() { 352 return evs; 353 } 354 355 /** 356 * Tests 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}