View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  package org.apache.bcel.generic;
18  
19  import java.util.ArrayList;
20  import java.util.List;
21  import java.util.Objects;
22  import java.util.stream.Stream;
23  
24  import org.apache.bcel.Const;
25  import org.apache.bcel.classfile.Annotations;
26  import org.apache.bcel.classfile.Attribute;
27  import org.apache.bcel.classfile.Constant;
28  import org.apache.bcel.classfile.ConstantObject;
29  import org.apache.bcel.classfile.ConstantPool;
30  import org.apache.bcel.classfile.ConstantValue;
31  import org.apache.bcel.classfile.Field;
32  import org.apache.bcel.classfile.Utility;
33  import org.apache.bcel.util.BCELComparator;
34  
35  /**
36   * Template class for building up a field. The only extraordinary thing one can do is to add a constant value attribute
37   * to a field (which must of course be compatible with to the declared type).
38   *
39   * @see Field
40   */
41  public class FieldGen extends FieldGenOrMethodGen {
42  
43      private static BCELComparator<FieldGen> bcelComparator = new BCELComparator<FieldGen>() {
44  
45          @Override
46          public boolean equals(final FieldGen a, final FieldGen b) {
47              return a == b || a != null && b != null && Objects.equals(a.getName(), b.getName()) && Objects.equals(a.getSignature(), b.getSignature());
48          }
49  
50          @Override
51          public int hashCode(final FieldGen o) {
52              return o != null ? Objects.hash(o.getSignature(), o.getName()) : 0;
53          }
54      };
55  
56      /**
57       * @return Comparison strategy object.
58       */
59      public static BCELComparator<FieldGen> getComparator() {
60          return bcelComparator;
61      }
62  
63      /**
64       * @param comparator Comparison strategy object.
65       */
66      public static void setComparator(final BCELComparator<FieldGen> comparator) {
67          bcelComparator = comparator;
68      }
69  
70      private Object value;
71  
72      private List<FieldObserver> observers;
73  
74      /**
75       * Instantiate from existing field.
76       *
77       * @param field Field object.
78       * @param cp constant pool (must contain the same entries as the field's constant pool).
79       */
80      public FieldGen(final Field field, final ConstantPoolGen cp) {
81          this(field.getAccessFlags(), Type.getType(field.getSignature()), field.getName(), cp);
82          final Attribute[] attrs = field.getAttributes();
83          for (final Attribute attr : attrs) {
84              if (attr instanceof ConstantValue) {
85                  setValue(((ConstantValue) attr).getConstantValueIndex());
86              } else if (attr instanceof Annotations) {
87                  final Annotations runtimeAnnotations = (Annotations) attr;
88                  runtimeAnnotations.forEach(element -> addAnnotationEntry(new AnnotationEntryGen(element, cp, false)));
89              } else {
90                  addAttribute(attr);
91              }
92          }
93      }
94  
95      /**
96       * Declare a field. If it is static (isStatic() == true) and has a basic type like int or String it may have an initial
97       * value associated with it as defined by setInitValue().
98       *
99       * @param accessFlags access qualifiers
100      * @param type field type
101      * @param name field name
102      * @param cp constant pool
103      */
104     public FieldGen(final int accessFlags, final Type type, final String name, final ConstantPoolGen cp) {
105         super(accessFlags);
106         setType(type);
107         setName(name);
108         setConstantPool(cp);
109     }
110 
111     private void addAnnotationsAsAttribute(final ConstantPoolGen cp) {
112         Stream.of(AnnotationEntryGen.getAnnotationAttributes(cp, super.getAnnotationEntries())).forEach(this::addAttribute);
113     }
114 
115     private int addConstant() {
116         switch (super.getType().getType()) { // sic
117         case Const.T_INT:
118         case Const.T_CHAR:
119         case Const.T_BYTE:
120         case Const.T_BOOLEAN:
121         case Const.T_SHORT:
122             return super.getConstantPool().addInteger(((Integer) value).intValue());
123         case Const.T_FLOAT:
124             return super.getConstantPool().addFloat(((Float) value).floatValue());
125         case Const.T_DOUBLE:
126             return super.getConstantPool().addDouble(((Double) value).doubleValue());
127         case Const.T_LONG:
128             return super.getConstantPool().addLong(((Long) value).longValue());
129         case Const.T_REFERENCE:
130             return super.getConstantPool().addString((String) value);
131         default:
132             throw new IllegalStateException("Unhandled : " + super.getType().getType()); // sic
133         }
134     }
135 
136     /**
137      * Add observer for this object.
138      */
139     public void addObserver(final FieldObserver o) {
140         if (observers == null) {
141             observers = new ArrayList<>();
142         }
143         observers.add(o);
144     }
145 
146     /**
147      * Remove any initial value.
148      */
149     public void cancelInitValue() {
150         value = null;
151     }
152 
153     private void checkType(final Type atype) {
154         final Type superType = super.getType();
155         if (superType == null) {
156             throw new ClassGenException("You haven't defined the type of the field yet");
157         }
158         if (!isFinal()) {
159             throw new ClassGenException("Only final fields may have an initial value!");
160         }
161         if (!superType.equals(atype)) {
162             throw new ClassGenException("Types are not compatible: " + superType + " vs. " + atype);
163         }
164     }
165 
166     /**
167      * @return deep copy of this field
168      */
169     public FieldGen copy(final ConstantPoolGen cp) {
170         final FieldGen fg = (FieldGen) clone();
171         fg.setConstantPool(cp);
172         return fg;
173     }
174 
175     /**
176      * Return value as defined by given BCELComparator strategy. By default two FieldGen objects are said to be equal when
177      * their names and signatures are equal.
178      *
179      * @see Object#equals(Object)
180      */
181     @Override
182     public boolean equals(final Object obj) {
183         return obj instanceof FieldGen && bcelComparator.equals(this, (FieldGen) obj);
184     }
185 
186     /**
187      * Gets field object after having set up all necessary values.
188      */
189     public Field getField() {
190         final String signature = getSignature();
191         final int nameIndex = super.getConstantPool().addUtf8(super.getName());
192         final int signatureIndex = super.getConstantPool().addUtf8(signature);
193         if (value != null) {
194             checkType(super.getType());
195             final int index = addConstant();
196             addAttribute(new ConstantValue(super.getConstantPool().addUtf8("ConstantValue"), 2, index, super.getConstantPool().getConstantPool())); // sic
197         }
198         addAnnotationsAsAttribute(super.getConstantPool());
199         return new Field(super.getAccessFlags(), nameIndex, signatureIndex, getAttributes(), super.getConstantPool().getConstantPool()); // sic
200     }
201 
202     public String getInitValue() {
203         return Objects.toString(value, null);
204     }
205 
206     @Override
207     public String getSignature() {
208         return super.getType().getSignature();
209     }
210 
211     /**
212      * Return value as defined by given BCELComparator strategy. By default return the hash code of the field's name XOR
213      * signature.
214      *
215      * @see Object#hashCode()
216      */
217     @Override
218     public int hashCode() {
219         return bcelComparator.hashCode(this);
220     }
221 
222     /**
223      * Remove observer for this object.
224      */
225     public void removeObserver(final FieldObserver o) {
226         if (observers != null) {
227             observers.remove(o);
228         }
229     }
230 
231     public void setInitValue(final boolean b) {
232         checkType(Type.BOOLEAN);
233         if (b) {
234             value = Integer.valueOf(1);
235         }
236     }
237 
238     public void setInitValue(final byte b) {
239         checkType(Type.BYTE);
240         if (b != 0) {
241             value = Integer.valueOf(b);
242         }
243     }
244 
245     public void setInitValue(final char c) {
246         checkType(Type.CHAR);
247         if (c != 0) {
248             value = Integer.valueOf(c);
249         }
250     }
251 
252     public void setInitValue(final double d) {
253         checkType(Type.DOUBLE);
254         if (d != 0.0) {
255             value = Double.valueOf(d);
256         }
257     }
258 
259     public void setInitValue(final float f) {
260         checkType(Type.FLOAT);
261         if (f != 0.0) {
262             value = Float.valueOf(f);
263         }
264     }
265 
266     public void setInitValue(final int i) {
267         checkType(Type.INT);
268         if (i != 0) {
269             value = Integer.valueOf(i);
270         }
271     }
272 
273     public void setInitValue(final long l) {
274         checkType(Type.LONG);
275         if (l != 0L) {
276             value = Long.valueOf(l);
277         }
278     }
279 
280     public void setInitValue(final short s) {
281         checkType(Type.SHORT);
282         if (s != 0) {
283             value = Integer.valueOf(s);
284         }
285     }
286 
287     /**
288      * Sets (optional) initial value of field, otherwise it will be set to null/0/false by the JVM automatically.
289      */
290     public void setInitValue(final String str) {
291         checkType(ObjectType.getInstance("java.lang.String"));
292         if (str != null) {
293             value = str;
294         }
295     }
296 
297     private void setValue(final int index) {
298         final ConstantPool cp = super.getConstantPool().getConstantPool();
299         final Constant c = cp.getConstant(index);
300         value = ((ConstantObject) c).getConstantValue(cp);
301     }
302 
303     /**
304      * Return string representation close to declaration format, 'public static final short MAX = 100', e.g..
305      *
306      * @return String representation of field
307      */
308     @Override
309     public final String toString() {
310         String name;
311         String signature;
312         String access; // Short cuts to constant pool
313         access = Utility.accessToString(super.getAccessFlags());
314         access = access.isEmpty() ? "" : access + " ";
315         signature = super.getType().toString();
316         name = getName();
317         final StringBuilder buf = new StringBuilder(32); // CHECKSTYLE IGNORE MagicNumber
318         buf.append(access).append(signature).append(" ").append(name);
319         final String value = getInitValue();
320         if (value != null) {
321             buf.append(" = ").append(value);
322         }
323         return buf.toString();
324     }
325 
326     /**
327      * Call notify() method on all observers. This method is not called automatically whenever the state has changed, but
328      * has to be called by the user after they have finished editing the object.
329      */
330     public void update() {
331         if (observers != null) {
332             for (final FieldObserver observer : observers) {
333                 observer.notify(this);
334             }
335         }
336     }
337 }