Package com.esotericsoftware.kryo.serialize

Source Code of com.esotericsoftware.kryo.serialize.CompatibleFieldSerializer$CachedField

package com.esotericsoftware.kryo.serialize;

import static com.esotericsoftware.minlog.Log.*;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.PriorityQueue;

import com.esotericsoftware.kryo.Context;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.NotNull;
import com.esotericsoftware.kryo.SerializationException;
import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.Kryo.RegisteredClass;
import com.esotericsoftware.kryo.util.Util;
import com.esotericsoftware.reflectasm.FieldAccess;

/**
* Serializes objects using direct field assignment, with limited support for forward and backward compatibility. Fields can be
* added or removed without invalidating previously serialized bytes. Note that changing the type of a field is not supported.
* <p>
* There is additional overhead compared to {@link FieldSerializer}. A header is output the first time an object of a given type
* is serialized. The header consists of an int for the number of fields, then a String for each field name. Also, to support
* skipping the bytes for a field that no longer exists, for each field value an int is written that is the length of the value in
* bytes.
* @author Nathan Sweet <misc@n4te.com>
*/
public class CompatibleFieldSerializer extends Serializer {
  final Kryo kryo;
  final Class type;
  private CachedField[] fields;
  Object access;
  private boolean fieldsCanBeNull = true, setFieldsAsAccessible = true;

  public CompatibleFieldSerializer (Kryo kryo, Class type) {
    this.kryo = kryo;
    this.type = type;
    rebuildCachedFields();
  }

  private void rebuildCachedFields () {
    if (type.isInterface()) {
      fields = new CachedField[0]; // No fields to serialize.
      return;
    }

    // Collect all fields.
    ArrayList<Field> allFields = new ArrayList();
    Class nextClass = type;
    while (nextClass != Object.class) {
      Collections.addAll(allFields, nextClass.getDeclaredFields());
      nextClass = nextClass.getSuperclass();
    }

    ArrayList<CachedField> publicFields = new ArrayList();
    PriorityQueue<CachedField> cachedFields = new PriorityQueue(Math.max(1, allFields.size()), new Comparator<CachedField>() {
      public int compare (CachedField o1, CachedField o2) {
        // Fields are sorted by alpha so the order of the data is known.
        return o1.field.getName().compareTo(o2.field.getName());
      }
    });
    for (int i = 0, n = allFields.size(); i < n; i++) {
      Field field = allFields.get(i);

      int modifiers = field.getModifiers();
      if (Modifier.isTransient(modifiers)) continue;
      if (Modifier.isStatic(modifiers)) continue;
      if (field.isSynthetic()) continue;

      if (!field.isAccessible()) {
        if (!setFieldsAsAccessible) continue;
        try {
          field.setAccessible(true);
        } catch (AccessControlException ex) {
          continue;
        }
      }

      CachedField cachedField = new CachedField();
      cachedField.field = field;
      if (fieldsCanBeNull)
        cachedField.canBeNull = !field.isAnnotationPresent(NotNull.class);
      else
        cachedField.canBeNull = false;

      // Always use the same serializer for this field if the field's class is final.
      Class fieldClass = field.getType();
      if (isFinal(fieldClass)) cachedField.fieldClass = fieldClass;

      cachedFields.add(cachedField);
      if (Modifier.isPublic(modifiers) && Modifier.isPublic(fieldClass.getModifiers())) publicFields.add(cachedField);
    }

    if (!Util.isAndroid && Modifier.isPublic(type.getModifiers()) && !publicFields.isEmpty()) {
      // Use ReflectASM for any public fields.
      try {
        access = FieldAccess.get(type);
        for (int i = 0, n = publicFields.size(); i < n; i++) {
          CachedField cachedField = publicFields.get(i);
          cachedField.accessIndex = ((FieldAccess)access).getIndex(cachedField.field.getName());
        }
      } catch (AccessControlException ignored) {
      }
    }

    int fieldCount = cachedFields.size();
    fields = new CachedField[fieldCount];
    for (int i = 0; i < fieldCount; i++)
      fields[i] = cachedFields.poll();
  }

  /**
   * Sets the default value for {@link CachedField#setCanBeNull(boolean)}.
   * @param fieldsCanBeNull False if none of the fields are null. Saves 1 byte per field. True if it is not known (default).
   */
  public void setFieldsCanBeNull (boolean fieldsCanBeNull) {
    this.fieldsCanBeNull = fieldsCanBeNull;
    rebuildCachedFields();
  }

  /**
   * Controls which fields are accessed.
   * @param setFieldsAsAccessible If true, all non-transient fields (inlcuding private fields) will be serialized and
   *           {@link Field#setAccessible(boolean) set as accessible} (default). If false, only fields in the public API will be
   *           serialized.
   */
  public void setFieldsAsAccessible (boolean setFieldsAsAccessible) {
    this.setFieldsAsAccessible = setFieldsAsAccessible;
    rebuildCachedFields();
  }

  public void writeObjectData (ByteBuffer buffer, Object object) {
    Context context = Kryo.getContext();
    if (context.getTemp(this, "schemaWritten") == null) {
      context.putTemp(this, "schemaWritten", Boolean.TRUE);
      if (TRACE) trace("kryo", "Writing " + fields.length + " field names.");
      IntSerializer.put(buffer, fields.length, true);
      for (int i = 0, n = fields.length; i < n; i++)
        StringSerializer.put(buffer, fields[i].field.getName());
    }

    for (int i = 0, n = fields.length; i < n; i++) {
      CachedField cachedField = fields[i];
      try {
        if (TRACE) trace("kryo", "Writing field: " + cachedField + " (" + object.getClass().getName() + ")");

        Object value = cachedField.get(object);
        if (value == null) {
          kryo.writeClass(buffer, null);
          continue;
        }

        int start = buffer.position();
        try {
          buffer.position(start + 1);
        } catch (IllegalArgumentException ex) {
          new BufferOverflowException();
        }

        Serializer serializer = cachedField.serializer;
        if (cachedField.fieldClass == null) {
          RegisteredClass registeredClass = kryo.writeClass(buffer, value.getClass());
          if (serializer == null) serializer = registeredClass.getSerializer();
          serializer.writeObjectData(buffer, value);
        } else {
          if (serializer == null)
            cachedField.serializer = serializer = kryo.getRegisteredClass(cachedField.fieldClass).getSerializer();
          if (!cachedField.canBeNull)
            serializer.writeObjectData(buffer, value);
          else
            serializer.writeObject(buffer, value);
        }

        int dataLength = buffer.position() - start - 1;
        if (dataLength <= 127) {
          // Ideally it fits in one byte.
          buffer.put(start, (byte)dataLength);
        } else {
          // Shift the data over to make room for the length.
          byte[] temp = context.getByteArray(dataLength);
          buffer.position(start + 1);
          buffer.get(temp);
          buffer.position(start);
          IntSerializer.put(buffer, dataLength, true);
          buffer.put(temp);
        }
      } catch (IllegalAccessException ex) {
        throw new SerializationException("Error accessing field in class: " + object.getClass().getName(), ex);
      } catch (SerializationException ex) {
        ex.addTrace(cachedField + " (" + object.getClass().getName() + ")");
        throw ex;
      } catch (RuntimeException runtimeEx) {
        SerializationException ex = new SerializationException(runtimeEx);
        ex.addTrace(cachedField + " (" + object.getClass().getName() + ")");
        throw ex;
      }
    }
    if (TRACE) trace("kryo", "Wrote object: " + object);
  }

  public <T> T readObjectData (ByteBuffer buffer, Class<T> type) {
    return readObjectData(newInstance(kryo, type), buffer, type);
  }

  protected <T> T readObjectData (T object, ByteBuffer buffer, Class<T> type) {
    Context context = Kryo.getContext();
    CachedField[] fields = (CachedField[])context.getTemp(this, "schema");
    if (fields == null) {
      int length = IntSerializer.get(buffer, true);
      if (TRACE) trace("kryo", "Reading " + length + " field names.");
      String[] names = new String[length];
      for (int i = 0; i < length; i++)
        names[i] = StringSerializer.get(buffer);

      fields = new CachedField[length];
      CachedField[] allFields = this.fields;
      outer: //
      for (int i = 0, n = names.length; i < n; i++) {
        String schemaName = names[i];
        for (int ii = 0, nn = allFields.length; ii < nn; ii++) {
          if (allFields[ii].field.getName().equals(schemaName)) {
            fields[i] = allFields[ii];
            continue outer;
          }
        }
        if (TRACE) trace("kryo", "Ignoring obsolete field: " + schemaName);
      }
      context.putTemp(this, "schema", fields);
    }

    for (int i = 0, n = fields.length; i < n; i++) {
      int dataLength = IntSerializer.get(buffer, true);

      CachedField cachedField = fields[i];
      try {
        if (cachedField == null) {
          if (TRACE) trace("kryo", "Skipping obsolete field bytes: " + dataLength);
          try {
            buffer.position(buffer.position() + dataLength);
          } catch (IllegalArgumentException ex) {
            new BufferOverflowException();
          }
          continue;
        }

        if (TRACE) trace("kryo", "Reading field: " + cachedField + " (" + type.getName() + ")");

        Object value;

        if (dataLength == 0)
          value = null;
        else {
          Class concreteType = cachedField.fieldClass;
          Serializer serializer = cachedField.serializer;
          if (concreteType == null) {
            RegisteredClass registeredClass = kryo.readClass(buffer);
            if (registeredClass == null)
              value = null;
            else {
              concreteType = registeredClass.getType();
              if (serializer == null) serializer = registeredClass.getSerializer();
              value = serializer.readObjectData(buffer, concreteType);
            }
          } else {
            if (serializer == null)
              cachedField.serializer = serializer = kryo.getRegisteredClass(concreteType).getSerializer();
            if (!cachedField.canBeNull)
              value = serializer.readObjectData(buffer, concreteType);
            else
              value = serializer.readObject(buffer, concreteType);
          }
        }

        cachedField.set(object, value);
      } catch (IllegalAccessException ex) {
        throw new SerializationException("Error accessing field in class: " + type.getName(), ex);
      } catch (SerializationException ex) {
        ex.addTrace(cachedField + " (" + type.getName() + ")");
        throw ex;
      } catch (RuntimeException runtimeEx) {
        SerializationException ex = new SerializationException(runtimeEx);
        ex.addTrace(cachedField + " (" + type.getName() + ")");
        throw ex;
      }
    }
    if (TRACE) trace("kryo", "Read object: " + object);
    return object;
  }

  /**
   * Allows specific fields to be optimized.
   */
  public CachedField getField (String fieldName) {
    for (CachedField cachedField : fields)
      if (cachedField.field.getName().equals(fieldName)) return cachedField;
    throw new IllegalArgumentException("Field \"" + fieldName + "\" not found on class: " + type.getName());
  }

  /**
   * Removes a field so that it won't be serialized.
   */
  public void removeField (String fieldName) {
    for (int i = 0; i < fields.length; i++) {
      CachedField cachedField = fields[i];
      if (cachedField.field.getName().equals(fieldName)) {
        CachedField[] newFields = new CachedField[fields.length - 1];
        System.arraycopy(fields, 0, newFields, 0, i);
        System.arraycopy(fields, i + 1, newFields, i, newFields.length - i);
        fields = newFields;
        return;
      }
    }
    throw new IllegalArgumentException("Field \"" + fieldName + "\" not found on class: " + type.getName());
  }

  /**
   * Controls how a field will be serialized.
   */
  public class CachedField {
    Field field;
    Class fieldClass;
    Serializer serializer;
    boolean canBeNull;
    int accessIndex = -1;

    /**
     * @param fieldClass The concrete class of the values for this field. This saves 1-2 bytes. The serializer registered for
     *           the specified class will be used. Only set to a non-null value if the field type in the class definition is
     *           final or the values for this field will not vary.
     */
    public void setClass (Class fieldClass) {
      this.fieldClass = fieldClass;
      this.serializer = null;
    }

    /**
     * @param fieldClass The concrete class of the values for this field. This saves 1-2 bytes. Only set to a non-null value if
     *           the field type in the class definition is final or the values for this field will not vary.
     */
    public void setClass (Class fieldClass, Serializer serializer) {
      this.fieldClass = fieldClass;
      this.serializer = serializer;
    }

    public void setCanBeNull (boolean canBeNull) {
      this.canBeNull = canBeNull;
    }

    public String toString () {
      return field.getName();
    }

    Object get (Object object) throws IllegalAccessException {
      if (accessIndex != -1) return ((FieldAccess)access).get(object, accessIndex);
      return field.get(object);
    }

    void set (Object object, Object value) throws IllegalAccessException {
      if (accessIndex != -1)
        ((FieldAccess)access).set(object, accessIndex, value);
      else
        field.set(object, value);
    }
  }
}
TOP

Related Classes of com.esotericsoftware.kryo.serialize.CompatibleFieldSerializer$CachedField

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.