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

org.conqat.lib.commons.serialization.SerializedEntityPool 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;

import java.io.IOException;
import java.io.ObjectStreamConstants;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

import org.conqat.lib.commons.assertion.CCSMAssert;
import org.conqat.lib.commons.collections.CollectionUtils;
import org.conqat.lib.commons.serialization.classes.SerializedClass;
import org.conqat.lib.commons.serialization.classes.SerializedFieldBase;
import org.conqat.lib.commons.serialization.objects.SerializedObject;
import org.conqat.lib.commons.string.StringUtils;

/**
 * A class that manages a set of serialized entities (objects and classes) as well as secondary
 * entities referenced by them.
 */
public class SerializedEntityPool {

	/** The handle representing the null value. */
	public static final int NULL_HANDLE = 0;

	/**
	 * Start value for handles as defined in the
	 * spec.
	 */
	/* package */static final int START_HANDLE = ObjectStreamConstants.baseWireHandle;

	/**
	 * Class name prefix for arrays of non-primitive java classes.
	 */
	private static final String NON_PRIMITIVE_ARRAY_PREFIX = "[L";

	/** Counter used to determine the next entity handle. */
	private int nextHandle = START_HANDLE;

	/** The existing entities in this pool (both root and referenced). */
	private final Map entities = new HashMap<>();

	/**
	 * The handles of root entities, i.e. entities that are actual part of a serialization stream and
	 * not only indirectly referenced.
	 */
	private final List rootHandles = new ArrayList<>();

	/**
	 * The classes encountered. They are additionally kept in this list to allow for faster lookup. We
	 * do not use a map (by name) as the name of a class may be changed programmatically.
	 */
	private final List classEntities = new ArrayList<>();

	/** Resets the pool to an empty state. */
	public void reset() {
		nextHandle = START_HANDLE;
		entities.clear();
		rootHandles.clear();
		classEntities.clear();
	}

	/**
	 * Adds an entity to this pool and returns the new handle assigned to it.
	 */
	/* package */int addEntity(SerializedEntityBase entity) {
		if (entity instanceof SerializedClass) {
			classEntities.add((SerializedClass) entity);
		}

		int handle = nextHandle++;
		entities.put(handle, entity);
		return handle;
	}

	/**
	 * Removes an entity from this pool.
	 */
	public void removeEntity(SerializedEntityBase entity) {
		if (entity instanceof SerializedClass) {
			classEntities.remove((SerializedClass) entity);
		}

		entities.remove(entity.handle);
	}

	/**
	 * Removes all entities of the given type from the pool and also removes the class.
	 */
	public void removeEntitiesOfType(String typeToRemove) throws IOException {
		SerializedClass serializedClass = findClass(typeToRemove);
		if (serializedClass == null) {
			return;
		}
		entities.remove(serializedClass.getHandle());
		for (SerializedObject serializedObject : CollectionUtils.filter(getEntities(SerializedObject.class),
				entity -> entity.getClassHandle() == serializedClass.getHandle())) {
			entities.remove(serializedObject.getHandle());
		}
	}

	/**
	 * Removes the SerializedClass definition if there are no instances of that class anymore in the
	 * pool and no other classes that refer to it as superclass.
	 */
	public void removeClassIfUnused(String typeToRemove) throws IOException {
		SerializedClass serializedClass = findClass(typeToRemove);
		if (serializedClass == null) {
			return;
		}
		boolean hasInstances = CollectionUtils.anyMatch(getEntities(SerializedObject.class),
				entity -> entity.getClassHandle() == serializedClass.getHandle());
		if (hasInstances) {
			return;
		}
		boolean hasSubclasses = CollectionUtils.anyMatch(getEntities(SerializedClass.class),
				entity -> entity.getSuperClassHandle() == serializedClass.getHandle());
		if (hasSubclasses) {
			return;
		}
		classEntities.remove(serializedClass);
		entities.remove(serializedClass.getHandle());
	}

	/** Returns whether the given handle is known to this pool. */
	public boolean containsHandle(int handle) {
		return entities.containsKey(handle);
	}

	/**
	 * Returns an entity by handle. Throws an exception if the handle is not known.
	 */
	private SerializedEntityBase getEntity(int handle) throws IOException {
		if (handle == NULL_HANDLE) {
			return null;
		}

		SerializedEntityBase entity = entities.get(handle);
		if (entity == null) {
			throw new IOException("No entity registered for handle " + handle);
		}
		return entity;
	}

	/**
	 * Returns an entity of specific expected type. Throws an exception if the handle is not known or of
	 * different type. Returns null if the given handle points to null.
	 */
	public  T getEntity(int handle, Class expectedType) throws IOException {
		SerializedEntityBase entity = getEntity(handle);
		if (entity == null) {
			return null;
		}
		if (!expectedType.isInstance(entity)) {
			throw new IOException(
					"Expected type " + expectedType + " for handle " + handle + " but was " + entity.getClass());
		}
		return expectedType.cast(entity);
	}

	/** Return all entities anywhere in the object graph of the given type. */
	public  List getEntities(Class expectedType) throws IOException {
		return getEntitiesForType(entities.values(), expectedType);
	}

	/** Return all root entities of the given type. */
	public  List getRootEntities(Class expectedType) throws IOException {
		return getEntitiesForType(getRootEntities(), expectedType);
	}

	@SuppressWarnings("unchecked")
	private static  List getEntitiesForType(
			Collection entities, Class expectedType) {
		List returnEntities = new ArrayList<>();
		for (SerializedEntityBase entity : entities) {
			if (expectedType.isInstance(entity)) {
				returnEntities.add((T) entity);
			}
		}
		return returnEntities;
	}

	/** Returns the root entities in this pool. */
	public List getRootEntities() throws IOException {
		List rootEntities = new ArrayList<>();
		for (int handle : rootHandles) {
			rootEntities.add(getEntity(handle));
		}
		return rootEntities;
	}

	/** Adds a handle as root. */
	public void registerRootHandle(int handle) throws IOException {
		if (handle != NULL_HANDLE && !containsHandle(handle)) {
			throw new IOException("Can not register unknown handle " + handle + " as root!");
		}
		rootHandles.add(handle);
	}

	/** Attempts to find a class by its name. Returns null if not found. */
	public SerializedClass findClass(String name) {
		for (SerializedClass classEntity : classEntities) {
			if (classEntity.getName().equals(name)) {
				return classEntity;
			}
		}
		return null;
	}

	/**
	 * Returns the {@link SerializedClass} entities that are contained in the entity pool.
	 */
	public List getClassEntities() {
		return classEntities;
	}

	/** Renames the given class if found. */
	public void renameClass(String oldName, String newName) {
		SerializedClass serializedClass = findClass(oldName);
		// In some strange cases we might have multiple instances of a SerializedClass
		// within an object (TS-37612).
		while (serializedClass != null) {
			serializedClass.setName(newName);
			serializedClass = findClass(oldName);
		}
	}

	/**
	 * Asserts that all serialized class names are matched by the given function.
	 */
	public void assertAllPlainClassNamesMatch(Function matcher, String message) {
		for (SerializedClass classEntity : classEntities) {
			String plainClassName = StringUtils.stripPrefix(classEntity.getName(), NON_PRIMITIVE_ARRAY_PREFIX);
			if (!matcher.apply(plainClassName)) {
				CCSMAssert.fail(message + ": " + plainClassName);
			}
		}
	}

	/**
	 * Gets the class with the given name from this entity pool or creates a new one using the given
	 * fields.
	 */
	public int getOrCreateClass(String className, SerializedFieldBase... fields) {
		SerializedClass clazz = findClass(className);
		if (clazz == null) {
			clazz = SerializedClass.createClass(this, className);
			for (SerializedFieldBase field : fields) {
				clazz.addField(field);
			}
		}
		return clazz.getHandle();
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy