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 * Converts a list of AnnotationGen objects into a set of attributes that 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 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}