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}