blob: f6207001dea1ca79bea2254b93bfc1a5cd798be7 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fury.serializer.collection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.function.Function;
import org.apache.fury.Fury;
import org.apache.fury.collection.Tuple2;
import org.apache.fury.logging.Logger;
import org.apache.fury.logging.LoggerFactory;
import org.apache.fury.memory.MemoryBuffer;
import org.apache.fury.serializer.Serializer;
import org.apache.fury.util.Platform;
import org.apache.fury.util.Preconditions;
import org.apache.fury.util.Utils;
/** Serializer for unmodifiable Collections and Maps created via Collections. */
@SuppressWarnings({"rawtypes", "unchecked"})
public class UnmodifiableSerializers {
private static final Logger LOG = LoggerFactory.getLogger(UnmodifiableSerializers.class);
private static class Offset {
// Graalvm unsafe offset substitution support: Make the call followed by a field store
// directly or by a sign extend node followed directly by a field store.
private static final long SOURCE_COLLECTION_FIELD_OFFSET;
private static final long SOURCE_MAP_FIELD_OFFSET;
static {
// UnmodifiableList/Set/Etc.. extends UnmodifiableCollection
String clsName = "java.util.Collections$UnmodifiableCollection";
try {
SOURCE_COLLECTION_FIELD_OFFSET =
Platform.UNSAFE.objectFieldOffset(Class.forName(clsName).getDeclaredField("c"));
} catch (Exception e) {
LOG.info("Could not access source collection field in {}", clsName);
throw new RuntimeException(e);
}
clsName = "java.util.Collections$UnmodifiableMap";
try {
// UnmodifiableSortedMap/UnmodifiableNavigableMap extends UnmodifiableMap
SOURCE_MAP_FIELD_OFFSET =
Platform.UNSAFE.objectFieldOffset(Class.forName(clsName).getDeclaredField("m"));
} catch (Exception e) {
LOG.info("Could not access source map field in {}", clsName);
throw new RuntimeException(e);
}
}
}
public static final class UnmodifiableCollectionSerializer
extends CollectionSerializer<Collection> {
private final Function factory;
private final long offset;
public UnmodifiableCollectionSerializer(Fury fury, Class cls, Function factory, long offset) {
super(fury, cls, false);
this.factory = factory;
this.offset = offset;
}
@Override
public void write(MemoryBuffer buffer, Collection value) {
Preconditions.checkArgument(value.getClass() == type);
Object fieldValue = Platform.getObject(value, offset);
fury.writeRef(buffer, fieldValue);
}
@Override
public Collection read(MemoryBuffer buffer) {
final Object sourceCollection = fury.readRef(buffer);
return (Collection) factory.apply(sourceCollection);
}
}
public static final class UnmodifiableMapSerializer extends MapSerializer<Map> {
private final Function factory;
private final long offset;
public UnmodifiableMapSerializer(Fury fury, Class cls, Function factory, long offset) {
super(fury, cls, false);
this.factory = factory;
this.offset = offset;
}
@Override
public void write(MemoryBuffer buffer, Map value) {
Preconditions.checkArgument(value.getClass() == type);
Object fieldValue = Platform.getObject(value, offset);
fury.writeRef(buffer, fieldValue);
}
@Override
public Map read(MemoryBuffer buffer) {
final Object sourceCollection = fury.readRef(buffer);
return (Map) factory.apply(sourceCollection);
}
}
static Serializer createSerializer(Fury fury, Class<?> cls) {
for (Tuple2<Class<?>, Function> factory : unmodifiableFactories()) {
if (factory.f0 == cls) {
return createSerializer(fury, factory);
}
}
throw new IllegalArgumentException("Unsupported type " + cls);
}
private static Serializer<?> createSerializer(Fury fury, Tuple2<Class<?>, Function> factory) {
if (Collection.class.isAssignableFrom(factory.f0)) {
return new UnmodifiableCollectionSerializer(
fury, factory.f0, factory.f1, Offset.SOURCE_COLLECTION_FIELD_OFFSET);
} else {
return new UnmodifiableMapSerializer(
fury, factory.f0, factory.f1, Offset.SOURCE_MAP_FIELD_OFFSET);
}
}
static Tuple2<Class<?>, Function>[] unmodifiableFactories() {
Tuple2<Class<?>, Function> collectionFactory =
Tuple2.of(
Collections.unmodifiableCollection(Collections.singletonList("")).getClass(),
o -> Collections.unmodifiableCollection((Collection) o));
Tuple2<Class<?>, Function> randomAccessListFactory =
Tuple2.of(
Collections.unmodifiableList(new ArrayList<Void>()).getClass(),
o -> Collections.unmodifiableList((List<?>) o));
Tuple2<Class<?>, Function> listFactory =
Tuple2.of(
Collections.unmodifiableList(new LinkedList<Void>()).getClass(),
o -> Collections.unmodifiableList((List<?>) o));
Tuple2<Class<?>, Function> setFactory =
Tuple2.of(
Collections.unmodifiableSet(new HashSet<Void>()).getClass(),
o -> Collections.unmodifiableSet((Set<?>) o));
Tuple2<Class<?>, Function> sortedsetFactory =
Tuple2.of(
Collections.unmodifiableSortedSet(new TreeSet<>()).getClass(),
o -> Collections.unmodifiableSortedSet((SortedSet<?>) o));
Tuple2<Class<?>, Function> mapFactory =
Tuple2.of(
Collections.unmodifiableMap(new HashMap<>()).getClass(),
o -> Collections.unmodifiableMap((Map) o));
Tuple2<Class<?>, Function> sortedmapFactory =
Tuple2.of(
Collections.unmodifiableSortedMap(new TreeMap<>()).getClass(),
o -> Collections.unmodifiableSortedMap((SortedMap) o));
return new Tuple2[] {
collectionFactory,
randomAccessListFactory,
listFactory,
setFactory,
sortedsetFactory,
mapFactory,
sortedmapFactory
};
}
/**
* Registers serializers for unmodifiable Collections created via {@link Collections}, including
* {@link Map}s.
*
* @see Collections#unmodifiableCollection(Collection)
* @see Collections#unmodifiableList(List)
* @see Collections#unmodifiableSet(Set)
* @see Collections#unmodifiableSortedSet(SortedSet)
* @see Collections#unmodifiableMap(Map)
* @see Collections#unmodifiableSortedMap(SortedMap)
*/
public static void registerSerializers(Fury fury) {
try {
for (Tuple2<Class<?>, Function> factory : unmodifiableFactories()) {
fury.registerSerializer(factory.f0, createSerializer(fury, factory));
}
} catch (Throwable e) {
Utils.ignore(e);
}
}
}