Skip to content
Permalink
Browse files
IGNITE-16240 Support putFields()+writeFields() and readFields() in Us…
…er Object Serialization
  • Loading branch information
rpuch authored and SammyVimes committed Jan 18, 2022
1 parent ded59ba commit a7d78f9c597bcab939b89334525f69041d61d1c4
Showing 11 changed files with 965 additions and 28 deletions.
@@ -17,8 +17,14 @@

package org.apache.ignite.internal.network.serialization;

import static java.util.stream.Collectors.toUnmodifiableMap;

import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

@@ -59,6 +65,21 @@ public class ClassDescriptor {
*/
private final boolean isFinal;

/** Total number of bytes needed to store all primitive fields. */
private final int primitiveFieldsDataSize;
/** Total number of non-primitive fields. */
private final int objectFieldsCount;

private Map<String, FieldDescriptor> fieldsByName;
/**
* Offsets into primitive fields data array (which has size {@link #primitiveFieldsDataSize}).
* This array is a byte array containing data of all the primitive fields of an object.
* (Not to be confused with the offsets used in the context of {@link sun.misc.Unsafe}).
*/
private Object2IntMap<String> primitiveFieldDataOffsets;
/** Indices of non-primitive fields in the object fields array. */
private Object2IntMap<String> objectFieldIndices;

private final SpecialSerializationMethods serializationMethods;

/**
@@ -78,9 +99,28 @@ public ClassDescriptor(
this.serialization = serialization;
this.isFinal = Modifier.isFinal(clazz.getModifiers());

primitiveFieldsDataSize = computePrimitiveFieldsDataSize(fields);
objectFieldsCount = computeObjectFieldsCount(fields);

serializationMethods = new SpecialSerializationMethodsImpl(this);
}

private static int computePrimitiveFieldsDataSize(List<FieldDescriptor> fields) {
int accumulatedBytes = 0;
for (FieldDescriptor fieldDesc : fields) {
if (fieldDesc.isPrimitive()) {
accumulatedBytes += Primitives.widthInBytes(fieldDesc.clazz());
}
}
return accumulatedBytes;
}

private static int computeObjectFieldsCount(List<FieldDescriptor> fields) {
return (int) fields.stream()
.filter(fieldDesc -> !fieldDesc.isPrimitive())
.count();
}

/**
* Returns descriptor id.
*
@@ -288,14 +328,6 @@ public SpecialSerializationMethods serializationMethods() {
return serializationMethods;
}

@Override
public String toString() {
return "ClassDescriptor{"
+ "className='" + className() + '\''
+ ", descriptorId=" + descriptorId
+ '}';
}

/**
* Returns {@code true} if this descriptor describes same class as the given descriptor.
*
@@ -305,4 +337,115 @@ public String toString() {
public boolean describesSameClass(ClassDescriptor other) {
return other.clazz() == clazz();
}

/**
* Returns total number of bytes needed to store all primitive fields.
*
* @return total number of bytes needed to store all primitive fields
*/
public int primitiveFieldsDataSize() {
return primitiveFieldsDataSize;
}

/**
* Returns total number of object (i.e. non-primitive) fields.
*
* @return total number of object (i.e. non-primitive) fields
*/
public int objectFieldsCount() {
return objectFieldsCount;
}

/**
* Return offset into primitive fields data (which has size {@link #primitiveFieldsDataSize()}).
* These are different from the offsets used in the context of {@link sun.misc.Unsafe}.
*
* @param fieldName primitive field name
* @param requiredType field type
* @return offset into primitive fields data
*/
public int primitiveFieldDataOffset(String fieldName, Class<?> requiredType) {
assert requiredType.isPrimitive();

if (fieldsByName == null) {
fieldsByName = fieldsByNameMap(fields);
}

FieldDescriptor fieldDesc = fieldsByName.get(fieldName);
if (fieldDesc == null) {
throw new IllegalStateException("Did not find a field with name " + fieldName);
}
if (fieldDesc.clazz() != requiredType) {
throw new IllegalStateException("Field " + fieldName + " has type " + fieldDesc.clazz()
+ ", but it was used as " + requiredType);
}

if (primitiveFieldDataOffsets == null) {
primitiveFieldDataOffsets = primitiveFieldDataOffsetsMap(fields);
}

assert primitiveFieldDataOffsets.containsKey(fieldName);

return primitiveFieldDataOffsets.getInt(fieldName);
}

private static Map<String, FieldDescriptor> fieldsByNameMap(List<FieldDescriptor> fields) {
return fields.stream()
.collect(toUnmodifiableMap(FieldDescriptor::name, Function.identity()));
}

private static Object2IntMap<String> primitiveFieldDataOffsetsMap(List<FieldDescriptor> fields) {
Object2IntMap<String> map = new Object2IntOpenHashMap<>();

int accumulatedOffset = 0;
for (FieldDescriptor fieldDesc : fields) {
if (fieldDesc.isPrimitive()) {
map.put(fieldDesc.name(), accumulatedOffset);
accumulatedOffset += Primitives.widthInBytes(fieldDesc.clazz());
}
}

return map;
}

/**
* Returns index of a non-primitive (i.e. object) field in the object fields array.
*
* @param fieldName object field name
* @return index of a non-primitive (i.e. object) field in the object fields array
*/
public int objectFieldIndex(String fieldName) {
if (objectFieldIndices == null) {
objectFieldIndices = computeObjectFieldIndices(fields);
}

if (!objectFieldIndices.containsKey(fieldName)) {
throw new IllegalStateException("Did not find an object field with name " + fieldName);
}

return objectFieldIndices.getInt(fieldName);
}

private Object2IntMap<String> computeObjectFieldIndices(List<FieldDescriptor> fields) {
Object2IntMap<String> map = new Object2IntOpenHashMap<>();

int currentIndex = 0;
for (FieldDescriptor fieldDesc : fields) {
if (!fieldDesc.isPrimitive()) {
map.put(fieldDesc.name(), currentIndex);
currentIndex++;
}
}

return map;
}

/** {@inheritDoc} */
@Override
public String toString() {
return "ClassDescriptor{"
+ "className='" + className() + '\''
+ ", descriptorId=" + descriptorId
+ '}';
}
}
@@ -99,6 +99,27 @@ public int typeDescriptorId() {
return typeDescriptorId;
}

/**
* Returns {@code true} if this field has a primitive type.
*
* @return {@code true} if this field has a primitive type
*/
public boolean isPrimitive() {
return clazz.isPrimitive();
}

/**
* Returns width in bytes (that is, how many bytes a value of the field type takes) of the field type.
* If the field type is not primitive, throws an exception.
*
* @return width in bytes
*/
public int primitiveWidthInBytes() {
assert isPrimitive();

return Primitives.widthInBytes(clazz);
}

/**
* Returns {@link FieldAccessor} for this field.
*
@@ -0,0 +1,52 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.ignite.internal.network.serialization;

/**
* Utils to work with primitives.
*/
public class Primitives {
/**
* Returns number of bytes a value of the given primtive type takes (1 for byte, 8 for long and so on).
*
* @param clazz primitive type
* @return number of bytes
* @throws IllegalArgumentException if the passed type is not primitive
*/
public static int widthInBytes(Class<?> clazz) {
if (clazz == byte.class) {
return Byte.BYTES;
} else if (clazz == short.class) {
return Short.BYTES;
} else if (clazz == int.class) {
return Integer.BYTES;
} else if (clazz == long.class) {
return Long.BYTES;
} else if (clazz == float.class) {
return Float.BYTES;
} else if (clazz == double.class) {
return Double.BYTES;
} else if (clazz == char.class) {
return Character.BYTES;
} else if (clazz == boolean.class) {
return 1;
} else {
throw new IllegalArgumentException(clazz + " is not primitive");
}
}
}
@@ -0,0 +1,103 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.ignite.internal.network.serialization.marshal;

/**
* Code for packing/unpacking primitive values to/from a byte array at specific offsets.
*/
class Bits {
static boolean getBoolean(byte[] b, int off) {
return b[off] != 0;
}

static char getChar(byte[] b, int off) {
return (char) ((b[off + 1] & 0xFF)
+ (b[off] << 8));
}

static short getShort(byte[] b, int off) {
return (short) ((b[off + 1] & 0xFF)
+ (b[off] << 8));
}

static int getInt(byte[] b, int off) {
return ((b[off + 3] & 0xFF))
+ ((b[off + 2] & 0xFF) << 8)
+ ((b[off + 1] & 0xFF) << 16)
+ ((b[off]) << 24);
}

static float getFloat(byte[] b, int off) {
return Float.intBitsToFloat(getInt(b, off));
}

static long getLong(byte[] b, int off) {
return ((b[off + 7] & 0xFFL))
+ ((b[off + 6] & 0xFFL) << 8)
+ ((b[off + 5] & 0xFFL) << 16)
+ ((b[off + 4] & 0xFFL) << 24)
+ ((b[off + 3] & 0xFFL) << 32)
+ ((b[off + 2] & 0xFFL) << 40)
+ ((b[off + 1] & 0xFFL) << 48)
+ (((long) b[off]) << 56);
}

static double getDouble(byte[] b, int off) {
return Double.longBitsToDouble(getLong(b, off));
}

static void putBoolean(byte[] b, int off, boolean val) {
b[off] = (byte) (val ? 1 : 0);
}

static void putChar(byte[] b, int off, char val) {
b[off + 1] = (byte) (val);
b[off] = (byte) (val >>> 8);
}

static void putShort(byte[] b, int off, short val) {
b[off + 1] = (byte) (val);
b[off] = (byte) (val >>> 8);
}

static void putInt(byte[] b, int off, int val) {
b[off + 3] = (byte) (val);
b[off + 2] = (byte) (val >>> 8);
b[off + 1] = (byte) (val >>> 16);
b[off] = (byte) (val >>> 24);
}

static void putFloat(byte[] b, int off, float val) {
putInt(b, off, Float.floatToIntBits(val));
}

static void putLong(byte[] b, int off, long val) {
b[off + 7] = (byte) (val);
b[off + 6] = (byte) (val >>> 8);
b[off + 5] = (byte) (val >>> 16);
b[off + 4] = (byte) (val >>> 24);
b[off + 3] = (byte) (val >>> 32);
b[off + 2] = (byte) (val >>> 40);
b[off + 1] = (byte) (val >>> 48);
b[off] = (byte) (val >>> 56);
}

static void putDouble(byte[] b, int off, double val) {
putLong(b, off, Double.doubleToLongBits(val));
}
}

0 comments on commit a7d78f9

Please sign in to comment.