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<FieldGen> bcelComparator = new BCELComparator<FieldGen>() {
044
045        @Override
046        public boolean equals(final FieldGen a, final FieldGen b) {
047            return a == b || a != null && b != null && Objects.equals(a.getName(), b.getName()) && Objects.equals(a.getSignature(), b.getSignature());
048        }
049
050        @Override
051        public int hashCode(final FieldGen o) {
052            return o != null ? Objects.hash(o.getSignature(), o.getName()) : 0;
053        }
054    };
055
056    /**
057     * @return Comparison strategy object.
058     */
059    public static BCELComparator<FieldGen> getComparator() {
060        return bcelComparator;
061    }
062
063    /**
064     * @param comparator Comparison strategy object.
065     */
066    public static void setComparator(final BCELComparator<FieldGen> comparator) {
067        bcelComparator = comparator;
068    }
069
070    private Object value;
071
072    private List<FieldObserver> observers;
073
074    /**
075     * Instantiate from existing field.
076     *
077     * @param field Field object.
078     * @param cp constant pool (must contain the same entries as the field's constant pool).
079     */
080    public FieldGen(final Field field, final ConstantPoolGen cp) {
081        this(field.getAccessFlags(), Type.getType(field.getSignature()), field.getName(), cp);
082        final Attribute[] attrs = field.getAttributes();
083        for (final Attribute attr : attrs) {
084            if (attr instanceof ConstantValue) {
085                setValue(((ConstantValue) attr).getConstantValueIndex());
086            } else if (attr instanceof Annotations) {
087                final Annotations runtimeAnnotations = (Annotations) attr;
088                runtimeAnnotations.forEach(element -> addAnnotationEntry(new AnnotationEntryGen(element, cp, false)));
089            } else {
090                addAttribute(attr);
091            }
092        }
093    }
094
095    /**
096     * Declare a field. If it is static (isStatic() == true) and has a basic type like int or String it may have an initial
097     * value associated with it as defined by setInitValue().
098     *
099     * @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}