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}