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}