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 *
017 */
018package org.apache.bcel.generic;
019
020import java.util.ArrayList;
021import java.util.List;
022import java.util.Objects;
023import java.util.stream.Stream;
024
025import org.apache.bcel.Const;
026import org.apache.bcel.classfile.Annotations;
027import org.apache.bcel.classfile.Attribute;
028import org.apache.bcel.classfile.Constant;
029import org.apache.bcel.classfile.ConstantObject;
030import org.apache.bcel.classfile.ConstantPool;
031import org.apache.bcel.classfile.ConstantValue;
032import org.apache.bcel.classfile.Field;
033import org.apache.bcel.classfile.Utility;
034import org.apache.bcel.util.BCELComparator;
035
036/**
037 * Template class for building up a field. The only extraordinary thing one can do is to add a constant value attribute
038 * to a field (which must of course be compatible with to the declared type).
039 *
040 * @see Field
041 */
042public class FieldGen extends FieldGenOrMethodGen {
043
044    private static BCELComparator bcelComparator = new BCELComparator() {
045
046        @Override
047        public boolean equals(final Object o1, final Object o2) {
048            final FieldGen THIS = (FieldGen) o1;
049            final FieldGen THAT = (FieldGen) o2;
050            return Objects.equals(THIS.getName(), THAT.getName()) && Objects.equals(THIS.getSignature(), THAT.getSignature());
051        }
052
053        @Override
054        public int hashCode(final Object o) {
055            final FieldGen THIS = (FieldGen) o;
056            return THIS.getSignature().hashCode() ^ THIS.getName().hashCode();
057        }
058    };
059
060    /**
061     * @return Comparison strategy object
062     */
063    public static BCELComparator getComparator() {
064        return bcelComparator;
065    }
066
067    /**
068     * @param comparator Comparison strategy object
069     */
070    public static void setComparator(final BCELComparator comparator) {
071        bcelComparator = comparator;
072    }
073
074    private Object value;
075
076    private List<FieldObserver> observers;
077
078    /**
079     * Instantiate from existing field.
080     *
081     * @param field Field object
082     * @param cp constant pool (must contain the same entries as the field's constant pool)
083     */
084    public FieldGen(final Field field, final ConstantPoolGen cp) {
085        this(field.getAccessFlags(), Type.getType(field.getSignature()), field.getName(), cp);
086        final Attribute[] attrs = field.getAttributes();
087        for (final Attribute attr : attrs) {
088            if (attr instanceof ConstantValue) {
089                setValue(((ConstantValue) attr).getConstantValueIndex());
090            } else if (attr instanceof Annotations) {
091                final Annotations runtimeAnnotations = (Annotations) attr;
092                runtimeAnnotations.forEach(element -> addAnnotationEntry(new AnnotationEntryGen(element, cp, false)));
093            } else {
094                addAttribute(attr);
095            }
096        }
097    }
098
099    /**
100     * Declare a field. If it is static (isStatic() == true) and has a basic type like int or String it may have an initial
101     * value associated with it as defined by setInitValue().
102     *
103     * @param access_flags access qualifiers
104     * @param type field type
105     * @param name field name
106     * @param cp constant pool
107     */
108    public FieldGen(final int access_flags, final Type type, final String name, final ConstantPoolGen cp) {
109        super(access_flags);
110        setType(type);
111        setName(name);
112        setConstantPool(cp);
113    }
114
115    private void addAnnotationsAsAttribute(final ConstantPoolGen cp) {
116        Stream.of(AnnotationEntryGen.getAnnotationAttributes(cp, super.getAnnotationEntries())).forEach(this::addAttribute);
117    }
118
119    private int addConstant() {
120        switch (super.getType().getType()) { // sic
121        case Const.T_INT:
122        case Const.T_CHAR:
123        case Const.T_BYTE:
124        case Const.T_BOOLEAN:
125        case Const.T_SHORT:
126            return super.getConstantPool().addInteger(((Integer) value).intValue());
127        case Const.T_FLOAT:
128            return super.getConstantPool().addFloat(((Float) value).floatValue());
129        case Const.T_DOUBLE:
130            return super.getConstantPool().addDouble(((Double) value).doubleValue());
131        case Const.T_LONG:
132            return super.getConstantPool().addLong(((Long) value).longValue());
133        case Const.T_REFERENCE:
134            return super.getConstantPool().addString((String) value);
135        default:
136            throw new IllegalStateException("Unhandled : " + super.getType().getType()); // sic
137        }
138    }
139
140    /**
141     * Add observer for this object.
142     */
143    public void addObserver(final FieldObserver o) {
144        if (observers == null) {
145            observers = new ArrayList<>();
146        }
147        observers.add(o);
148    }
149
150    /**
151     * Remove any initial value.
152     */
153    public void cancelInitValue() {
154        value = null;
155    }
156
157    private void checkType(final Type atype) {
158        final Type superType = super.getType();
159        if (superType == null) {
160            throw new ClassGenException("You haven't defined the type of the field yet");
161        }
162        if (!isFinal()) {
163            throw new ClassGenException("Only final fields may have an initial value!");
164        }
165        if (!superType.equals(atype)) {
166            throw new ClassGenException("Types are not compatible: " + superType + " vs. " + atype);
167        }
168    }
169
170    /**
171     * @return deep copy of this field
172     */
173    public FieldGen copy(final ConstantPoolGen cp) {
174        final FieldGen fg = (FieldGen) clone();
175        fg.setConstantPool(cp);
176        return fg;
177    }
178
179    /**
180     * Return value as defined by given BCELComparator strategy. By default two FieldGen objects are said to be equal when
181     * their names and signatures are equal.
182     *
183     * @see Object#equals(Object)
184     */
185    @Override
186    public boolean equals(final Object obj) {
187        return bcelComparator.equals(this, obj);
188    }
189
190    /**
191     * Get field object after having set up all necessary values.
192     */
193    public Field getField() {
194        final String signature = getSignature();
195        final int name_index = super.getConstantPool().addUtf8(super.getName());
196        final int signature_index = super.getConstantPool().addUtf8(signature);
197        if (value != null) {
198            checkType(super.getType());
199            final int index = addConstant();
200            addAttribute(new ConstantValue(super.getConstantPool().addUtf8("ConstantValue"), 2, index, super.getConstantPool().getConstantPool())); // sic
201        }
202        addAnnotationsAsAttribute(super.getConstantPool());
203        return new Field(super.getAccessFlags(), name_index, signature_index, getAttributes(), super.getConstantPool().getConstantPool()); // sic
204    }
205
206    public String getInitValue() {
207        if (value != null) {
208            return value.toString();
209        }
210        return null;
211    }
212
213    @Override
214    public String getSignature() {
215        return super.getType().getSignature();
216    }
217
218    /**
219     * Return value as defined by given BCELComparator strategy. By default return the hashcode of the field's name XOR
220     * signature.
221     *
222     * @see Object#hashCode()
223     */
224    @Override
225    public int hashCode() {
226        return bcelComparator.hashCode(this);
227    }
228
229    /**
230     * Remove observer for this object.
231     */
232    public void removeObserver(final FieldObserver o) {
233        if (observers != null) {
234            observers.remove(o);
235        }
236    }
237
238    public void setInitValue(final boolean b) {
239        checkType(Type.BOOLEAN);
240        if (b) {
241            value = Integer.valueOf(1);
242        }
243    }
244
245    public void setInitValue(final byte b) {
246        checkType(Type.BYTE);
247        if (b != 0) {
248            value = Integer.valueOf(b);
249        }
250    }
251
252    public void setInitValue(final char c) {
253        checkType(Type.CHAR);
254        if (c != 0) {
255            value = Integer.valueOf(c);
256        }
257    }
258
259    public void setInitValue(final double d) {
260        checkType(Type.DOUBLE);
261        if (d != 0.0) {
262            value = Double.valueOf(d);
263        }
264    }
265
266    public void setInitValue(final float f) {
267        checkType(Type.FLOAT);
268        if (f != 0.0) {
269            value = Float.valueOf(f);
270        }
271    }
272
273    public void setInitValue(final int i) {
274        checkType(Type.INT);
275        if (i != 0) {
276            value = Integer.valueOf(i);
277        }
278    }
279
280    public void setInitValue(final long l) {
281        checkType(Type.LONG);
282        if (l != 0L) {
283            value = Long.valueOf(l);
284        }
285    }
286
287    public void setInitValue(final short s) {
288        checkType(Type.SHORT);
289        if (s != 0) {
290            value = Integer.valueOf(s);
291        }
292    }
293
294    /**
295     * Set (optional) initial value of field, otherwise it will be set to null/0/false by the JVM automatically.
296     */
297    public void setInitValue(final String str) {
298        checkType(ObjectType.getInstance("java.lang.String"));
299        if (str != null) {
300            value = str;
301        }
302    }
303
304    private void setValue(final int index) {
305        final ConstantPool cp = super.getConstantPool().getConstantPool();
306        final Constant c = cp.getConstant(index);
307        value = ((ConstantObject) c).getConstantValue(cp);
308    }
309
310    /**
311     * Return string representation close to declaration format, `public static final short MAX = 100', e.g..
312     *
313     * @return String representation of field
314     */
315    @Override
316    public final String toString() {
317        String name;
318        String signature;
319        String access; // Short cuts to constant pool
320        access = Utility.accessToString(super.getAccessFlags());
321        access = access.isEmpty() ? "" : access + " ";
322        signature = super.getType().toString();
323        name = getName();
324        final StringBuilder buf = new StringBuilder(32); // CHECKSTYLE IGNORE MagicNumber
325        buf.append(access).append(signature).append(" ").append(name);
326        final String value = getInitValue();
327        if (value != null) {
328            buf.append(" = ").append(value);
329        }
330        return buf.toString();
331    }
332
333    /**
334     * Call notify() method on all observers. This method is not called automatically whenever the state has changed, but
335     * has to be called by the user after they have finished editing the object.
336     */
337    public void update() {
338        if (observers != null) {
339            for (final FieldObserver observer : observers) {
340                observer.notify(this);
341            }
342        }
343    }
344}