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}