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     * @return Comparison strategy object.
060     */
061    public static BCELComparator<FieldGen> getComparator() {
062        return bcelComparator;
063    }
064
065    /**
066     * @param comparator Comparison strategy object.
067     */
068    public static void setComparator(final BCELComparator<FieldGen> comparator) {
069        bcelComparator = comparator;
070    }
071
072    private Object value;
073
074    private List<FieldObserver> observers;
075
076    /**
077     * Instantiate from existing field.
078     *
079     * @param field Field object.
080     * @param cp constant pool (must contain the same entries as the field's constant pool).
081     */
082    public FieldGen(final Field field, final ConstantPoolGen cp) {
083        this(field.getAccessFlags(), Type.getType(field.getSignature()), field.getName(), cp);
084        final Attribute[] attrs = field.getAttributes();
085        for (final Attribute attr : attrs) {
086            if (attr instanceof ConstantValue) {
087                setValue(((ConstantValue) attr).getConstantValueIndex());
088            } else if (attr instanceof Annotations) {
089                final Annotations runtimeAnnotations = (Annotations) attr;
090                runtimeAnnotations.forEach(element -> addAnnotationEntry(new AnnotationEntryGen(element, cp, false)));
091            } else {
092                addAttribute(attr);
093            }
094        }
095    }
096
097    /**
098     * Declare a field. If it is static (isStatic() == true) and has a basic type like int or String it may have an initial
099     * value associated with it as defined by setInitValue().
100     *
101     * @param accessFlags access qualifiers
102     * @param type field type
103     * @param name field name
104     * @param cp constant pool
105     */
106    public FieldGen(final int accessFlags, final Type type, final String name, final ConstantPoolGen cp) {
107        super(accessFlags);
108        setType(type);
109        setName(name);
110        setConstantPool(cp);
111    }
112
113    private void addAnnotationsAsAttribute(final ConstantPoolGen cp) {
114        Stream.of(AnnotationEntryGen.getAnnotationAttributes(cp, super.getAnnotationEntries())).forEach(this::addAttribute);
115    }
116
117    private int addConstant() {
118        switch (super.getType().getType()) { // sic
119        case Const.T_INT:
120        case Const.T_CHAR:
121        case Const.T_BYTE:
122        case Const.T_BOOLEAN:
123        case Const.T_SHORT:
124            return super.getConstantPool().addInteger(((Integer) value).intValue());
125        case Const.T_FLOAT:
126            return super.getConstantPool().addFloat(((Float) value).floatValue());
127        case Const.T_DOUBLE:
128            return super.getConstantPool().addDouble(((Double) value).doubleValue());
129        case Const.T_LONG:
130            return super.getConstantPool().addLong(((Long) value).longValue());
131        case Const.T_REFERENCE:
132            return super.getConstantPool().addString((String) value);
133        default:
134            throw new IllegalStateException("Unhandled : " + super.getType().getType()); // sic
135        }
136    }
137
138    /**
139     * Add observer for this object.
140     */
141    public void addObserver(final FieldObserver o) {
142        if (observers == null) {
143            observers = new ArrayList<>();
144        }
145        observers.add(o);
146    }
147
148    /**
149     * Remove any initial value.
150     */
151    public void cancelInitValue() {
152        value = null;
153    }
154
155    private void checkType(final Type atype) {
156        final Type superType = super.getType();
157        if (superType == null) {
158            throw new ClassGenException("You haven't defined the type of the field yet");
159        }
160        if (!isFinal()) {
161            throw new ClassGenException("Only final fields may have an initial value!");
162        }
163        if (!superType.equals(atype)) {
164            throw new ClassGenException("Types are not compatible: " + superType + " vs. " + atype);
165        }
166    }
167
168    /**
169     * @return deep copy of this field
170     */
171    public FieldGen copy(final ConstantPoolGen cp) {
172        final FieldGen fg = (FieldGen) clone();
173        fg.setConstantPool(cp);
174        return fg;
175    }
176
177    /**
178     * Return value as defined by given BCELComparator strategy. By default two FieldGen objects are said to be equal when
179     * their names and signatures are equal.
180     *
181     * @see Object#equals(Object)
182     */
183    @Override
184    public boolean equals(final Object obj) {
185        return obj instanceof FieldGen && bcelComparator.equals(this, (FieldGen) obj);
186    }
187
188    /**
189     * Gets field object after having set up all necessary values.
190     */
191    public Field getField() {
192        final String signature = getSignature();
193        final int nameIndex = super.getConstantPool().addUtf8(super.getName());
194        final int signatureIndex = super.getConstantPool().addUtf8(signature);
195        if (value != null) {
196            checkType(super.getType());
197            final int index = addConstant();
198            addAttribute(new ConstantValue(super.getConstantPool().addUtf8("ConstantValue"), 2, index, super.getConstantPool().getConstantPool())); // sic
199        }
200        addAnnotationsAsAttribute(super.getConstantPool());
201        return new Field(super.getAccessFlags(), nameIndex, signatureIndex, getAttributes(), super.getConstantPool().getConstantPool()); // sic
202    }
203
204    public String getInitValue() {
205        return Objects.toString(value, null);
206    }
207
208    @Override
209    public String getSignature() {
210        return super.getType().getSignature();
211    }
212
213    /**
214     * Return value as defined by given BCELComparator strategy. By default return the hash code of the field's name XOR
215     * signature.
216     *
217     * @see Object#hashCode()
218     */
219    @Override
220    public int hashCode() {
221        return bcelComparator.hashCode(this);
222    }
223
224    /**
225     * Remove observer for this object.
226     */
227    public void removeObserver(final FieldObserver o) {
228        if (observers != null) {
229            observers.remove(o);
230        }
231    }
232
233    public void setInitValue(final boolean b) {
234        checkType(Type.BOOLEAN);
235        if (b) {
236            value = Integer.valueOf(1);
237        }
238    }
239
240    public void setInitValue(final byte b) {
241        checkType(Type.BYTE);
242        if (b != 0) {
243            value = Integer.valueOf(b);
244        }
245    }
246
247    public void setInitValue(final char c) {
248        checkType(Type.CHAR);
249        if (c != 0) {
250            value = Integer.valueOf(c);
251        }
252    }
253
254    public void setInitValue(final double d) {
255        checkType(Type.DOUBLE);
256        if (d != 0.0) {
257            value = Double.valueOf(d);
258        }
259    }
260
261    public void setInitValue(final float f) {
262        checkType(Type.FLOAT);
263        if (f != 0.0) {
264            value = Float.valueOf(f);
265        }
266    }
267
268    public void setInitValue(final int i) {
269        checkType(Type.INT);
270        if (i != 0) {
271            value = Integer.valueOf(i);
272        }
273    }
274
275    public void setInitValue(final long l) {
276        checkType(Type.LONG);
277        if (l != 0L) {
278            value = Long.valueOf(l);
279        }
280    }
281
282    public void setInitValue(final short s) {
283        checkType(Type.SHORT);
284        if (s != 0) {
285            value = Integer.valueOf(s);
286        }
287    }
288
289    /**
290     * Sets (optional) initial value of field, otherwise it will be set to null/0/false by the JVM automatically.
291     */
292    public void setInitValue(final String str) {
293        checkType(ObjectType.getInstance("java.lang.String"));
294        if (str != null) {
295            value = str;
296        }
297    }
298
299    private void setValue(final int index) {
300        final ConstantPool cp = super.getConstantPool().getConstantPool();
301        final Constant c = cp.getConstant(index);
302        value = ((ConstantObject) c).getConstantValue(cp);
303    }
304
305    /**
306     * Return string representation close to declaration format, for example: 'public static final short MAX = 100'.
307     *
308     * @return String representation of field
309     */
310    @Override
311    public final String toString() {
312        // Short cuts to constant pool
313        String access = Utility.accessToString(super.getAccessFlags());
314        access = access.isEmpty() ? "" : access + " ";
315        final String signature = super.getType().toString();
316        final String 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}