Skip to content

Commit 4a41970

Browse files
authored
Add serializers for unmodifiable and synchronized collections (#1154)
* Add serializers for unmodifiable and synchronized collections
1 parent 8cf8e4f commit 4a41970

5 files changed

Lines changed: 567 additions & 1 deletion

File tree

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
/* Copyright (c) 2008-2025, Nathan Sweet
2+
* All rights reserved.
3+
*
4+
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following
5+
* conditions are met:
6+
*
7+
* - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8+
* - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
9+
* disclaimer in the documentation and/or other materials provided with the distribution.
10+
* - Neither the name of Esoteric Software nor the names of its contributors may be used to endorse or promote products derived
11+
* from this software without specific prior written permission.
12+
*
13+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
14+
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
15+
* SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
16+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
17+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
18+
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
19+
20+
package com.esotericsoftware.kryo.serializers;
21+
22+
import com.esotericsoftware.kryo.Kryo;
23+
import com.esotericsoftware.kryo.KryoException;
24+
import com.esotericsoftware.kryo.Serializer;
25+
import com.esotericsoftware.kryo.io.Input;
26+
import com.esotericsoftware.kryo.io.Output;
27+
import com.esotericsoftware.kryo.unsafe.UnsafeUtil;
28+
import com.esotericsoftware.minlog.Log;
29+
30+
import java.util.ArrayList;
31+
import java.util.Arrays;
32+
import java.util.Collection;
33+
import java.util.Collections;
34+
import java.util.HashMap;
35+
import java.util.HashSet;
36+
import java.util.LinkedList;
37+
import java.util.List;
38+
import java.util.Map;
39+
import java.util.Set;
40+
import java.util.SortedMap;
41+
import java.util.SortedSet;
42+
import java.util.TreeMap;
43+
import java.util.TreeSet;
44+
import java.util.function.Function;
45+
46+
/** Serializer for synchronized Collections and Maps created via Collections. */
47+
@SuppressWarnings({"rawtypes", "unchecked"})
48+
public class SynchronizedCollectionSerializers {
49+
50+
private static class Offset {
51+
private static final long SOURCE_COLLECTION_FIELD_OFFSET;
52+
private static final long SOURCE_MAP_FIELD_OFFSET;
53+
54+
static {
55+
String clsName = "java.util.Collections$SynchronizedCollection";
56+
try {
57+
SOURCE_COLLECTION_FIELD_OFFSET = UnsafeUtil.objectFieldOffset(Class.forName(clsName).getDeclaredField("c"));
58+
} catch (Exception e) {
59+
Log.warn("Could not access source collection field in {}", clsName);
60+
throw new KryoException(e);
61+
}
62+
clsName = "java.util.Collections$SynchronizedMap";
63+
try {
64+
SOURCE_MAP_FIELD_OFFSET = UnsafeUtil.objectFieldOffset(Class.forName(clsName).getDeclaredField("m"));
65+
} catch (Exception e) {
66+
Log.warn("Could not access source map field in {}", clsName);
67+
throw new KryoException(e);
68+
}
69+
}
70+
}
71+
72+
static final class SynchronizedCollectionSerializer extends CollectionSerializer<Collection> {
73+
private final Function factory;
74+
private final long offset;
75+
76+
public SynchronizedCollectionSerializer (Function factory, long offset) {
77+
setAcceptsNull(false);
78+
this.factory = factory;
79+
this.offset = offset;
80+
}
81+
82+
@Override
83+
public void write (Kryo kryo, Output output, Collection collection) {
84+
Object unwrapped = UnsafeUtil.getObject(collection, offset);
85+
synchronized (collection) {
86+
kryo.writeClassAndObject(output, unwrapped);
87+
}
88+
}
89+
90+
@Override
91+
public Collection read (Kryo kryo, Input input, Class<? extends Collection> type) {
92+
final Object sourceCollection = kryo.readClassAndObject(input);
93+
return (Collection)factory.apply(sourceCollection);
94+
}
95+
96+
@Override
97+
public Collection copy (Kryo kryo, Collection original) {
98+
synchronized (original) {
99+
final Object collection = UnsafeUtil.getObject(original, offset);
100+
return (Collection)factory.apply(kryo.copy(collection));
101+
}
102+
}
103+
}
104+
105+
static final class SynchronizedMapSerializer extends MapSerializer<Map> {
106+
private final Function factory;
107+
private final long offset;
108+
109+
public SynchronizedMapSerializer (Function factory, long offset) {
110+
setAcceptsNull(false);
111+
this.factory = factory;
112+
this.offset = offset;
113+
}
114+
115+
@Override
116+
public void write (Kryo kryo, Output output, Map map) {
117+
Object unwrapped = UnsafeUtil.getObject(map, offset);
118+
synchronized (map) {
119+
kryo.writeClassAndObject(output, unwrapped);
120+
}
121+
}
122+
123+
@Override
124+
public Map read (Kryo kryo, Input input, Class<? extends Map> type) {
125+
final Object sourceMap = kryo.readClassAndObject(input);
126+
return (Map)factory.apply(sourceMap);
127+
}
128+
129+
@Override
130+
public Map copy (Kryo kryo, Map original) {
131+
synchronized (original) {
132+
final Object map = UnsafeUtil.getObject(original, offset);
133+
return (Map)factory.apply(kryo.copy(map));
134+
}
135+
}
136+
}
137+
138+
private static Serializer<?> createSerializer (Map.Entry<Class<?>, Function> factory) {
139+
if (Collection.class.isAssignableFrom(factory.getKey())) {
140+
return new SynchronizedCollectionSerializer(factory.getValue(), Offset.SOURCE_COLLECTION_FIELD_OFFSET);
141+
} else {
142+
return new SynchronizedMapSerializer(factory.getValue(), Offset.SOURCE_MAP_FIELD_OFFSET);
143+
}
144+
}
145+
146+
static Map<Class<?>, Function> synchronizedFactories () {
147+
final Map<Class<?>, Function> factories = new HashMap<>();
148+
factories.put(
149+
Collections.synchronizedCollection(Arrays.asList("")).getClass(),
150+
o -> Collections.synchronizedCollection((Collection)o));
151+
factories.put(
152+
Collections.synchronizedList(new ArrayList<Void>()).getClass(),
153+
o1 -> Collections.synchronizedList((List<?>)o1));
154+
factories.put(
155+
Collections.synchronizedList(new LinkedList<Void>()).getClass(),
156+
o2 -> Collections.synchronizedList((List<?>)o2));
157+
factories.put(
158+
Collections.synchronizedSet(new HashSet<Void>()).getClass(),
159+
o3 -> Collections.synchronizedSet((Set<?>)o3));
160+
factories.put(
161+
Collections.synchronizedSortedSet(new TreeSet<>()).getClass(),
162+
o4 -> Collections.synchronizedSortedSet((SortedSet<?>)o4));
163+
factories.put(
164+
Collections.synchronizedMap(new HashMap<Void, Void>()).getClass(),
165+
o5 -> Collections.synchronizedMap((Map)o5));
166+
factories.put(
167+
Collections.synchronizedSortedMap(new TreeMap<>()).getClass(),
168+
o6 -> Collections.synchronizedSortedMap((SortedMap)o6));
169+
return factories;
170+
}
171+
172+
/** Registering serializers for synchronized Collections and Maps created via {@link Collections}.
173+
*
174+
* @see Collections#synchronizedCollection(Collection)
175+
* @see Collections#synchronizedList(List)
176+
* @see Collections#synchronizedSet(Set)
177+
* @see Collections#synchronizedSortedSet(SortedSet)
178+
* @see Collections#synchronizedMap(Map)
179+
* @see Collections#synchronizedSortedMap(SortedMap) **/
180+
public static void registerSerializers (Kryo kryo) {
181+
try {
182+
for (Map.Entry<Class<?>, Function> factory : synchronizedFactories().entrySet()) {
183+
kryo.register(factory.getKey(), createSerializer(factory));
184+
}
185+
} catch (Throwable t) {
186+
Log.warn("Unable to register serializers for synchronized collections.", t);
187+
}
188+
}
189+
190+
/** Adding default serializers for synchronized Collections and Maps created via {@link Collections}.
191+
*
192+
* @see Collections#synchronizedCollection(Collection)
193+
* @see Collections#synchronizedList(List)
194+
* @see Collections#synchronizedSet(Set)
195+
* @see Collections#synchronizedSortedSet(SortedSet)
196+
* @see Collections#synchronizedMap(Map)
197+
* @see Collections#synchronizedSortedMap(SortedMap) **/
198+
public static void addDefaultSerializers (Kryo kryo) {
199+
try {
200+
for (Map.Entry<Class<?>, Function> factory : synchronizedFactories().entrySet()) {
201+
kryo.addDefaultSerializer(factory.getKey(), createSerializer(factory));
202+
}
203+
} catch (Throwable t) {
204+
Log.warn("Unable to add default serializers for synchronized collections.", t);
205+
}
206+
}
207+
}

0 commit comments

Comments
 (0)