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