001/*-------------------------------------------------------------------------+ 002| | 003| Copyright 2005-2011 the ConQAT Project | 004| | 005| Licensed under the Apache License, Version 2.0 (the "License"); | 006| you may not use this file except in compliance with the License. | 007| You may obtain a copy of the License at | 008| | 009| http://www.apache.org/licenses/LICENSE-2.0 | 010| | 011| Unless required by applicable law or agreed to in writing, software | 012| distributed under the License is distributed on an "AS IS" BASIS, | 013| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 014| See the License for the specific language governing permissions and | 015| limitations under the License. | 016+-------------------------------------------------------------------------*/ 017package org.conqat.lib.commons.serialization; 018 019import java.io.IOException; 020import java.io.ObjectStreamConstants; 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.HashMap; 024import java.util.List; 025import java.util.Map; 026import java.util.function.Function; 027 028import org.conqat.lib.commons.assertion.CCSMAssert; 029import org.conqat.lib.commons.serialization.classes.SerializedClass; 030import org.conqat.lib.commons.string.StringUtils; 031 032/** 033 * A class that manages a set of serialized entities (objects and classes) as 034 * well as secondary entities referenced by them. 035 */ 036public class SerializedEntityPool { 037 038 /** The handle representing the <code>null</code> value. */ 039 public static final int NULL_HANDLE = 0; 040 041 /** 042 * Start value for handles as defined in the <a href= 043 * "http://docs.oracle.com/javase/6/docs/platform/serialization/spec/protocol.html" 044 * >spec</a>. 045 */ 046 /* package */static final int START_HANDLE = ObjectStreamConstants.baseWireHandle; 047 048 /** 049 * Class name prefix for arrays of non-primitive java classes. 050 */ 051 private static final String NON_PRIMITIVE_ARRAY_PREFIX = "[L"; 052 053 /** Counter used to determine the next entity handle. */ 054 private int nextHandle = START_HANDLE; 055 056 /** The existing entities in this pool (both root and referenced). */ 057 private final Map<Integer, SerializedEntityBase> entities = new HashMap<>(); 058 059 /** 060 * The handles of root entities, i.e. entities that are actual part of a 061 * serialization stream and not only indirectly referenced. 062 */ 063 private final List<Integer> rootHandles = new ArrayList<>(); 064 065 /** 066 * The classes encountered. They are additionally kept in this list to allow for 067 * faster lookup. We do not use a map (by name) as the name of a class may be 068 * changed programmatically. 069 */ 070 private final List<SerializedClass> classEntities = new ArrayList<>(); 071 072 /** Resets the pool to an empty state. */ 073 public void reset() { 074 nextHandle = START_HANDLE; 075 entities.clear(); 076 rootHandles.clear(); 077 classEntities.clear(); 078 } 079 080 /** 081 * Adds an entity to this pool and returns the new handle assigned to it. 082 */ 083 /* package */int addEntity(SerializedEntityBase entity) { 084 if (entity instanceof SerializedClass) { 085 classEntities.add((SerializedClass) entity); 086 } 087 088 int handle = nextHandle++; 089 entities.put(handle, entity); 090 return handle; 091 } 092 093 /** Returns whether the given handle is known to this pool. */ 094 public boolean containsHandle(int handle) { 095 return entities.containsKey(handle); 096 } 097 098 /** 099 * Returns an entity by handle. Throws an exception if the handle is not known. 100 */ 101 private SerializedEntityBase getEntity(int handle) throws IOException { 102 if (handle == NULL_HANDLE) { 103 return null; 104 } 105 106 SerializedEntityBase entity = entities.get(handle); 107 if (entity == null) { 108 throw new IOException("No entity registered for handle " + handle); 109 } 110 return entity; 111 } 112 113 /** 114 * Returns an entity of specific expected type. Throws an exception if the 115 * handle is not known or of different type. 116 */ 117 @SuppressWarnings("unchecked") 118 public <T extends SerializedEntityBase> T getEntity(int handle, Class<T> expectedType) throws IOException { 119 SerializedEntityBase entity = getEntity(handle); 120 if (!expectedType.isInstance(entity)) { 121 throw new IOException( 122 "Expected type " + expectedType + " for handle " + handle + " but was " + entity.getClass()); 123 } 124 return (T) entity; 125 } 126 127 /** Return all entities anywhere in the object graph of the given type. */ 128 public <T extends SerializedEntityBase> List<T> getEntities(Class<T> expectedType) throws IOException { 129 return getEntitiesForType(entities.values(), expectedType); 130 } 131 132 /** Return all root entities of the given type. */ 133 public <T extends SerializedEntityBase> List<T> getRootEntities(Class<T> expectedType) throws IOException { 134 return getEntitiesForType(getRootEntities(), expectedType); 135 } 136 137 @SuppressWarnings("unchecked") 138 private static <T extends SerializedEntityBase> List<T> getEntitiesForType( 139 Collection<? extends SerializedEntityBase> entities, Class<T> expectedType) { 140 List<T> returnEntities = new ArrayList<>(); 141 for (SerializedEntityBase entity : entities) { 142 if (expectedType.isInstance(entity)) { 143 returnEntities.add((T) entity); 144 } 145 } 146 return returnEntities; 147 } 148 149 /** Returns the root entities in this pool. */ 150 public List<SerializedEntityBase> getRootEntities() throws IOException { 151 List<SerializedEntityBase> rootEntities = new ArrayList<>(); 152 for (int handle : rootHandles) { 153 rootEntities.add(getEntity(handle)); 154 } 155 return rootEntities; 156 } 157 158 /** Adds a handle as root. */ 159 public void registerRootHandle(int handle) throws IOException { 160 if (handle != NULL_HANDLE && !containsHandle(handle)) { 161 throw new IOException("Can not register unknown handle " + handle + " as root!"); 162 } 163 rootHandles.add(handle); 164 } 165 166 /** Attempts to find a class by its name. Returns null if not found. */ 167 public SerializedClass findClass(String name) { 168 for (SerializedClass classEntity : classEntities) { 169 if (classEntity.getName().equals(name)) { 170 return classEntity; 171 } 172 } 173 return null; 174 } 175 176 /** Renames the given class if found. */ 177 public void renameClass(String oldName, String newName) { 178 SerializedClass serializedClass = findClass(oldName); 179 if (serializedClass != null) { 180 serializedClass.setName(newName); 181 } 182 } 183 184 /** 185 * Asserts that all serialized class names are matched by the given function. 186 */ 187 public void assertAllPlainClassNamesMatch(Function<String, Boolean> matcher, String message) { 188 for (SerializedClass classEntity : classEntities) { 189 String plainClassName = StringUtils.stripPrefix(classEntity.getName(), NON_PRIMITIVE_ARRAY_PREFIX); 190 if (!matcher.apply(plainClassName)) { 191 CCSMAssert.fail(message + ": " + plainClassName); 192 } 193 } 194 } 195}