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.util.ArrayList; 022import java.util.List; 023import java.util.Objects; 024import java.util.stream.Stream; 025 026import org.apache.bcel.Const; 027import org.apache.bcel.classfile.Annotations; 028import org.apache.bcel.classfile.Attribute; 029import org.apache.bcel.classfile.Constant; 030import org.apache.bcel.classfile.ConstantObject; 031import org.apache.bcel.classfile.ConstantPool; 032import org.apache.bcel.classfile.ConstantValue; 033import org.apache.bcel.classfile.Field; 034import org.apache.bcel.classfile.Utility; 035import org.apache.bcel.util.BCELComparator; 036 037/** 038 * Template class for building up a field. The only extraordinary thing one can do is to add a constant value attribute 039 * to a field (which must of course be compatible with to the declared type). 040 * 041 * @see Field 042 */ 043public class FieldGen extends FieldGenOrMethodGen { 044 045 private static BCELComparator<FieldGen> bcelComparator = new BCELComparator<FieldGen>() { 046 047 @Override 048 public boolean equals(final FieldGen a, final FieldGen b) { 049 return a == b || a != null && b != null && Objects.equals(a.getName(), b.getName()) && Objects.equals(a.getSignature(), b.getSignature()); 050 } 051 052 @Override 053 public int hashCode(final FieldGen o) { 054 return o != null ? Objects.hash(o.getSignature(), o.getName()) : 0; 055 } 056 }; 057 058 /** 059 * Gets the comparison strategy object. 060 * 061 * @return Comparison strategy object. 062 */ 063 public static BCELComparator<FieldGen> getComparator() { 064 return bcelComparator; 065 } 066 067 /** 068 * Sets the comparison strategy object. 069 * 070 * @param comparator Comparison strategy object. 071 */ 072 public static void setComparator(final BCELComparator<FieldGen> comparator) { 073 bcelComparator = comparator; 074 } 075 076 private Object value; 077 078 private List<FieldObserver> observers; 079 080 /** 081 * Instantiate from existing field. 082 * 083 * @param field Field object. 084 * @param cp constant pool (must contain the same entries as the field's constant pool). 085 */ 086 public FieldGen(final Field field, final ConstantPoolGen cp) { 087 this(field.getAccessFlags(), Type.getType(field.getSignature()), field.getName(), cp); 088 final Attribute[] attrs = field.getAttributes(); 089 for (final Attribute attr : attrs) { 090 if (attr instanceof ConstantValue) { 091 setValue(((ConstantValue) attr).getConstantValueIndex()); 092 } else if (attr instanceof Annotations) { 093 final Annotations runtimeAnnotations = (Annotations) attr; 094 runtimeAnnotations.forEach(element -> addAnnotationEntry(new AnnotationEntryGen(element, cp, false))); 095 } else { 096 addAttribute(attr); 097 } 098 } 099 } 100 101 /** 102 * Declare a field. If it is static (isStatic() == true) and has a basic type like int or String it may have an initial 103 * value associated with it as defined by setInitValue(). 104 * 105 * @param accessFlags access qualifiers. 106 * @param type field type. 107 * @param name field name. 108 * @param cp constant pool. 109 */ 110 public FieldGen(final int accessFlags, final Type type, final String name, final ConstantPoolGen cp) { 111 super(accessFlags); 112 setType(type); 113 setName(name); 114 setConstantPool(cp); 115 } 116 117 private void addAnnotationsAsAttribute(final ConstantPoolGen cp) { 118 Stream.of(AnnotationEntryGen.getAnnotationAttributes(cp, super.getAnnotationEntries())).forEach(this::addAttribute); 119 } 120 121 private int addConstant() { 122 switch (super.getType().getType()) { // sic 123 case Const.T_INT: 124 case Const.T_CHAR: 125 case Const.T_BYTE: 126 case Const.T_BOOLEAN: 127 case Const.T_SHORT: 128 return super.getConstantPool().addInteger(((Integer) value).intValue()); 129 case Const.T_FLOAT: 130 return super.getConstantPool().addFloat(((Float) value).floatValue()); 131 case Const.T_DOUBLE: 132 return super.getConstantPool().addDouble(((Double) value).doubleValue()); 133 case Const.T_LONG: 134 return super.getConstantPool().addLong(((Long) value).longValue()); 135 case Const.T_REFERENCE: 136 return super.getConstantPool().addString((String) value); 137 default: 138 throw new IllegalStateException("Unhandled : " + super.getType().getType()); // sic 139 } 140 } 141 142 /** 143 * Add observer for this object. 144 * 145 * @param o the observer to add. 146 */ 147 public void addObserver(final FieldObserver o) { 148 if (observers == null) { 149 observers = new ArrayList<>(); 150 } 151 observers.add(o); 152 } 153 154 /** 155 * Remove any initial value. 156 */ 157 public void cancelInitValue() { 158 value = null; 159 } 160 161 private void checkType(final Type atype) { 162 final Type superType = super.getType(); 163 if (superType == null) { 164 throw new ClassGenException("You haven't defined the type of the field yet"); 165 } 166 if (!isFinal()) { 167 throw new ClassGenException("Only final fields may have an initial value!"); 168 } 169 if (!superType.equals(atype)) { 170 throw new ClassGenException("Types are not compatible: " + superType + " vs. " + atype); 171 } 172 } 173 174 /** 175 * Creates a deep copy of this field. 176 * 177 * @param cp the constant pool. 178 * @return deep copy of this field. 179 */ 180 public FieldGen copy(final ConstantPoolGen cp) { 181 final FieldGen fg = (FieldGen) clone(); 182 fg.setConstantPool(cp); 183 return fg; 184 } 185 186 /** 187 * Return value as defined by given BCELComparator strategy. By default two FieldGen objects are said to be equal when 188 * their names and signatures are equal. 189 * 190 * @see Object#equals(Object) 191 */ 192 @Override 193 public boolean equals(final Object obj) { 194 return obj instanceof FieldGen && bcelComparator.equals(this, (FieldGen) obj); 195 } 196 197 /** 198 * Gets field object after having set up all necessary values. 199 * 200 * @return the field object. 201 */ 202 public Field getField() { 203 final String signature = getSignature(); 204 final int nameIndex = super.getConstantPool().addUtf8(super.getName()); 205 final int signatureIndex = super.getConstantPool().addUtf8(signature); 206 if (value != null) { 207 checkType(super.getType()); 208 final int index = addConstant(); 209 addAttribute(new ConstantValue(super.getConstantPool().addUtf8("ConstantValue"), 2, index, super.getConstantPool().getConstantPool())); // sic 210 } 211 addAnnotationsAsAttribute(super.getConstantPool()); 212 return new Field(super.getAccessFlags(), nameIndex, signatureIndex, getAttributes(), super.getConstantPool().getConstantPool()); // sic 213 } 214 215 /** 216 * Gets the initial value. 217 * 218 * @return the initial value. 219 */ 220 public String getInitValue() { 221 return Objects.toString(value, null); 222 } 223 224 @Override 225 public String getSignature() { 226 return super.getType().getSignature(); 227 } 228 229 /** 230 * Return value as defined by given BCELComparator strategy. By default return the hash code of the field's name XOR 231 * signature. 232 * 233 * @see Object#hashCode() 234 */ 235 @Override 236 public int hashCode() { 237 return bcelComparator.hashCode(this); 238 } 239 240 /** 241 * Remove observer for this object. 242 * 243 * @param o the observer to remove. 244 */ 245 public void removeObserver(final FieldObserver o) { 246 if (observers != null) { 247 observers.remove(o); 248 } 249 } 250 251 /** 252 * Sets the initial value. 253 * 254 * @param b the boolean value. 255 */ 256 public void setInitValue(final boolean b) { 257 checkType(Type.BOOLEAN); 258 if (b) { 259 value = Integer.valueOf(1); 260 } 261 } 262 263 /** 264 * Sets the initial value. 265 * 266 * @param b the byte value. 267 */ 268 public void setInitValue(final byte b) { 269 checkType(Type.BYTE); 270 if (b != 0) { 271 value = Integer.valueOf(b); 272 } 273 } 274 275 /** 276 * Sets the initial value. 277 * 278 * @param c the char value. 279 */ 280 public void setInitValue(final char c) { 281 checkType(Type.CHAR); 282 if (c != 0) { 283 value = Integer.valueOf(c); 284 } 285 } 286 287 /** 288 * Sets the initial value. 289 * 290 * @param d the double value. 291 */ 292 public void setInitValue(final double d) { 293 checkType(Type.DOUBLE); 294 if (d != 0.0) { 295 value = Double.valueOf(d); 296 } 297 } 298 299 /** 300 * Sets the initial value. 301 * 302 * @param f the float value. 303 */ 304 public void setInitValue(final float f) { 305 checkType(Type.FLOAT); 306 if (f != 0.0) { 307 value = Float.valueOf(f); 308 } 309 } 310 311 /** 312 * Sets the initial value. 313 * 314 * @param i the int value. 315 */ 316 public void setInitValue(final int i) { 317 checkType(Type.INT); 318 if (i != 0) { 319 value = Integer.valueOf(i); 320 } 321 } 322 323 /** 324 * Sets the initial value. 325 * 326 * @param l the long value. 327 */ 328 public void setInitValue(final long l) { 329 checkType(Type.LONG); 330 if (l != 0L) { 331 value = Long.valueOf(l); 332 } 333 } 334 335 /** 336 * Sets the initial value. 337 * 338 * @param s the short value. 339 */ 340 public void setInitValue(final short s) { 341 checkType(Type.SHORT); 342 if (s != 0) { 343 value = Integer.valueOf(s); 344 } 345 } 346 347 /** 348 * Sets (optional) initial value of field, otherwise it will be set to null/0/false by the JVM automatically. 349 * 350 * @param str the string value. 351 */ 352 public void setInitValue(final String str) { 353 checkType(ObjectType.getInstance("java.lang.String")); 354 if (str != null) { 355 value = str; 356 } 357 } 358 359 private void setValue(final int index) { 360 final ConstantPool cp = super.getConstantPool().getConstantPool(); 361 final Constant c = cp.getConstant(index); 362 value = ((ConstantObject) c).getConstantValue(cp); 363 } 364 365 /** 366 * Return string representation close to declaration format, for example: 'public static final short MAX = 100'. 367 * 368 * @return String representation of field. 369 */ 370 @Override 371 public final String toString() { 372 // Short cuts to constant pool 373 String access = Utility.accessToString(super.getAccessFlags()); 374 access = access.isEmpty() ? "" : access + " "; 375 final String signature = super.getType().toString(); 376 final String name = getName(); 377 final StringBuilder buf = new StringBuilder(32); // CHECKSTYLE IGNORE MagicNumber 378 buf.append(access).append(signature).append(" ").append(name); 379 final String value = getInitValue(); 380 if (value != null) { 381 buf.append(" = ").append(value); 382 } 383 return buf.toString(); 384 } 385 386 /** 387 * Call notify() method on all observers. This method is not called automatically whenever the state has changed, but 388 * has to be called by the user after they have finished editing the object. 389 */ 390 public void update() { 391 if (observers != null) { 392 for (final FieldObserver observer : observers) { 393 observer.notify(this); 394 } 395 } 396 } 397}