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