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