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.BufferedInputStream; 020import java.io.ByteArrayInputStream; 021import java.io.DataInputStream; 022import java.io.EOFException; 023import java.io.IOException; 024import java.io.InputStream; 025import java.io.ObjectStreamConstants; 026 027import org.conqat.lib.commons.assertion.CCSMAssert; 028import org.conqat.lib.commons.serialization.classes.SerializedClass; 029import org.conqat.lib.commons.serialization.classes.SerializedProxyClass; 030import org.conqat.lib.commons.serialization.objects.SerializedArrayObject; 031import org.conqat.lib.commons.serialization.objects.SerializedClassObject; 032import org.conqat.lib.commons.serialization.objects.SerializedEnumLiteral; 033import org.conqat.lib.commons.serialization.objects.SerializedObject; 034import org.conqat.lib.commons.serialization.objects.SerializedStringObject; 035 036/** 037 * Parses serialized entities (objects and classes). 038 */ 039public class SerializedEntityParser { 040 041 /** The stream to read from. */ 042 private final DataInputStream din; 043 044 /** The pool we use to store read objects into. */ 045 private final SerializedEntityPool pool = new SerializedEntityPool(); 046 047 /** Constructor. */ 048 private SerializedEntityParser(DataInputStream din) { 049 CCSMAssert.isTrue(din.markSupported(), "Our parser requires an input stream that supports marking! " 050 + "However, all calling methods should ensure this."); 051 052 this.din = din; 053 } 054 055 /** Performs the actual parsing and returns the pool of parsed objects. */ 056 private SerializedEntityPool parse() throws IOException { 057 short magic = din.readShort(); 058 if (magic != ObjectStreamConstants.STREAM_MAGIC) { 059 throw new IOException("Magic header not found!"); 060 } 061 062 short version = din.readShort(); 063 if (version != ObjectStreamConstants.STREAM_VERSION) { 064 throw new IOException("Unexpected stream version!"); 065 } 066 067 try { 068 while (true) { 069 pool.registerRootHandle(parseContent()); 070 } 071 } catch (EOFException e) { 072 // this exception is the intended mechanism of communicating the end 073 // of the stream. So all is ok. 074 } 075 076 return pool; 077 } 078 079 /** 080 * Parses the <code>content</code> part of the stream. Returns a handle for the 081 * created object. 082 */ 083 public int parseContent() throws IOException { 084 byte next = din.readByte(); 085 switch (next) { 086 // these are the options for "object" 087 case ObjectStreamConstants.TC_OBJECT: 088 return parseObject(); 089 case ObjectStreamConstants.TC_CLASS: 090 return parseClass(); 091 case ObjectStreamConstants.TC_ARRAY: 092 return parseArray(); 093 case ObjectStreamConstants.TC_STRING: 094 return parseString(false); 095 case ObjectStreamConstants.TC_LONGSTRING: 096 return parseString(true); 097 case ObjectStreamConstants.TC_ENUM: 098 return parseEnum(); 099 case ObjectStreamConstants.TC_CLASSDESC: 100 return parsePlainClassDesc(); 101 case ObjectStreamConstants.TC_PROXYCLASSDESC: 102 return parseProxyClassDesc(); 103 case ObjectStreamConstants.TC_REFERENCE: 104 return readHandle(); 105 case ObjectStreamConstants.TC_NULL: 106 return SerializedEntityPool.NULL_HANDLE; 107 case ObjectStreamConstants.TC_EXCEPTION: 108 // not sure how to create them; maybe only relevant in RMI? 109 throw new IOException("Top-level exceptions are not supported!"); 110 case ObjectStreamConstants.TC_RESET: 111 pool.reset(); 112 return SerializedEntityPool.NULL_HANDLE; 113 case ObjectStreamConstants.TC_BLOCKDATA: 114 case ObjectStreamConstants.TC_BLOCKDATALONG: 115 // although the grammar allows them at this point, we could find no 116 // way block data can happen top-level 117 throw new IOException("Unexpected block data at top-level!"); 118 default: 119 throw new IOException("Unexpected value for next: " + next); 120 } 121 } 122 123 /** 124 * Parses the <code>newClass</code> part of the stream. Returns the handle of 125 * the object. 126 */ 127 private int parseClass() throws IOException { 128 return new SerializedClassObject(pool, parseClassDesc()).getHandle(); 129 } 130 131 /** 132 * Parses the <code>newString</code> of the stream. Argument determines whether 133 * to parse a long string. Returns the handle of the object. 134 */ 135 private int parseString(boolean longString) throws IOException { 136 return new SerializedStringObject(din, pool, longString).getHandle(); 137 } 138 139 /** 140 * Parses the <code>newObject</code> part of the stream. Returns the handle of 141 * the object. 142 */ 143 private int parseObject() throws IOException { 144 return new SerializedObject(din, pool, this, parseClassDesc()).getHandle(); 145 } 146 147 /** 148 * Parses the <code>newArray</code> part of the stream. Returns the handle of 149 * the array object. 150 */ 151 private int parseArray() throws IOException { 152 return new SerializedArrayObject(din, pool, this, parseClassDesc()).getHandle(); 153 } 154 155 /** 156 * Parses the <code>newEnum</code> part of the stream. Returns the handle of the 157 * enum literal. 158 */ 159 private int parseEnum() throws IOException { 160 return new SerializedEnumLiteral(pool, this, parseClassDesc()).getHandle(); 161 } 162 163 /** 164 * Parses the <code>classDesc</code> part of the stream. Returns the handle of 165 * the class. 166 */ 167 public int parseClassDesc() throws IOException { 168 byte next = din.readByte(); 169 switch (next) { 170 case ObjectStreamConstants.TC_CLASSDESC: 171 return parsePlainClassDesc(); 172 case ObjectStreamConstants.TC_PROXYCLASSDESC: 173 return parseProxyClassDesc(); 174 case ObjectStreamConstants.TC_NULL: 175 return SerializedEntityPool.NULL_HANDLE; 176 case ObjectStreamConstants.TC_REFERENCE: 177 return readHandle(); 178 default: 179 throw new IOException("Unexpected value for next: " + next); 180 } 181 } 182 183 /** 184 * Parses the "normal" part of a <code>newClassDesc</code> part of the stream. 185 * Returns the handle of the class. 186 */ 187 private int parsePlainClassDesc() throws IOException { 188 return new SerializedClass(din, pool, this).getHandle(); 189 } 190 191 /** 192 * Parses the proxy part of a <code>newClassDesc</code> part of the stream. 193 * Returns the handle of the class. 194 */ 195 private int parseProxyClassDesc() throws IOException { 196 return new SerializedProxyClass(din, pool, this).getHandle(); 197 } 198 199 /** Reads a handle to a previously read object from the stream. */ 200 private int readHandle() throws IOException { 201 int handle = din.readInt(); 202 if (!pool.containsHandle(handle)) { 203 throw new IOException("Handle to unknown object: " + handle); 204 } 205 return handle; 206 } 207 208 /** 209 * Parses an object and ensures that the returned object is a 210 * {@link SerializedStringObject}. 211 */ 212 public SerializedStringObject parseStringObject() throws IOException { 213 return pool.getEntity(parseContent(), SerializedStringObject.class); 214 } 215 216 /** Parses entities from a given data input stream. */ 217 public static SerializedEntityPool parse(DataInputStream din) throws IOException { 218 return new SerializedEntityParser(din).parse(); 219 } 220 221 /** Parses entities from a given input stream. */ 222 public static SerializedEntityPool parse(InputStream in) throws IOException { 223 if (!in.markSupported()) { 224 in = new BufferedInputStream(in); 225 } 226 227 return parse(new DataInputStream(in)); 228 } 229 230 /** Parses entities from a given byte array. */ 231 public static SerializedEntityPool parse(byte[] data) throws IOException { 232 return parse(new ByteArrayInputStream(data)); 233 } 234 235 /** 236 * Returns true if the data represents a serialized object stream that can be 237 * parsed by the {@link SerializedEntityParser}. 238 */ 239 public static boolean isSerializedObjectStream(byte[] data) throws IOException { 240 if (data.length < Short.BYTES) { 241 return false; 242 } 243 try (DataInputStream din = new DataInputStream(new ByteArrayInputStream(data))) { 244 return din.readShort() == ObjectStreamConstants.STREAM_MAGIC; 245 } 246 } 247}