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