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.ByteArrayOutputStream; 020import java.io.DataOutputStream; 021import java.io.IOException; 022import java.io.ObjectStreamConstants; 023import java.io.OutputStream; 024import java.util.HashMap; 025import java.util.IdentityHashMap; 026import java.util.List; 027import java.util.Map; 028 029import org.conqat.lib.commons.assertion.CCSMAssert; 030import org.conqat.lib.commons.serialization.objects.LongStringUtils; 031import org.conqat.lib.commons.serialization.objects.SerializedObjectBase; 032import org.conqat.lib.commons.serialization.objects.SerializedStringObject; 033 034/** 035 * Serializes serialized entities (objects + classes) back to a stream. 036 */ 037public class SerializedEntitySerializer { 038 039 /** Maximal allowed size for short block data. */ 040 private static final int MAX_SHORT_BLOCK_DATA_SIZE = 255; 041 042 /** The stream to write into. */ 043 private final DataOutputStream dos; 044 045 /** Counter used to determine the next entity handle. */ 046 private int nextHandle = SerializedEntityPool.START_HANDLE; 047 048 /** Entities already written together with their handles. */ 049 private final Map<SerializedEntityBase, Integer> entityPool = new IdentityHashMap<>(); 050 051 /** 052 * Strings already written together with their handles. This is kept 053 * separate from the {@link #entityPool} to allow exploitation of 054 * (accidental) equality in strings. 055 */ 056 private final Map<String, Integer> stringPool = new HashMap<>(); 057 058 /** Constructor. */ 059 private SerializedEntitySerializer(DataOutputStream dos) { 060 this.dos = dos; 061 } 062 063 /** Performs the actual serialization. */ 064 private void serialize(List<SerializedEntityBase> entities) throws IOException { 065 dos.writeShort(ObjectStreamConstants.STREAM_MAGIC); 066 dos.writeShort(ObjectStreamConstants.STREAM_VERSION); 067 068 for (SerializedEntityBase entity : entities) { 069 if (entity == null) { 070 dos.writeByte(ObjectStreamConstants.TC_NULL); 071 } else { 072 entity.serialize(dos, this); 073 } 074 } 075 } 076 077 /** Serializes a pool to a data output stream. */ 078 public static void serializeToStream(List<SerializedEntityBase> entities, DataOutputStream dos) throws IOException { 079 new SerializedEntitySerializer(dos).serialize(entities); 080 } 081 082 /** Serializes a pool to an output stream. */ 083 public static void serializeToStream(List<SerializedEntityBase> entities, OutputStream out) throws IOException { 084 DataOutputStream dos = new DataOutputStream(out); 085 serializeToStream(entities, dos); 086 dos.flush(); 087 } 088 089 /** Serializes a pool to a raw array. */ 090 public static byte[] serializeToBytes(List<SerializedEntityBase> entities) throws IOException { 091 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 092 serializeToStream(entities, baos); 093 baos.close(); 094 return baos.toByteArray(); 095 } 096 097 /** Serializes a string as an object. */ 098 public void serializeStringObject(String value) throws IOException { 099 serializeStringObject(value, true); 100 } 101 102 /** Serializes a string as an object. */ 103 public void serializeStringObject(String value, boolean useStringPool) throws IOException { 104 if (useStringPool) { 105 Integer existingHandle = stringPool.get(value); 106 if (existingHandle != null) { 107 dos.writeByte(ObjectStreamConstants.TC_REFERENCE); 108 dos.writeInt(existingHandle); 109 return; 110 } 111 } 112 113 stringPool.put(value, obtainHandle()); 114 if (value.length() <= LongStringUtils.MAX_SHORT_STRING_LENGTH) { 115 dos.writeByte(ObjectStreamConstants.TC_STRING); 116 dos.writeUTF(value); 117 } else { 118 dos.writeByte(ObjectStreamConstants.TC_LONGSTRING); 119 LongStringUtils.writeUTF(value, dos); 120 } 121 } 122 123 /** Writes a raw block of data. */ 124 public void writeBlockData(byte[] data) throws IOException { 125 if (data.length <= MAX_SHORT_BLOCK_DATA_SIZE) { 126 dos.write(ObjectStreamConstants.TC_BLOCKDATA); 127 dos.writeByte(data.length); 128 } else { 129 dos.write(ObjectStreamConstants.TC_BLOCKDATALONG); 130 dos.writeInt(data.length); 131 } 132 dos.write(data); 133 } 134 135 /** 136 * Serializes an annotation list as found in <code>objectAnnotation</code> 137 * and <code>classAnnotation</code>. Note that this has nothing to do with 138 * Java annotations (the language feature), but rather is a list consisting 139 * of raw data (byte arrays) and handles (int). 140 */ 141 public void serializeAnnotationList(List<Object> rawDataList, SerializedEntityPool pool) 142 throws IOException { 143 for (Object rawData : rawDataList) { 144 if (rawData instanceof byte[]) { 145 writeBlockData((byte[]) rawData); 146 } else if (rawData instanceof Integer) { 147 int handle = (Integer) rawData; 148 serializeObject(handle, SerializedObjectBase.class, pool, dos, this); 149 } else { 150 throw new SerializationConsistencyException("Unexpected type in class annotations!"); 151 } 152 } 153 } 154 155 /** 156 * Serializes the object defined by the given handle. Also accepts the null 157 * handle. 158 */ 159 public static void serializeObject(int handle, Class<? extends SerializedEntityBase> expectedType, 160 SerializedEntityPool pool, DataOutputStream dos, SerializedEntitySerializer serializer) throws IOException { 161 if (handle == SerializedEntityPool.NULL_HANDLE) { 162 dos.writeByte(ObjectStreamConstants.TC_NULL); 163 } else { 164 pool.getEntity(handle, expectedType).serialize(dos, serializer); 165 } 166 } 167 168 /** Returns the next handle for a written object. */ 169 private int obtainHandle() { 170 return nextHandle++; 171 } 172 173 /** 174 * Attempts to write a reference to this object if it has been written 175 * before. Returns whether a reference could be written. This may not be 176 * used for strings ({@link SerializedStringObject}). 177 */ 178 public boolean writeReference(SerializedEntityBase entity) throws IOException { 179 CCSMAssert.isFalse(entity instanceof SerializedStringObject, "String are handled in a separate pool!"); 180 181 Integer existingHandle = entityPool.get(entity); 182 if (existingHandle != null) { 183 dos.writeByte(ObjectStreamConstants.TC_REFERENCE); 184 dos.writeInt(existingHandle); 185 return true; 186 } 187 return false; 188 } 189 190 /** Registers an entity for a handle to be referenceable later on. */ 191 public void registerHandle(SerializedEntityBase entity) throws IOException { 192 if (entityPool.containsKey(entity)) { 193 throw new IOException("Duplicate registration of entity " + entity); 194 } 195 196 entityPool.put(entity, obtainHandle()); 197 } 198 199}