FieldGen.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  *  Unless required by applicable law or agreed to in writing, software
  12.  *  distributed under the License is distributed on an "AS IS" BASIS,
  13.  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  *  See the License for the specific language governing permissions and
  15.  *  limitations under the License.
  16.  */
  17. package org.apache.bcel.generic;

  18. import java.util.ArrayList;
  19. import java.util.List;
  20. import java.util.Objects;
  21. import java.util.stream.Stream;

  22. import org.apache.bcel.Const;
  23. import org.apache.bcel.classfile.Annotations;
  24. import org.apache.bcel.classfile.Attribute;
  25. import org.apache.bcel.classfile.Constant;
  26. import org.apache.bcel.classfile.ConstantObject;
  27. import org.apache.bcel.classfile.ConstantPool;
  28. import org.apache.bcel.classfile.ConstantValue;
  29. import org.apache.bcel.classfile.Field;
  30. import org.apache.bcel.classfile.Utility;
  31. import org.apache.bcel.util.BCELComparator;

  32. /**
  33.  * Template class for building up a field. The only extraordinary thing one can do is to add a constant value attribute
  34.  * to a field (which must of course be compatible with to the declared type).
  35.  *
  36.  * @see Field
  37.  */
  38. public class FieldGen extends FieldGenOrMethodGen {

  39.     private static BCELComparator<FieldGen> bcelComparator = new BCELComparator<FieldGen>() {

  40.         @Override
  41.         public boolean equals(final FieldGen a, final FieldGen b) {
  42.             return a == b || a != null && b != null && Objects.equals(a.getName(), b.getName()) && Objects.equals(a.getSignature(), b.getSignature());
  43.         }

  44.         @Override
  45.         public int hashCode(final FieldGen o) {
  46.             return o != null ? Objects.hash(o.getSignature(), o.getName()) : 0;
  47.         }
  48.     };

  49.     /**
  50.      * @return Comparison strategy object.
  51.      */
  52.     public static BCELComparator<FieldGen> getComparator() {
  53.         return bcelComparator;
  54.     }

  55.     /**
  56.      * @param comparator Comparison strategy object.
  57.      */
  58.     public static void setComparator(final BCELComparator<FieldGen> comparator) {
  59.         bcelComparator = comparator;
  60.     }

  61.     private Object value;

  62.     private List<FieldObserver> observers;

  63.     /**
  64.      * Instantiate from existing field.
  65.      *
  66.      * @param field Field object.
  67.      * @param cp constant pool (must contain the same entries as the field's constant pool).
  68.      */
  69.     public FieldGen(final Field field, final ConstantPoolGen cp) {
  70.         this(field.getAccessFlags(), Type.getType(field.getSignature()), field.getName(), cp);
  71.         final Attribute[] attrs = field.getAttributes();
  72.         for (final Attribute attr : attrs) {
  73.             if (attr instanceof ConstantValue) {
  74.                 setValue(((ConstantValue) attr).getConstantValueIndex());
  75.             } else if (attr instanceof Annotations) {
  76.                 final Annotations runtimeAnnotations = (Annotations) attr;
  77.                 runtimeAnnotations.forEach(element -> addAnnotationEntry(new AnnotationEntryGen(element, cp, false)));
  78.             } else {
  79.                 addAttribute(attr);
  80.             }
  81.         }
  82.     }

  83.     /**
  84.      * Declare a field. If it is static (isStatic() == true) and has a basic type like int or String it may have an initial
  85.      * value associated with it as defined by setInitValue().
  86.      *
  87.      * @param accessFlags access qualifiers
  88.      * @param type field type
  89.      * @param name field name
  90.      * @param cp constant pool
  91.      */
  92.     public FieldGen(final int accessFlags, final Type type, final String name, final ConstantPoolGen cp) {
  93.         super(accessFlags);
  94.         setType(type);
  95.         setName(name);
  96.         setConstantPool(cp);
  97.     }

  98.     private void addAnnotationsAsAttribute(final ConstantPoolGen cp) {
  99.         Stream.of(AnnotationEntryGen.getAnnotationAttributes(cp, super.getAnnotationEntries())).forEach(this::addAttribute);
  100.     }

  101.     private int addConstant() {
  102.         switch (super.getType().getType()) { // sic
  103.         case Const.T_INT:
  104.         case Const.T_CHAR:
  105.         case Const.T_BYTE:
  106.         case Const.T_BOOLEAN:
  107.         case Const.T_SHORT:
  108.             return super.getConstantPool().addInteger(((Integer) value).intValue());
  109.         case Const.T_FLOAT:
  110.             return super.getConstantPool().addFloat(((Float) value).floatValue());
  111.         case Const.T_DOUBLE:
  112.             return super.getConstantPool().addDouble(((Double) value).doubleValue());
  113.         case Const.T_LONG:
  114.             return super.getConstantPool().addLong(((Long) value).longValue());
  115.         case Const.T_REFERENCE:
  116.             return super.getConstantPool().addString((String) value);
  117.         default:
  118.             throw new IllegalStateException("Unhandled : " + super.getType().getType()); // sic
  119.         }
  120.     }

  121.     /**
  122.      * Add observer for this object.
  123.      */
  124.     public void addObserver(final FieldObserver o) {
  125.         if (observers == null) {
  126.             observers = new ArrayList<>();
  127.         }
  128.         observers.add(o);
  129.     }

  130.     /**
  131.      * Remove any initial value.
  132.      */
  133.     public void cancelInitValue() {
  134.         value = null;
  135.     }

  136.     private void checkType(final Type atype) {
  137.         final Type superType = super.getType();
  138.         if (superType == null) {
  139.             throw new ClassGenException("You haven't defined the type of the field yet");
  140.         }
  141.         if (!isFinal()) {
  142.             throw new ClassGenException("Only final fields may have an initial value!");
  143.         }
  144.         if (!superType.equals(atype)) {
  145.             throw new ClassGenException("Types are not compatible: " + superType + " vs. " + atype);
  146.         }
  147.     }

  148.     /**
  149.      * @return deep copy of this field
  150.      */
  151.     public FieldGen copy(final ConstantPoolGen cp) {
  152.         final FieldGen fg = (FieldGen) clone();
  153.         fg.setConstantPool(cp);
  154.         return fg;
  155.     }

  156.     /**
  157.      * Return value as defined by given BCELComparator strategy. By default two FieldGen objects are said to be equal when
  158.      * their names and signatures are equal.
  159.      *
  160.      * @see Object#equals(Object)
  161.      */
  162.     @Override
  163.     public boolean equals(final Object obj) {
  164.         return obj instanceof FieldGen && bcelComparator.equals(this, (FieldGen) obj);
  165.     }

  166.     /**
  167.      * Gets field object after having set up all necessary values.
  168.      */
  169.     public Field getField() {
  170.         final String signature = getSignature();
  171.         final int nameIndex = super.getConstantPool().addUtf8(super.getName());
  172.         final int signatureIndex = super.getConstantPool().addUtf8(signature);
  173.         if (value != null) {
  174.             checkType(super.getType());
  175.             final int index = addConstant();
  176.             addAttribute(new ConstantValue(super.getConstantPool().addUtf8("ConstantValue"), 2, index, super.getConstantPool().getConstantPool())); // sic
  177.         }
  178.         addAnnotationsAsAttribute(super.getConstantPool());
  179.         return new Field(super.getAccessFlags(), nameIndex, signatureIndex, getAttributes(), super.getConstantPool().getConstantPool()); // sic
  180.     }

  181.     public String getInitValue() {
  182.         return Objects.toString(value, null);
  183.     }

  184.     @Override
  185.     public String getSignature() {
  186.         return super.getType().getSignature();
  187.     }

  188.     /**
  189.      * Return value as defined by given BCELComparator strategy. By default return the hash code of the field's name XOR
  190.      * signature.
  191.      *
  192.      * @see Object#hashCode()
  193.      */
  194.     @Override
  195.     public int hashCode() {
  196.         return bcelComparator.hashCode(this);
  197.     }

  198.     /**
  199.      * Remove observer for this object.
  200.      */
  201.     public void removeObserver(final FieldObserver o) {
  202.         if (observers != null) {
  203.             observers.remove(o);
  204.         }
  205.     }

  206.     public void setInitValue(final boolean b) {
  207.         checkType(Type.BOOLEAN);
  208.         if (b) {
  209.             value = Integer.valueOf(1);
  210.         }
  211.     }

  212.     public void setInitValue(final byte b) {
  213.         checkType(Type.BYTE);
  214.         if (b != 0) {
  215.             value = Integer.valueOf(b);
  216.         }
  217.     }

  218.     public void setInitValue(final char c) {
  219.         checkType(Type.CHAR);
  220.         if (c != 0) {
  221.             value = Integer.valueOf(c);
  222.         }
  223.     }

  224.     public void setInitValue(final double d) {
  225.         checkType(Type.DOUBLE);
  226.         if (d != 0.0) {
  227.             value = Double.valueOf(d);
  228.         }
  229.     }

  230.     public void setInitValue(final float f) {
  231.         checkType(Type.FLOAT);
  232.         if (f != 0.0) {
  233.             value = Float.valueOf(f);
  234.         }
  235.     }

  236.     public void setInitValue(final int i) {
  237.         checkType(Type.INT);
  238.         if (i != 0) {
  239.             value = Integer.valueOf(i);
  240.         }
  241.     }

  242.     public void setInitValue(final long l) {
  243.         checkType(Type.LONG);
  244.         if (l != 0L) {
  245.             value = Long.valueOf(l);
  246.         }
  247.     }

  248.     public void setInitValue(final short s) {
  249.         checkType(Type.SHORT);
  250.         if (s != 0) {
  251.             value = Integer.valueOf(s);
  252.         }
  253.     }

  254.     /**
  255.      * Sets (optional) initial value of field, otherwise it will be set to null/0/false by the JVM automatically.
  256.      */
  257.     public void setInitValue(final String str) {
  258.         checkType(ObjectType.getInstance("java.lang.String"));
  259.         if (str != null) {
  260.             value = str;
  261.         }
  262.     }

  263.     private void setValue(final int index) {
  264.         final ConstantPool cp = super.getConstantPool().getConstantPool();
  265.         final Constant c = cp.getConstant(index);
  266.         value = ((ConstantObject) c).getConstantValue(cp);
  267.     }

  268.     /**
  269.      * Return string representation close to declaration format, 'public static final short MAX = 100', e.g..
  270.      *
  271.      * @return String representation of field
  272.      */
  273.     @Override
  274.     public final String toString() {
  275.         String name;
  276.         String signature;
  277.         String access; // Short cuts to constant pool
  278.         access = Utility.accessToString(super.getAccessFlags());
  279.         access = access.isEmpty() ? "" : access + " ";
  280.         signature = super.getType().toString();
  281.         name = getName();
  282.         final StringBuilder buf = new StringBuilder(32); // CHECKSTYLE IGNORE MagicNumber
  283.         buf.append(access).append(signature).append(" ").append(name);
  284.         final String value = getInitValue();
  285.         if (value != null) {
  286.             buf.append(" = ").append(value);
  287.         }
  288.         return buf.toString();
  289.     }

  290.     /**
  291.      * Call notify() method on all observers. This method is not called automatically whenever the state has changed, but
  292.      * has to be called by the user after they have finished editing the object.
  293.      */
  294.     public void update() {
  295.         if (observers != null) {
  296.             for (final FieldObserver observer : observers) {
  297.                 observer.notify(this);
  298.             }
  299.         }
  300.     }
  301. }