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 * @since 6.0 044 */ 045public class AnnotationEntryGen { 046 047 static final AnnotationEntryGen[] EMPTY_ARRAY = {}; 048 049 /** 050 * Converts a list of AnnotationGen objects into a set of attributes that can be attached to the class file. 051 * 052 * @param cp The constant pool gen where we can create the necessary name refs 053 * @param annotationEntryGens An array of AnnotationGen objects 054 */ 055 static Attribute[] getAnnotationAttributes(final ConstantPoolGen cp, final AnnotationEntryGen[] annotationEntryGens) { 056 if (ArrayUtils.isEmpty(annotationEntryGens)) { 057 return Attribute.EMPTY_ARRAY; 058 } 059 060 try { 061 int countVisible = 0; 062 int countInvisible = 0; 063 064 // put the annotations in the right output stream 065 for (final AnnotationEntryGen a : annotationEntryGens) { 066 if (a.isRuntimeVisible()) { 067 countVisible++; 068 } else { 069 countInvisible++; 070 } 071 } 072 073 final ByteArrayOutputStream rvaBytes = new ByteArrayOutputStream(); 074 final ByteArrayOutputStream riaBytes = new ByteArrayOutputStream(); 075 try (DataOutputStream rvaDos = new DataOutputStream(rvaBytes); DataOutputStream riaDos = new DataOutputStream(riaBytes)) { 076 077 rvaDos.writeShort(countVisible); 078 riaDos.writeShort(countInvisible); 079 080 // put the annotations in the right output stream 081 for (final AnnotationEntryGen a : annotationEntryGens) { 082 if (a.isRuntimeVisible()) { 083 a.dump(rvaDos); 084 } else { 085 a.dump(riaDos); 086 } 087 } 088 } 089 090 final byte[] rvaData = rvaBytes.toByteArray(); 091 final byte[] riaData = riaBytes.toByteArray(); 092 093 int rvaIndex = -1; 094 int riaIndex = -1; 095 096 if (rvaData.length > 2) { 097 rvaIndex = cp.addUtf8("RuntimeVisibleAnnotations"); 098 } 099 if (riaData.length > 2) { 100 riaIndex = cp.addUtf8("RuntimeInvisibleAnnotations"); 101 } 102 103 final List<Attribute> newAttributes = new ArrayList<>(); 104 if (rvaData.length > 2) { 105 newAttributes 106 .add(new RuntimeVisibleAnnotations(rvaIndex, rvaData.length, new DataInputStream(new ByteArrayInputStream(rvaData)), cp.getConstantPool())); 107 } 108 if (riaData.length > 2) { 109 newAttributes.add( 110 new RuntimeInvisibleAnnotations(riaIndex, riaData.length, new DataInputStream(new ByteArrayInputStream(riaData)), cp.getConstantPool())); 111 } 112 113 return newAttributes.toArray(Attribute.EMPTY_ARRAY); 114 } catch (final IOException e) { 115 System.err.println("IOException whilst processing annotations"); 116 e.printStackTrace(); 117 } 118 return null; 119 } 120 121 /** 122 * Annotations against a class are stored in one of four attribute kinds: - RuntimeVisibleParameterAnnotations - 123 * RuntimeInvisibleParameterAnnotations 124 */ 125 static Attribute[] getParameterAnnotationAttributes(final ConstantPoolGen cp, 126 final List<AnnotationEntryGen>[] /* Array of lists, array size depends on #params */ vec) { 127 final int[] visCount = new int[vec.length]; 128 int totalVisCount = 0; 129 final int[] invisCount = new int[vec.length]; 130 int totalInvisCount = 0; 131 try { 132 for (int i = 0; i < vec.length; i++) { 133 if (vec[i] != null) { 134 for (final AnnotationEntryGen element : vec[i]) { 135 if (element.isRuntimeVisible()) { 136 visCount[i]++; 137 totalVisCount++; 138 } else { 139 invisCount[i]++; 140 totalInvisCount++; 141 } 142 } 143 } 144 } 145 // Lets do the visible ones 146 final ByteArrayOutputStream rvaBytes = new ByteArrayOutputStream(); 147 try (DataOutputStream rvaDos = new DataOutputStream(rvaBytes)) { 148 rvaDos.writeByte(vec.length); // First goes number of parameters 149 for (int i = 0; i < vec.length; i++) { 150 rvaDos.writeShort(visCount[i]); 151 if (visCount[i] > 0) { 152 for (final AnnotationEntryGen element : vec[i]) { 153 if (element.isRuntimeVisible()) { 154 element.dump(rvaDos); 155 } 156 } 157 } 158 } 159 } 160 // Lets do the invisible ones 161 final ByteArrayOutputStream riaBytes = new ByteArrayOutputStream(); 162 try (DataOutputStream riaDos = new DataOutputStream(riaBytes)) { 163 riaDos.writeByte(vec.length); // First goes number of parameters 164 for (int i = 0; i < vec.length; i++) { 165 riaDos.writeShort(invisCount[i]); 166 if (invisCount[i] > 0) { 167 for (final AnnotationEntryGen element : vec[i]) { 168 if (!element.isRuntimeVisible()) { 169 element.dump(riaDos); 170 } 171 } 172 } 173 } 174 } 175 final byte[] rvaData = rvaBytes.toByteArray(); 176 final byte[] riaData = riaBytes.toByteArray(); 177 int rvaIndex = -1; 178 int riaIndex = -1; 179 if (totalVisCount > 0) { 180 rvaIndex = cp.addUtf8("RuntimeVisibleParameterAnnotations"); 181 } 182 if (totalInvisCount > 0) { 183 riaIndex = cp.addUtf8("RuntimeInvisibleParameterAnnotations"); 184 } 185 final List<Attribute> newAttributes = new ArrayList<>(); 186 if (totalVisCount > 0) { 187 newAttributes.add(new RuntimeVisibleParameterAnnotations(rvaIndex, rvaData.length, new DataInputStream(new ByteArrayInputStream(rvaData)), 188 cp.getConstantPool())); 189 } 190 if (totalInvisCount > 0) { 191 newAttributes.add(new RuntimeInvisibleParameterAnnotations(riaIndex, riaData.length, new DataInputStream(new ByteArrayInputStream(riaData)), 192 cp.getConstantPool())); 193 } 194 return newAttributes.toArray(Attribute.EMPTY_ARRAY); 195 } catch (final IOException e) { 196 System.err.println("IOException whilst processing parameter annotations"); 197 e.printStackTrace(); 198 } 199 return null; 200 } 201 202 public static AnnotationEntryGen read(final DataInput dis, final ConstantPoolGen cpool, final boolean b) throws IOException { 203 final AnnotationEntryGen a = new AnnotationEntryGen(cpool); 204 a.typeIndex = dis.readUnsignedShort(); 205 final int elemValuePairCount = dis.readUnsignedShort(); 206 for (int i = 0; i < elemValuePairCount; i++) { 207 final int nidx = dis.readUnsignedShort(); 208 a.addElementNameValuePair(new ElementValuePairGen(nidx, ElementValueGen.readElementValue(dis, cpool), cpool)); 209 } 210 a.isRuntimeVisible(b); 211 return a; 212 } 213 214 private int typeIndex; 215 216 private List<ElementValuePairGen> evs; 217 218 private final ConstantPoolGen cpool; 219 220 private boolean isRuntimeVisible; 221 222 /** 223 * Here we are taking a fixed annotation of type Annotation and building a modifiable AnnotationGen object. If the pool 224 * passed in is for a different class file, then copyPoolEntries should have been passed as true as that will force us 225 * to do a deep copy of the annotation and move the cpool entries across. We need to copy the type and the element name 226 * value pairs and the visibility. 227 */ 228 public AnnotationEntryGen(final AnnotationEntry a, final ConstantPoolGen cpool, final boolean copyPoolEntries) { 229 this.cpool = cpool; 230 if (copyPoolEntries) { 231 typeIndex = cpool.addUtf8(a.getAnnotationType()); 232 } else { 233 typeIndex = a.getAnnotationTypeIndex(); 234 } 235 isRuntimeVisible = a.isRuntimeVisible(); 236 evs = copyValues(a.getElementValuePairs(), cpool, copyPoolEntries); 237 } 238 239 private AnnotationEntryGen(final ConstantPoolGen cpool) { 240 this.cpool = cpool; 241 } 242 243 public AnnotationEntryGen(final ObjectType type, final List<ElementValuePairGen> elements, final boolean vis, final ConstantPoolGen cpool) { 244 this.cpool = cpool; 245 this.typeIndex = cpool.addUtf8(type.getSignature()); 246 evs = elements; 247 isRuntimeVisible = vis; 248 } 249 250 public void addElementNameValuePair(final ElementValuePairGen evp) { 251 if (evs == null) { 252 evs = new ArrayList<>(); 253 } 254 evs.add(evp); 255 } 256 257 private List<ElementValuePairGen> copyValues(final ElementValuePair[] in, final ConstantPoolGen cpool, final boolean copyPoolEntries) { 258 return Streams.of(in).map(nvp -> new ElementValuePairGen(nvp, cpool, copyPoolEntries)).collect(Collectors.toList()); 259 } 260 261 public void dump(final DataOutputStream dos) throws IOException { 262 dos.writeShort(typeIndex); // u2 index of type name in cpool 263 dos.writeShort(evs.size()); // u2 element_value pair count 264 for (final ElementValuePairGen envp : evs) { 265 envp.dump(dos); 266 } 267 } 268 269 /** 270 * Retrieve an immutable version of this AnnotationGen 271 */ 272 public AnnotationEntry getAnnotation() { 273 final AnnotationEntry a = new AnnotationEntry(typeIndex, cpool.getConstantPool(), isRuntimeVisible); 274 for (final ElementValuePairGen element : evs) { 275 a.addElementNameValuePair(element.getElementNameValuePair()); 276 } 277 return a; 278 } 279 280 public int getTypeIndex() { 281 return typeIndex; 282 } 283 284 public final String getTypeName() { 285 return getTypeSignature(); // BCELBUG: Should I use this instead? 286 // Utility.signatureToString(getTypeSignature()); 287 } 288 289 public final String getTypeSignature() { 290 // ConstantClass c = (ConstantClass) cpool.getConstant(typeIndex); 291 final ConstantUtf8 utf8 = (ConstantUtf8) cpool.getConstant(typeIndex/* c.getNameIndex() */); 292 return utf8.getBytes(); 293 } 294 295 /** 296 * Returns list of ElementNameValuePair objects. 297 * 298 * @return list of ElementNameValuePair objects. 299 */ 300 public List<ElementValuePairGen> getValues() { 301 return evs; 302 } 303 304 public boolean isRuntimeVisible() { 305 return isRuntimeVisible; 306 } 307 308 private void isRuntimeVisible(final boolean b) { 309 isRuntimeVisible = b; 310 } 311 312 public String toShortString() { 313 final StringBuilder s = new StringBuilder(); 314 s.append("@").append(getTypeName()).append("("); 315 for (int i = 0; i < evs.size(); i++) { 316 s.append(evs.get(i)); 317 if (i + 1 < evs.size()) { 318 s.append(","); 319 } 320 } 321 s.append(")"); 322 return s.toString(); 323 } 324 325 @Override 326 public String toString() { 327 final StringBuilder s = new StringBuilder(32); // CHECKSTYLE IGNORE MagicNumber 328 s.append("AnnotationGen:[").append(getTypeName()).append(" #").append(evs.size()).append(" {"); 329 for (int i = 0; i < evs.size(); i++) { 330 s.append(evs.get(i)); 331 if (i + 1 < evs.size()) { 332 s.append(","); 333 } 334 } 335 s.append("}]"); 336 return s.toString(); 337 } 338 339}