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 + " versus " + 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     * <p>
255     * Records an explicit constant value, including {@code false}. Use {@link #cancelInitValue()} to remove it.
256     * </p>
257     *
258     * @param b The boolean value.
259     */
260    public void setInitValue(final boolean b) {
261        checkType(Type.BOOLEAN);
262        value = Integer.valueOf(b ? 1 : 0);
263    }
264
265    /**
266     * Sets the initial value.
267     *
268     * @param b The byte value.
269     */
270    public void setInitValue(final byte b) {
271        checkType(Type.BYTE);
272        if (b != 0) {
273            value = Integer.valueOf(b);
274        }
275    }
276
277    /**
278     * Sets the initial value.
279     *
280     * @param c The char value.
281     */
282    public void setInitValue(final char c) {
283        checkType(Type.CHAR);
284        if (c != 0) {
285            value = Integer.valueOf(c);
286        }
287    }
288
289    /**
290     * Sets the initial value.
291     *
292     * @param d The double value.
293     */
294    public void setInitValue(final double d) {
295        checkType(Type.DOUBLE);
296        if (d != 0.0) {
297            value = Double.valueOf(d);
298        }
299    }
300
301    /**
302     * Sets the initial value.
303     *
304     * @param f The float value.
305     */
306    public void setInitValue(final float f) {
307        checkType(Type.FLOAT);
308        if (f != 0.0) {
309            value = Float.valueOf(f);
310        }
311    }
312
313    /**
314     * Sets the initial value.
315     *
316     * @param i The int value.
317     */
318    public void setInitValue(final int i) {
319        checkType(Type.INT);
320        if (i != 0) {
321            value = Integer.valueOf(i);
322        }
323    }
324
325    /**
326     * Sets the initial value.
327     *
328     * @param l The long value.
329     */
330    public void setInitValue(final long l) {
331        checkType(Type.LONG);
332        if (l != 0L) {
333            value = Long.valueOf(l);
334        }
335    }
336
337    /**
338     * Sets the initial value.
339     *
340     * @param s The short value.
341     */
342    public void setInitValue(final short s) {
343        checkType(Type.SHORT);
344        if (s != 0) {
345            value = Integer.valueOf(s);
346        }
347    }
348
349    /**
350     * Sets (optional) initial value of field, otherwise it will be set to null/0/false by the JVM automatically.
351     *
352     * @param str The string value.
353     */
354    public void setInitValue(final String str) {
355        checkType(ObjectType.getInstance("java.lang.String"));
356        if (str != null) {
357            value = str;
358        }
359    }
360
361    private void setValue(final int index) {
362        final ConstantPool cp = super.getConstantPool().getConstantPool();
363        final Constant c = cp.getConstant(index);
364        value = ((ConstantObject) c).getConstantValue(cp);
365    }
366
367    /**
368     * Return string representation close to declaration format, for example: 'public static final short MAX = 100'.
369     *
370     * @return String representation of field.
371     */
372    @Override
373    public final String toString() {
374        // Short cuts to constant pool
375        String access = Utility.accessToString(super.getAccessFlags());
376        access = access.isEmpty() ? "" : access + " ";
377        final String signature = super.getType().toString();
378        final String name = getName();
379        final StringBuilder buf = new StringBuilder(32); // CHECKSTYLE IGNORE MagicNumber
380        buf.append(access).append(signature).append(" ").append(name);
381        final String value = getInitValue();
382        if (value != null) {
383            buf.append(" = ").append(value);
384        }
385        return buf.toString();
386    }
387
388    /**
389     * Call notify() method on all observers. This method is not called automatically whenever the state has changed, but
390     * has to be called by the user after they have finished editing the object.
391     */
392    public void update() {
393        if (observers != null) {
394            for (final FieldObserver observer : observers) {
395                observer.notify(this);
396            }
397        }
398    }
399}