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}