All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.conqat.lib.commons.serialization.utils.SerializationMigrationUtils Maven / Gradle / Ivy

There is a newer version: 2024.7.2
Show newest version
/*
 * Copyright (c) CQSE GmbH
 *
 * Licensed 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.conqat.lib.commons.serialization.utils;

import static com.google.common.collect.Iterables.getOnlyElement;
import static java.util.stream.Collectors.toCollection;

import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

import org.checkerframework.checker.nullness.qual.Nullable;
import org.conqat.lib.commons.assertion.CCSMAssert;
import org.conqat.lib.commons.collections.UnmodifiableList;
import org.conqat.lib.commons.io.SerializationUtils;
import org.conqat.lib.commons.serialization.SerializedEntityBase;
import org.conqat.lib.commons.serialization.SerializedEntityParser;
import org.conqat.lib.commons.serialization.SerializedEntityPool;
import org.conqat.lib.commons.serialization.SerializedEntitySerializer;
import org.conqat.lib.commons.serialization.classes.SerializedClass;
import org.conqat.lib.commons.serialization.classes.SerializedComplexFieldBase;
import org.conqat.lib.commons.serialization.classes.SerializedFieldBase;
import org.conqat.lib.commons.serialization.objects.SerializedArrayObject;
import org.conqat.lib.commons.serialization.objects.SerializedClassValues;
import org.conqat.lib.commons.serialization.objects.SerializedEnumLiteral;
import org.conqat.lib.commons.serialization.objects.SerializedObject;
import org.conqat.lib.commons.serialization.objects.SerializedObjectBase;
import org.conqat.lib.commons.serialization.objects.SerializedStringObject;

import com.google.common.base.Function;

/**
 * Utility methods for migrating serialized objects.
 */
public class SerializationMigrationUtils {

	/** Transforms all strings in a (de)serialized string set. */
	public static void transformStringsInHashSet(SerializedObject setObject, Function transformation,
			SerializedEntityPool pool) throws IOException {
		SerializedClass setClass = findClassInHierarchy(setObject, HashSet.class);

		SerializedClassValues fieldSets = setObject.getFieldSet(setObject.getPlainClassHierarchy().indexOf(setClass));

		for (Object data : fieldSets.getPreFieldData()) {
			if (!(data instanceof Integer)) {
				continue;
			}

			SerializedEntityBase containedElement = pool.getEntity((Integer) data, SerializedEntityBase.class);
			if (containedElement instanceof SerializedStringObject) {
				SerializedStringObject stringContent = (SerializedStringObject) containedElement;
				stringContent.setValue(transformation.apply(stringContent.getValue()));
			}
		}
	}

	/** Returns all objects in a (de)serialized {@link ArrayList}. */
	@SuppressWarnings("unchecked")
	public static  List getObjectsFromArrayList(SerializedObject listObject,
			SerializedEntityPool pool) throws IOException {
		SerializedClass listClass = findClassInHierarchy(listObject, ArrayList.class);

		SerializedClassValues fieldSets = listObject
				.getFieldSet(listObject.getPlainClassHierarchy().indexOf(listClass));
		List objects = new ArrayList<>();
		for (Object data : fieldSets.getPostFieldData()) {
			if (!(data instanceof Integer)) {
				continue;
			}
			SerializedEntityBase containedElement = pool.getEntity((Integer) data, SerializedEntityBase.class);
			objects.add((T) containedElement);
		}
		return objects;
	}

	/**
	 * Tries to find a given class in the {@link SerializedObject}'s classHierarchy. Fails with an
	 * assertion error if the given class is not in the class hierarchy of the given serialized object.
	 *
	 * @param setObject
	 *            The object that defines the class hierarchy.
	 * @param classToFind
	 *            A class in the class hierarchy of the runtime type of #setObject.
	 */
	public static SerializedClass findClassInHierarchy(SerializedObject setObject, Class classToFind)
			throws IOException {
		for (SerializedClass classObject : setObject.getPlainClassHierarchy()) {
			if (classToFind.getName().equals(classObject.getName())) {
				return classObject;
			}
		}
		CCSMAssert.fail("Only works on " + classToFind.getName() + " objects!");
		return null;
	}

	/**
	 * Renames the given entity class, reads it from the pool and returns it. The given class must be a
	 * root entity in the pool.
	 */
	@SuppressWarnings("unchecked")
	public static  T serializeRootEntities(SerializedEntityPool entityPool)
			throws AssertionError, IOException, ClassNotFoundException {
		List rootEntities = entityPool.getRootEntities();
		byte[] newValue = SerializedEntitySerializer.serializeToBytes(rootEntities);
		return (T) SerializationUtils.deserializeFromByteArray(newValue);
	}

	/**
	 * Creates a SerializedObject for an ArrayList with the given objects as entries.
	 * 

* {@link #copy(SerializedEntityPool, SerializedEntityBase, SerializedEntityPool)} provides a more * generic way to accomplish this, if in a slightly more indirect way. */ public static SerializedObject createSerializedArrayListObject(List entries, SerializedEntityPool entityPool) throws IOException { ArrayList arrayList = entries.stream().map(WrappedHandle::of) .collect(toCollection(ArrayList::new)); byte[] bytes = SerializationUtils.serializeToByteArray(arrayList); SerializedEntityPool temporaryPool = SerializedEntityParser.parse(bytes); return copy(temporaryPool, getOnlyElement(temporaryPool.getRootEntities(SerializedObject.class)), entityPool); } /** * Reconstructs a {@link SerializedEntityBase} as a {@link Serializable} by serializing it to a * sequence of bytes and then deserializing that using Java's serialization framework. */ public static Serializable reconstructSerializable(SerializedEntityBase serializedEntity) throws IOException, ClassNotFoundException { byte[] bytes = SerializedEntitySerializer.serializeToBytes(List.of(serializedEntity)); return SerializationUtils.deserializeFromByteArray(bytes); } /** * When {@link #copy(SerializedEntityPool, SerializedEntityBase, SerializedEntityPool)} recursively * copies a {@link SerializedObject} from a source to target {@link SerializedEntityPool}, the * copies are assigned new handles in the target pool. This is the correct behavior. However, * occasionally one wants (part of) the copy to refer to an object already in the target * pool. In this case, a {@link WrappedHandle} offers a way out. When a {@link WrappedHandle} is * seen as a {@link SerializedObject} to be copied, instead of copying it like any other object the * target pool, the wrapped handle is simply replaced by the handle of an object (that must already * exist in the target pool!). *

* The primary use case is wrapping an object in some source pool into an object that is cumbersome * to create as a {@link SerializedObject} but easy to create in plain Java: * *

	 * SerializedEntityPool originalPool = ...;
	 * SerializedObject originalObject = ...;
	 *
	 * Set wrapper = Collections.singleton(WrappedHandle.of(originalObject));
	 * byte[] wrapperSerialization = SerializationUtils.serializeToByteArray(wrapper);
	 * SerializedEntityPool temporaryPool = SerializedEntityParser.parse(wrapperSerialization);
	 * SerializedObject serializedWrapper = temporaryPool.getRootEntities().get(0);
	 *
	 * copy(temporaryPool, serializedWrapper, originalPool);
	 * 
* * This adds all necessary entities like the singleton set and its classes to the original pool * without first deserializing and then re-serializing the original object. */ public static class WrappedHandle implements Serializable { private static final long serialVersionUID = 1; @SuppressWarnings("unused") // Read via getFieldValue in unwrap private final int handle; private WrappedHandle(int handle) { this.handle = handle; } /** Wraps the handle of the given {@linkplain SerializedObjectBase serialized object}. */ public static WrappedHandle of(SerializedObjectBase wrappedObject) { return new WrappedHandle(wrappedObject.getHandle()); } /** * Unwraps a handle, assuming the given {@link SerializedObject} is a serialized instance of * {@link WrappedHandle} in the given {@link SerializedEntityPool}. */ private static SerializedObjectBase unwrap(SerializedObject serializedWrapper, SerializedEntityPool entityPool) throws IOException { int wrappedHandle = (int) serializedWrapper.getFieldValue("handle"); return entityPool.getEntity(wrappedHandle, SerializedObjectBase.class); } /** * Determines whether the given {@linkplain SerializedEntityBase serialized entity} is a serialized * instance of {@link WrappedHandle}. */ public static boolean isWrappedHandle(SerializedEntityBase serializedEntity) throws IOException { if (!(serializedEntity instanceof SerializedObject)) { return false; } SerializedObject serializedObject = (SerializedObject) serializedEntity; SerializedClass serializedClass = serializedObject.getSerializedClass(); return serializedClass.getName().equals(WrappedHandle.class.getName()); } } /** * Copies an entity from one {@link SerializedEntityPool} to another. This method has some * limitations: *
    *
  1. It does not support all kinds of {@link SerializedEntityBase} yet.
  2. *
  3. It makes some assumptions about how custom serialization logic (i.e., a {@code writeObject} * method) operates. In particular, it assumes that any {@code int}s in * {@linkplain SerializedClassValues#getPreFieldData() pre-} or * {@linkplain SerializedClassValues#getPostFieldData() post-field data} encode an object handle. * This is true for common collection classes like {@link java.util.HashMap}.
  4. *
* If you are using this method for a data structure, it is hence essential to * check with a unit test that copying the exact data structure in question works correctly. * * @throws UnsupportedOperationException * If copying (parts of) the entity is not yet supported or the entity violates some * assumptions we (have to) make. */ public static SerializedEntityBase copy(SerializedEntityPool sourcePool, SerializedEntityBase sourceEntity, SerializedEntityPool targetPool) throws IOException { if (WrappedHandle.isWrappedHandle(sourceEntity)) { return WrappedHandle.unwrap((SerializedObject) sourceEntity, targetPool); } else if (sourceEntity instanceof SerializedObject) { return copy(sourcePool, (SerializedObject) sourceEntity, targetPool); } else if (sourceEntity instanceof SerializedStringObject) { SerializedStringObject sourceString = (SerializedStringObject) sourceEntity; String sourceStringValue = sourceString.getValue(); return new SerializedStringObject(sourceStringValue, targetPool); } else if (sourceEntity instanceof SerializedEnumLiteral) { return copy(sourcePool, (SerializedEnumLiteral) sourceEntity, targetPool); } else if (sourceEntity instanceof SerializedArrayObject) { return copy(sourcePool, (SerializedArrayObject) sourceEntity, targetPool); } else if (sourceEntity instanceof SerializedClass) { return copy(sourcePool, (SerializedClass) sourceEntity, targetPool); } else { throw new UnsupportedOperationException("Unsupported entity kind: " + sourceEntity.getClass()); } } private static SerializedObject copy(SerializedEntityPool sourcePool, SerializedObject sourceObject, SerializedEntityPool targetPool) throws IOException { SerializedClass sourceClass = sourceObject.getSerializedClass(); List sourceClassHierarchy = sourceObject.getPlainClassHierarchy(); if (sourceClassHierarchy.size() != sourceObject.getFieldSetCount()) { throw new UnsupportedOperationException( "We assume that every non-Object superclass contributes a field set"); } List targetFieldSets = new ArrayList<>(sourceObject.getFieldSetCount()); for (int i = 0; i < sourceObject.getFieldSetCount(); i++) { targetFieldSets.add( copyFieldSet(sourcePool, sourceClassHierarchy.get(i), sourceObject.getFieldSet(i), targetPool)); } SerializedEntityBase targetClass = copy(sourcePool, sourceClass, targetPool); int targetClassHandle = targetClass.getHandle(); return new SerializedObject(targetClassHandle, targetFieldSets, targetPool); } private static SerializedEnumLiteral copy(SerializedEntityPool sourcePool, SerializedEnumLiteral sourceEnumLiteral, SerializedEntityPool targetPool) throws IOException { SerializedClass sourceEnumClass = sourceEnumLiteral.getSerializedClass(); SerializedClass targetEnumClass = copy(sourcePool, sourceEnumClass, targetPool); return new SerializedEnumLiteral(targetPool, targetEnumClass.getHandle(), sourceEnumLiteral.getLiteralName()); } private static SerializedArrayObject copy(SerializedEntityPool sourcePool, SerializedArrayObject sourceArrayObject, SerializedEntityPool targetPool) throws IOException { SerializedFieldBase elementType = sourceArrayObject.getElementType(); SerializedClass sourceArrayClass = sourceArrayObject.getSerializedClass(); SerializedClass targetClass = copy(sourcePool, sourceArrayClass, targetPool); SerializedArrayObject targetArrayObject = new SerializedArrayObject(targetPool, targetClass.getHandle(), elementType); for (int i = 0; i < sourceArrayObject.size(); i++) { if (elementType instanceof SerializedComplexFieldBase) { int sourceElementHandle = (int) sourceArrayObject.getValue(i); int targetElementHandle = copyHandle(sourcePool, sourceElementHandle, targetPool); targetArrayObject.addValue(targetElementHandle); } else { Object sourceElementValue = sourceArrayObject.getValue(i); targetArrayObject.addValue(sourceElementValue); } } return targetArrayObject; } private static SerializedClass copy(SerializedEntityPool sourcePool, SerializedClass sourceClass, SerializedEntityPool targetPool) throws IOException { SerializedClass targetClass = targetPool.findClass(sourceClass.getName()); if (targetClass != null) { return targetClass; } targetClass = new SerializedClass(sourceClass.getName(), sourceClass.getSerialVersionUid(), sourceClass.getClassDescriptionFlags(), targetPool); SerializedClass sourceSuperClass = sourcePool.getEntity(sourceClass.getSuperClassHandle(), SerializedClass.class); if (sourceSuperClass != null) { SerializedEntityBase targetSuperClass = copy(sourcePool, sourceSuperClass, targetPool); targetClass.setSuperClassHandle(targetSuperClass.getHandle()); } for (SerializedFieldBase sourceClassField : sourceClass.getFields()) { targetClass.addField(sourceClassField); } return targetClass; } private static SerializedClassValues copyFieldSet(SerializedEntityPool sourcePool, SerializedClass sourceClass, SerializedClassValues sourceFieldSet, SerializedEntityPool targetPool) throws IOException { UnmodifiableList sourceClassFields = sourceClass.getFields(); if (sourceClassFields.size() != sourceFieldSet.getSize()) { throw new UnsupportedOperationException("We assume that every field is represented in the field set"); } SerializedClassValues targetFieldSet = new SerializedClassValues(sourceFieldSet.getSize()); appendData(sourcePool, sourceFieldSet, true, targetPool, targetFieldSet); for (int i = 0; i < sourceFieldSet.getSize(); i++) { SerializedFieldBase sourceClassField = sourceClassFields.get(i); if (sourceClassField instanceof SerializedComplexFieldBase) { int sourceFieldValueHandle = (int) sourceFieldSet.getValue(i); int targetFieldValueHandle = copyHandle(sourcePool, sourceFieldValueHandle, targetPool); targetFieldSet.setValue(i, targetFieldValueHandle); } else { Object sourceFieldValue = sourceFieldSet.getValue(i); targetFieldSet.setValue(i, sourceFieldValue); } } appendData(sourcePool, sourceFieldSet, false, targetPool, targetFieldSet); return targetFieldSet; } private static void appendData(SerializedEntityPool sourcePool, SerializedClassValues sourceClassValues, boolean isPreFields, SerializedEntityPool targetPool, SerializedClassValues targetClassValues) throws IOException, UnsupportedOperationException { List sourceFieldData = getFieldData(sourceClassValues, isPreFields); if (sourceFieldData == null) { return; } for (Object sourceFieldDatum : sourceFieldData) { if (sourceFieldDatum instanceof Integer) { // This assumes that any Integer references an object, which is true for the // custom writeObject logic used by common types like ArrayList or HashMap int referencedSourceObjectHandle = (int) sourceFieldDatum; int referencedTargetObjectHandle = copyHandle(sourcePool, referencedSourceObjectHandle, targetPool); targetClassValues.appendData(referencedTargetObjectHandle, isPreFields); } else if (sourceFieldDatum instanceof byte[]) { byte[] rawSourceData = (byte[]) sourceFieldDatum; targetClassValues.appendData(rawSourceData, isPreFields); } else { throw new UnsupportedOperationException("Unsupported field datum kind: " + sourceFieldDatum.getClass()); } } } private static int copyHandle(SerializedEntityPool sourcePool, int sourceHandle, SerializedEntityPool targetPool) throws IOException { if (sourceHandle == 0) { return 0; } SerializedObjectBase sourceObject = sourcePool.getEntity(sourceHandle, SerializedObjectBase.class); SerializedEntityBase targetObject = copy(sourcePool, sourceObject, targetPool); return targetObject.getHandle(); } private static @Nullable List getFieldData(SerializedClassValues classValues, boolean isPreFields) { if (isPreFields) { return classValues.getPreFieldData(); } else { return classValues.getPostFieldData(); } } }