View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.bcel.generic;
20  
21  import java.util.ArrayList;
22  import java.util.List;
23  import java.util.Objects;
24  import java.util.stream.Stream;
25  
26  import org.apache.bcel.Const;
27  import org.apache.bcel.classfile.Annotations;
28  import org.apache.bcel.classfile.Attribute;
29  import org.apache.bcel.classfile.Constant;
30  import org.apache.bcel.classfile.ConstantObject;
31  import org.apache.bcel.classfile.ConstantPool;
32  import org.apache.bcel.classfile.ConstantValue;
33  import org.apache.bcel.classfile.Field;
34  import org.apache.bcel.classfile.Utility;
35  import org.apache.bcel.util.BCELComparator;
36  
37  /**
38   * Template class for building up a field. The only extraordinary thing one can do is to add a constant value attribute
39   * to a field (which must of course be compatible with to the declared type).
40   *
41   * @see Field
42   */
43  public class FieldGen extends FieldGenOrMethodGen {
44  
45      private static BCELComparator<FieldGen> bcelComparator = new BCELComparator<FieldGen>() {
46  
47          @Override
48          public boolean equals(final FieldGen a, final FieldGen b) {
49              return a == b || a != null && b != null && Objects.equals(a.getName(), b.getName()) && Objects.equals(a.getSignature(), b.getSignature());
50          }
51  
52          @Override
53          public int hashCode(final FieldGen o) {
54              return o != null ? Objects.hash(o.getSignature(), o.getName()) : 0;
55          }
56      };
57  
58      /**
59       * Gets the comparison strategy object.
60       *
61       * @return Comparison strategy object.
62       */
63      public static BCELComparator<FieldGen> getComparator() {
64          return bcelComparator;
65      }
66  
67      /**
68       * Sets the comparison strategy object.
69       *
70       * @param comparator Comparison strategy object.
71       */
72      public static void setComparator(final BCELComparator<FieldGen> comparator) {
73          bcelComparator = comparator;
74      }
75  
76      private Object value;
77  
78      private List<FieldObserver> observers;
79  
80      /**
81       * Instantiate from existing field.
82       *
83       * @param field Field object.
84       * @param cp constant pool (must contain the same entries as the field's constant pool).
85       */
86      public FieldGen(final Field field, final ConstantPoolGen cp) {
87          this(field.getAccessFlags(), Type.getType(field.getSignature()), field.getName(), cp);
88          final Attribute[] attrs = field.getAttributes();
89          for (final Attribute attr : attrs) {
90              if (attr instanceof ConstantValue) {
91                  setValue(((ConstantValue) attr).getConstantValueIndex());
92              } else if (attr instanceof Annotations) {
93                  final Annotations runtimeAnnotations = (Annotations) attr;
94                  runtimeAnnotations.forEach(element -> addAnnotationEntry(new AnnotationEntryGen(element, cp, false)));
95              } else {
96                  addAttribute(attr);
97              }
98          }
99      }
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 }