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}