    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}