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