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