001/* Copyright (c) 2008-2023, Nathan Sweet 002 * All rights reserved. 003 * 004 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 005 * conditions are met: 006 * 007 * - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 008 * - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 009 * disclaimer in the documentation and/or other materials provided with the distribution. 010 * - Neither the name of Esoteric Software nor the names of its contributors may be used to endorse or promote products derived 011 * from this software without specific prior written permission. 012 * 013 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 014 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 015 * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 016 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 017 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 018 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ 019 020package com.github.tommyettinger.kryo.gdx; 021 022import static com.esotericsoftware.kryo.Kryo.*; 023 024import com.badlogic.gdx.utils.Array; 025import com.esotericsoftware.kryo.Kryo; 026import com.esotericsoftware.kryo.Registration; 027import com.esotericsoftware.kryo.Serializer; 028import com.esotericsoftware.kryo.io.Input; 029import com.esotericsoftware.kryo.io.Output; 030 031/** Serializes libGDX {@link Array} objects. 032 * @author Nathan Sweet */ 033public class ArraySerializer extends Serializer<Array> { 034 private boolean elementsCanBeNull = true; 035 private Serializer elementSerializer; 036 private Class elementClass; 037 038 public ArraySerializer() { 039 setAcceptsNull(true); 040 } 041 042 /** @param elementsCanBeNull False if all elements are not null. This saves 1 byte per element if elementClass is set. True if 043 * it is not known (default). */ 044 public void setElementsCanBeNull (boolean elementsCanBeNull) { 045 this.elementsCanBeNull = elementsCanBeNull; 046 } 047 048 /** The concrete class of the collection elements, or null if it is not known. This saves 1-2 bytes per element. Only set to a 049 * non-null value if the elements in the collection are known to all be instances of this class (or null). */ 050 public void setElementClass (Class elementClass) { 051 this.elementClass = elementClass; 052 } 053 054 public Class getElementClass () { 055 return elementClass; 056 } 057 058 /** Sets both {@link #setElementClass(Class)} and {@link #setElementSerializer(Serializer)}. */ 059 public void setElementClass (Class elementClass, Serializer serializer) { 060 this.elementClass = elementClass; 061 this.elementSerializer = serializer; 062 } 063 064 /** The serializer to be used for elements in collection, or null to use the serializer registered with {@link Kryo} for each 065 * element's type. Default is null. */ 066 public void setElementSerializer (Serializer elementSerializer) { 067 this.elementSerializer = elementSerializer; 068 } 069 070 public Serializer getElementSerializer () { 071 return this.elementSerializer; 072 } 073 074 public void write (Kryo kryo, Output output, Array collection) { 075 if (collection == null) { 076 output.writeByte(NULL); 077 return; 078 } 079 080 int length = collection.size; 081 if (length == 0) { 082 output.writeByte(1); 083 writeHeader(kryo, output, collection); 084 return; 085 } 086 087 boolean elementsCanBeNull = this.elementsCanBeNull; 088 Serializer elementSerializer = this.elementSerializer; 089 if (elementSerializer == null) { 090 Class genericClass = kryo.getGenerics().nextGenericClass(); 091 if (genericClass != null && kryo.isFinal(genericClass)) elementSerializer = kryo.getSerializer(genericClass); 092 } 093 try { 094 outer: 095 if (elementSerializer != null) { 096 inner: 097 if (elementsCanBeNull) { 098 for (Object element : collection) { 099 if (element == null) { 100 output.writeVarIntFlag(true, length + 1, true); 101 break inner; 102 } 103 } 104 output.writeVarIntFlag(false, length + 1, true); 105 elementsCanBeNull = false; 106 } else 107 output.writeVarInt(length + 1, true); 108 writeHeader(kryo, output, collection); 109 } else { // Serializer is unknown, check if all elements are the same type. 110 Class elementType = null; 111 boolean hasNull = false; 112 for (Object element : collection) { 113 if (element == null) 114 hasNull = true; 115 else if (elementType == null) 116 elementType = element.getClass(); 117 else if (element.getClass() != elementType) { // Elements are different types. 118 output.writeVarIntFlag(false, length + 1, true); 119 writeHeader(kryo, output, collection); 120 break outer; 121 } 122 } 123 output.writeVarIntFlag(true, length + 1, true); 124 writeHeader(kryo, output, collection); 125 if (elementType == null) { // All elements are null. 126 output.writeByte(NULL); 127 return; 128 } 129 // All elements are the same class. 130 kryo.writeClass(output, elementType); 131 elementSerializer = kryo.getSerializer(elementType); 132 if (elementsCanBeNull) { 133 output.writeBoolean(hasNull); 134 elementsCanBeNull = hasNull; 135 } 136 } 137 138 if (elementSerializer != null) { 139 if (elementsCanBeNull) { 140 for (Object element : collection) 141 kryo.writeObjectOrNull(output, element, elementSerializer); 142 } else { 143 for (Object element : collection) 144 kryo.writeObject(output, element, elementSerializer); 145 } 146 } else { 147 for (Object element : collection) 148 kryo.writeClassAndObject(output, element); 149 } 150 } finally { 151 kryo.getGenerics().popGenericType(); 152 } 153 } 154 155 /** Can be overidden to write data needed for {@link #create(Kryo, Input, Class, int)}. The default implementation does 156 * nothing. */ 157 protected void writeHeader (Kryo kryo, Output output, Array collection) { 158 } 159 160 /** Used by {@link #read(Kryo, Input, Class)} to create the new object. This can be overridden to customize object creation (eg 161 * to call a constructor with arguments), optionally reading bytes written in {@link #writeHeader(Kryo, Output, Array)}. 162 * The default implementation uses {@link Kryo#newInstance(Class)} with special cases for ArrayList and HashSet. */ 163 protected Array<?> create (Kryo kryo, Input input, Class<? extends Array> type, int size) { 164 return new Array<>(size); 165 } 166 167 public Array read (Kryo kryo, Input input, Class<? extends Array> type) { 168 Class elementClass = this.elementClass; 169 Serializer elementSerializer = this.elementSerializer; 170 if (elementSerializer == null) { 171 Class genericClass = kryo.getGenerics().nextGenericClass(); 172 if (genericClass != null && kryo.isFinal(genericClass)) { 173 elementSerializer = kryo.getSerializer(genericClass); 174 elementClass = genericClass; 175 } 176 } 177 178 try { 179 Array collection; 180 int length; 181 boolean elementsCanBeNull = this.elementsCanBeNull; 182 if (elementSerializer != null) { 183 if (elementsCanBeNull) { 184 elementsCanBeNull = input.readVarIntFlag(); 185 length = input.readVarIntFlag(true); 186 } else 187 length = input.readVarInt(true); 188 if (length == 0) return null; 189 190 length--; 191 collection = create(kryo, input, type, length); 192 kryo.reference(collection); 193 194 if (length == 0) return collection; 195 } else { 196 boolean sameType = input.readVarIntFlag(); 197 length = input.readVarIntFlag(true); 198 if (length == 0) return null; 199 200 length--; 201 collection = create(kryo, input, type, length); 202 kryo.reference(collection); 203 204 if (length == 0) return collection; 205 206 if (sameType) { 207 Registration registration = kryo.readClass(input); 208 if (registration == null) { // All elements are null. 209 for (int i = 0; i < length; i++) 210 collection.add(null); 211 kryo.getGenerics().popGenericType(); 212 return collection; 213 } 214 elementClass = registration.getType(); 215 elementSerializer = kryo.getSerializer(elementClass); 216 if (elementsCanBeNull) elementsCanBeNull = input.readBoolean(); 217 } 218 } 219 220 if (elementSerializer != null) { 221 if (elementsCanBeNull) { 222 for (int i = 0; i < length; i++) 223 collection.add(kryo.readObjectOrNull(input, elementClass, elementSerializer)); 224 } else { 225 for (int i = 0; i < length; i++) 226 collection.add(kryo.readObject(input, elementClass, elementSerializer)); 227 } 228 } else { 229 for (int i = 0; i < length; i++) 230 collection.add(kryo.readClassAndObject(input)); 231 } 232 return collection; 233 } finally { 234 kryo.getGenerics().popGenericType(); 235 } 236 } 237 238 /** Used by {@link #copy(Kryo, Array)} to create the new object. This can be overridden to customize object creation, eg 239 * to call a constructor with arguments. The default implementation uses {@link Kryo#newInstance(Class)}. */ 240 protected Array createCopy (Kryo kryo, Array original) { 241 return new Array(original.ordered, original.size); 242 } 243 244 public Array copy (Kryo kryo, Array original) { 245 Array copy = createCopy(kryo, original); 246 kryo.reference(copy); 247 for (Object element : original) 248 copy.add(kryo.copy(element)); 249 return copy; 250 } 251}