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