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.classes;
018
019import java.io.DataInputStream;
020import java.io.DataOutputStream;
021import java.io.IOException;
022import java.io.ObjectStreamConstants;
023import java.util.ArrayList;
024import java.util.List;
025
026import org.conqat.lib.commons.assertion.CCSMAssert;
027import org.conqat.lib.commons.collections.CollectionUtils;
028import org.conqat.lib.commons.collections.UnmodifiableList;
029import org.conqat.lib.commons.serialization.SerializedEntityParser;
030import org.conqat.lib.commons.serialization.SerializedEntityPool;
031import org.conqat.lib.commons.serialization.SerializedEntitySerializer;
032
033/**
034 * A serialized class.
035 */
036public class SerializedClass extends SerializedClassBase {
037
038        /**
039         * The class description flags used for enums (found by experimentation).
040         */
041        private static final byte ENUM_CLASS_DESCRIPTION_FLAGS = 18;
042
043        /** Fully qualified class name. */
044        private String name;
045
046        /** The serial version uid. */
047        private long serialVersionUid;
048
049        /** The class description flags. */
050        private byte classDescriptionFlags;
051
052        /** The fields of the class. */
053        private List<SerializedFieldBase> fields;
054
055        /** Constructor. */
056        public SerializedClass(DataInputStream din, SerializedEntityPool pool, SerializedEntityParser parser)
057                        throws IOException {
058                super(din, pool, parser);
059        }
060
061        /** Direct constructor. */
062        public SerializedClass(String name, int serialVersionUid, byte classDescriptionFlags, SerializedEntityPool pool) {
063                super(pool);
064                this.name = name;
065                this.serialVersionUid = serialVersionUid;
066                this.classDescriptionFlags = classDescriptionFlags;
067                this.fields = new ArrayList<>();
068        }
069
070        /** {@inheritDoc} */
071        @Override
072        protected void parseClass(DataInputStream din, SerializedEntityPool pool, SerializedEntityParser parser)
073                        throws IOException {
074                this.name = din.readUTF();
075                this.serialVersionUid = din.readLong();
076                this.classDescriptionFlags = din.readByte();
077
078                short fieldCount = din.readShort();
079                fields = new ArrayList<>();
080                for (int i = 0; i < fieldCount; ++i) {
081                        fields.add(readFieldDescription(din, parser));
082                }
083        }
084
085        /** Reads the <code>fieldDesc</code> part of the stream. */
086        private static SerializedFieldBase readFieldDescription(DataInputStream din, SerializedEntityParser parser)
087                        throws IOException {
088                byte next = din.readByte();
089                String fieldName = din.readUTF();
090
091                switch (next) {
092                case SerializedArrayField.TYPE_CODE:
093                        return new SerializedArrayField(fieldName, parser);
094                case SerializedObjectField.TYPE_CODE:
095                        return new SerializedObjectField(fieldName, parser);
096                default:
097                        return SerializedPrimitiveFieldBase.fromTypeCode((char) next, fieldName);
098                }
099        }
100
101        /** {@inheritDoc} */
102        @Override
103        protected void serializeClass(DataOutputStream dos, SerializedEntitySerializer serializer) throws IOException {
104                dos.writeByte(ObjectStreamConstants.TC_CLASSDESC);
105
106                dos.writeUTF(name);
107                dos.writeLong(serialVersionUid);
108                dos.writeByte(classDescriptionFlags);
109
110                dos.writeShort(fields.size());
111
112                // during serialization, the primitive types are required to come first,
113                // as otherwise we get an exception
114                for (SerializedFieldBase field : fields) {
115                        if (field instanceof SerializedPrimitiveFieldBase) {
116                                field.serialize(dos, serializer);
117                        }
118                }
119                for (SerializedFieldBase field : fields) {
120                        if (!(field instanceof SerializedPrimitiveFieldBase)) {
121                                field.serialize(dos, serializer);
122                        }
123                }
124        }
125
126        /** Returns the name. */
127        public String getName() {
128                return name;
129        }
130
131        /** Sets the name. */
132        public void setName(String name) {
133                this.name = name;
134        }
135
136        /** Returns the serial version. */
137        public long getSerialVersionUid() {
138                return serialVersionUid;
139        }
140
141        /** Sets the serial version uid. */
142        public void setSerialVersionUid(long serialVersionUid) {
143                this.serialVersionUid = serialVersionUid;
144        }
145
146        /** Returns the class description flags. */
147        public byte getClassDescriptionFlags() {
148                return classDescriptionFlags;
149        }
150
151        /** Returns the fields of this class. */
152        public UnmodifiableList<SerializedFieldBase> getFields() {
153                return CollectionUtils.asUnmodifiable(fields);
154        }
155
156        /** {@inheritDoc} */
157        @Override
158        public String toString() {
159                return "Plain class " + name;
160        }
161
162        /** Returns the field by name (or null if not found). */
163        public SerializedFieldBase getField(String name) {
164                // we do not use a map to speed up lookup as field names may be changed
165                for (SerializedFieldBase field : fields) {
166                        if (field.getName().equals(name)) {
167                                return field;
168                        }
169                }
170                return null;
171        }
172
173        /** Returns whether this class has a field with the given name. */
174        public boolean containsField(String name) {
175                return getField(name) != null;
176        }
177
178        /**
179         * Adds a field to this class. Note that the instances of this class have to
180         * be adjusted accordingly, otherwise you will get exceptions during
181         * serialization.
182         */
183        public void addField(SerializedFieldBase newField) {
184                CCSMAssert.isTrue(getField(newField.getName()) == null, "Field with this name already exists!");
185                fields.add(newField);
186        }
187
188        /**
189         * Removes the field of given name from this class. Note that the instances
190         * of this class have to be adjusted accordingly, otherwise you will get
191         * exceptions during serialization.
192         */
193        public void removeField(String name) {
194                fields.removeIf(field -> field.getName().equals(name));
195        }
196
197        /** Returns whether this is an externalizable class. */
198        public boolean isExternalizable() {
199                return (classDescriptionFlags & ObjectStreamConstants.SC_EXTERNALIZABLE) != 0;
200        }
201
202        /** Returns whether this is a serializable class. */
203        public boolean isSerializable() {
204                return (classDescriptionFlags & ObjectStreamConstants.SC_SERIALIZABLE) != 0;
205        }
206
207        /**
208         * Returns whether this class has a custom write method (only relevant for
209         * serializable).
210         */
211        public boolean hasWriteMethod() {
212                return (classDescriptionFlags & ObjectStreamConstants.SC_WRITE_METHOD) != 0;
213        }
214
215        /**
216         * Returns whether this class stores block data (only relevant for
217         * externalizable).
218         */
219        public boolean hasBlockData() {
220                return (classDescriptionFlags & ObjectStreamConstants.SC_BLOCK_DATA) != 0;
221        }
222
223        /** Factory method for creating a new enum class. */
224        public static SerializedClass createSimpleEnum(SerializedEntityPool pool, String enumName) {
225                SerializedClass resultClass = new SerializedClass(enumName, 0, ENUM_CLASS_DESCRIPTION_FLAGS, pool);
226
227                SerializedClass enumClass = pool.findClass(Enum.class.getName());
228                if (enumClass == null) {
229                        enumClass = new SerializedClass(Enum.class.getName(), 0, ENUM_CLASS_DESCRIPTION_FLAGS, pool);
230                }
231                resultClass.superClassHandle = enumClass.getHandle();
232
233                return resultClass;
234        }
235
236}