org.conqat.lib.commons.serialization.SerializedEntityPool Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of teamscale-lib-commons Show documentation
Show all versions of teamscale-lib-commons Show documentation
Provides common utility functions
/*
* 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();
}
}