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}