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