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