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.objects; 018 019import java.io.DataInputStream; 020import java.io.DataOutputStream; 021import java.io.Externalizable; 022import java.io.IOException; 023import java.io.ObjectOutputStream; 024import java.io.ObjectStreamConstants; 025import java.io.Serializable; 026import java.util.ArrayList; 027import java.util.Collections; 028import java.util.List; 029 030import org.conqat.lib.commons.serialization.SerializedEntityParser; 031import org.conqat.lib.commons.serialization.SerializedEntityPool; 032import org.conqat.lib.commons.serialization.SerializedEntitySerializer; 033import org.conqat.lib.commons.serialization.classes.SerializedClass; 034import org.conqat.lib.commons.serialization.classes.SerializedFieldBase; 035import org.conqat.lib.commons.serialization.classes.SerializedPrimitiveFieldBase; 036 037/** 038 * The values of an object corresponding to a single class, i.e. each object has 039 * multiple class values, one for each class in its hierarchy. This can store 040 * values for all declared (non-transient) fields of a class as well as 041 * additional data. The presence of both the fields and the additional data are 042 * optional depending on the serialization mechanism used (default 043 * {@link Serializable}, serializable with custom read/write methods, 044 * {@link Externalizable}). 045 * 046 * The old externalization protocol from Java versions before 1.2 is not 047 * supported by this class. 048 */ 049public class SerializedClassValues { 050 051 /** 052 * Chunks of raw data (byte arrays) found before the field values. These are 053 * written by custom serialization or externalizable classes. If no fields are 054 * written, data is only put into {@link #preFieldData}. This list may be null 055 * to indicate no data. 056 */ 057 private List<Object> preFieldData = null; 058 059 /** 060 * The values for the fields of the corresponding class. This may be null if the 061 * fields are not stored (e.g. custom read/write or {@link Externalizable}). 062 */ 063 private List<Object> fieldValues = null; 064 065 /** 066 * Chunks of raw data (byte arrays) found after the field values. These are 067 * written by custom serialization or externalizable classes. If no fields are 068 * written, data is only put into {@link #preFieldData}. This list may be null 069 * to indicate no data. 070 */ 071 private List<Object> postFieldData = null; 072 073 /** Constructor. */ 074 public SerializedClassValues(int size) { 075 fieldValues = new ArrayList<>(Collections.nCopies(size, null)); 076 } 077 078 /** Constructor. */ 079 public SerializedClassValues(SerializedClass serializedClass, DataInputStream din, SerializedEntityParser parser) 080 throws IOException { 081 if (serializedClass.isSerializable()) { 082 if (serializedClass.hasWriteMethod()) { 083 parseWithData(serializedClass, din, parser, true); 084 } else { 085 parseFieldValues(serializedClass, din, parser); 086 } 087 } else if (serializedClass.isExternalizable()) { 088 if (!serializedClass.hasBlockData()) { 089 throw new IOException( 090 "Externalizable with externalContents (old serialization protocol) not supported!"); 091 } 092 parseWithData(serializedClass, din, parser, false); 093 } else { 094 throw new IOException( 095 "Invalid class encountered: neither serializable nor externalizable: " + serializedClass.getName()); 096 } 097 } 098 099 /** 100 * Parses raw data and field values. There seems to be a gap in the 101 * specification for {@link Serializable} classes with custom read/write 102 * methods. If the writeObject() methods calls 103 * {@link ObjectOutputStream#defaultWriteObject()} it seems we have no way of 104 * deciding whether a data block or field values start. The implementation 105 * probably copes with this by calling the readObject() method and expecting it 106 * to work just right. As calling a method is not an option for us, we use the 107 * following heuristic. If the next byte is one of the block data, string, or 108 * object constants, we interpret it as such, otherwise we assume field data. 109 * This heuristic can make errors as the field data might actually start with on 110 * of the mentioned constants. This is unlikely but possible. 111 * 112 * @param mayContainFields 113 * if this is false, only raw data (and no field values) are 114 * expected. 115 */ 116 private void parseWithData(SerializedClass serializedClass, DataInputStream din, SerializedEntityParser parser, 117 boolean mayContainFields) throws IOException { 118 boolean isPreFields = true; 119 120 while (true) { 121 din.mark(1); 122 byte next = din.readByte(); 123 124 switch (next) { 125 case ObjectStreamConstants.TC_BLOCKDATA: 126 int shortBlockLength = din.readUnsignedByte(); 127 readBlockData(din, shortBlockLength, isPreFields); 128 break; 129 case ObjectStreamConstants.TC_BLOCKDATALONG: 130 int longBlockLength = din.readInt(); 131 readBlockData(din, longBlockLength, isPreFields); 132 break; 133 case ObjectStreamConstants.TC_ENDBLOCKDATA: 134 return; 135 case ObjectStreamConstants.TC_STRING: 136 case ObjectStreamConstants.TC_LONGSTRING: 137 case ObjectStreamConstants.TC_OBJECT: 138 case ObjectStreamConstants.TC_ENUM: 139 case ObjectStreamConstants.TC_REFERENCE: 140 case ObjectStreamConstants.TC_ARRAY: 141 case ObjectStreamConstants.TC_NULL: 142 din.reset(); 143 appendData(parser.parseContent(), isPreFields); 144 break; 145 default: 146 // assume fields 147 if (!mayContainFields) { 148 throw new IOException("No more fields expected at this time!"); 149 } 150 mayContainFields = false; 151 isPreFields = false; 152 153 din.reset(); 154 parseFieldValues(serializedClass, din, parser); 155 } 156 } 157 } 158 159 /** 160 * Reads block data of given length. 161 * 162 * @param isPreFields 163 * determines whether to store into {@link #preFieldData} or 164 * {@link #postFieldData}. 165 */ 166 private void readBlockData(DataInputStream din, int length, boolean isPreFields) throws IOException { 167 byte[] data = new byte[length]; 168 din.readFully(data); 169 appendData(data, isPreFields); 170 } 171 172 /** 173 * Appends data to either {@link #preFieldData} or {@link #postFieldData}. 174 */ 175 private void appendData(Object data, boolean isPreFields) { 176 if (isPreFields) { 177 if (preFieldData == null) { 178 preFieldData = new ArrayList<>(); 179 } 180 preFieldData.add(data); 181 } else { 182 if (postFieldData == null) { 183 postFieldData = new ArrayList<>(); 184 } 185 postFieldData.add(data); 186 } 187 } 188 189 /** 190 * Reads the values for the fields of the class into {@link #fieldValues}. 191 */ 192 private void parseFieldValues(SerializedClass serializedClass, DataInputStream din, SerializedEntityParser parser) 193 throws IOException { 194 fieldValues = new ArrayList<>(); 195 for (SerializedFieldBase field : serializedClass.getFields()) { 196 fieldValues.add(field.readValue(din, parser)); 197 } 198 } 199 200 /** Returns the given value. */ 201 public Object getValue(int index) { 202 return fieldValues.get(index); 203 } 204 205 /** Sets the given value. */ 206 public void setValue(int index, Object value) { 207 while (index >= fieldValues.size()) { 208 fieldValues.add(null); 209 } 210 fieldValues.set(index, value); 211 } 212 213 /** Removes the value at given index. */ 214 public void removeValue(int index) { 215 fieldValues.remove(index); 216 } 217 218 /** Serializes the class values. */ 219 public void serialize(SerializedClass serializedClass, SerializedEntityPool pool, DataOutputStream dos, 220 SerializedEntitySerializer serializer) throws IOException { 221 writeRawData(preFieldData, pool, serializer); 222 223 if (fieldValues != null) { 224 serializeFieldValues(serializedClass, pool, dos, serializer); 225 } 226 227 writeRawData(postFieldData, pool, serializer); 228 229 if (!serializedClass.isSerializable() || serializedClass.hasWriteMethod()) { 230 dos.writeByte(ObjectStreamConstants.TC_ENDBLOCKDATA); 231 } 232 } 233 234 /** Serializes the field values. */ 235 private void serializeFieldValues(SerializedClass serializedClass, SerializedEntityPool pool, DataOutputStream dos, 236 SerializedEntitySerializer serializer) throws IOException { 237 // during serialization, the primitive types are required to come 238 // first, as otherwise we get an exception 239 int index = 0; 240 for (SerializedFieldBase field : serializedClass.getFields()) { 241 if (field instanceof SerializedPrimitiveFieldBase) { 242 Object value = fieldValues.get(index); 243 field.writeValue(value, pool, dos, serializer); 244 } 245 index += 1; 246 } 247 248 index = 0; 249 for (SerializedFieldBase field : serializedClass.getFields()) { 250 if (!(field instanceof SerializedPrimitiveFieldBase)) { 251 Object value = fieldValues.get(index); 252 field.writeValue(value, pool, dos, serializer); 253 } 254 index += 1; 255 } 256 } 257 258 /** 259 * Writes the raw data to the stream, which consists of both byte arrays and 260 * handles. The provided list may be null. 261 */ 262 private static void writeRawData(List<Object> blockData, SerializedEntityPool pool, 263 SerializedEntitySerializer serializer) throws IOException { 264 if (blockData == null) { 265 return; 266 } 267 268 serializer.serializeAnnotationList(blockData, pool); 269 } 270 271 /** Returns whether field data is present. */ 272 public boolean hasFieldValues() { 273 return fieldValues != null; 274 } 275 276 /** Returns the pre field data. */ 277 public List<Object> getPreFieldData() { 278 return preFieldData; 279 } 280 281 /** Returns the post field data. */ 282 public List<Object> getPostFieldData() { 283 return postFieldData; 284 } 285 286}