blob: 202ceeb18c0210f35fa5d7da76d750f8d5afa5fd [file] [log] [blame]
// Copyright 2018 The Bazel Authors. All rights reserved.
//
// Licensed 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 com.google.devtools.build.lib.skyframe.serialization;
import static com.google.devtools.build.lib.skyframe.serialization.ArrayProcessor.deserializeObjectArray;
import static sun.misc.Unsafe.ARRAY_OBJECT_BASE_OFFSET;
import static sun.misc.Unsafe.ARRAY_OBJECT_INDEX_SCALE;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import com.google.devtools.build.lib.skyframe.serialization.DeferredObjectCodec.DeferredValue;
import com.google.errorprone.annotations.Keep;
import com.google.protobuf.CodedInputStream;
import com.google.protobuf.CodedOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
/**
* Codecs for {@link Multimap}. Handles {@link ImmutableListMultimap}, {@link ImmutableSetMultimap}
* and {@link LinkedHashMultimap}.
*/
@SuppressWarnings({"unchecked", "rawtypes", "NonApiType"})
public final class MultimapCodecs {
@Keep // used reflectively
private static class ImmutableListMultimapCodec
extends DeferredObjectCodec<ImmutableListMultimap> {
@Override
public Class<ImmutableListMultimap> getEncodedClass() {
return ImmutableListMultimap.class;
}
@Override
public void serialize(
SerializationContext context, ImmutableListMultimap obj, CodedOutputStream codedOut)
throws SerializationException, IOException {
serializeMultimap(context, obj, codedOut);
}
@Override
public DeferredValue<ImmutableListMultimap> deserializeDeferred(
AsyncDeserializationContext context, CodedInputStream codedIn)
throws SerializationException, IOException {
int size = codedIn.readInt32();
if (size == 0) {
return ImmutableListMultimap::of;
}
ImmutableListMultimapBuffer buffer = new ImmutableListMultimapBuffer(size);
for (int i = 0; i < size; i++) {
context.deserialize(
codedIn, buffer.keys, ARRAY_OBJECT_BASE_OFFSET + i * ARRAY_OBJECT_INDEX_SCALE);
int valuesCount = codedIn.readInt32();
Object[] values = new Object[valuesCount];
buffer.values[i] = values;
// The builder merely collects these references in an ArrayList, so (unlike the set-type
// multimaps) these values do not need to be fully deserialized.
deserializeObjectArray(context, codedIn, buffer.values[i], valuesCount);
}
return buffer;
}
}
@Keep // used reflectively
private static class ImmutableSetMultimapCodec extends DeferredObjectCodec<ImmutableSetMultimap> {
@Override
public Class<ImmutableSetMultimap> getEncodedClass() {
return ImmutableSetMultimap.class;
}
@Override
public void serialize(
SerializationContext context, ImmutableSetMultimap obj, CodedOutputStream codedOut)
throws SerializationException, IOException {
serializeMultimap(context, obj, codedOut);
}
@Override
public DeferredValue<ImmutableSetMultimap> deserializeDeferred(
AsyncDeserializationContext context, CodedInputStream codedIn)
throws SerializationException, IOException {
int size = codedIn.readInt32();
if (size == 0) {
return ImmutableSetMultimap::of;
}
ImmutableSetMultimapBuffer buffer = new ImmutableSetMultimapBuffer(size);
deserializeSetMultimap(context, codedIn, buffer);
return buffer;
}
}
@Keep // used reflectively
private static class LinkedHashMultimapCodec extends DeferredObjectCodec<LinkedHashMultimap> {
@Override
public Class<LinkedHashMultimap> getEncodedClass() {
return LinkedHashMultimap.class;
}
@Override
public void serialize(
SerializationContext context, LinkedHashMultimap obj, CodedOutputStream codedOut)
throws SerializationException, IOException {
serializeMultimap(context, obj, codedOut);
}
@Override
public DeferredValue<LinkedHashMultimap> deserializeDeferred(
AsyncDeserializationContext context, CodedInputStream codedIn)
throws SerializationException, IOException {
int size = codedIn.readInt32();
if (size == 0) {
return LinkedHashMultimap::create;
}
LinkedHashMultimapBuffer buffer = new LinkedHashMultimapBuffer(size);
deserializeSetMultimap(context, codedIn, buffer);
return buffer;
}
}
private static void serializeMultimap(
SerializationContext context, Multimap obj, CodedOutputStream codedOut)
throws SerializationException, IOException {
Map map = obj.asMap();
codedOut.writeInt32NoTag(map.size());
for (Object next : map.entrySet()) {
Map.Entry entry = (Map.Entry) next;
context.serialize(entry.getKey(), codedOut);
Collection values = (Collection) entry.getValue();
codedOut.writeInt32NoTag(values.size());
for (Object value : values) {
context.serialize(value, codedOut);
}
}
}
/** Takes care to fully deserialize all keys and values as they will be used in sets. */
private static void deserializeSetMultimap(
AsyncDeserializationContext context, CodedInputStream codedIn, MultimapBuffer buffer)
throws SerializationException, IOException {
for (int i = 0; i < buffer.size(); i++) {
context.deserialize(
codedIn, buffer.keys, ARRAY_OBJECT_BASE_OFFSET + i * ARRAY_OBJECT_INDEX_SCALE);
int valuesCount = codedIn.readInt32();
Object[] values = new Object[valuesCount];
buffer.values[i] = values;
// The builder uses a set to collect the values so they must be complete.
deserializeObjectArray(context, codedIn, values, valuesCount);
}
}
private static class MultimapBuffer {
final Object[] keys;
final Object[][] values;
private MultimapBuffer(int size) {
this.keys = new Object[size];
this.values = new Object[size][];
}
int size() {
return keys.length;
}
}
private static class ImmutableListMultimapBuffer extends MultimapBuffer
implements DeferredValue<ImmutableListMultimap> {
private ImmutableListMultimapBuffer(int size) {
super(size);
}
@Override
public ImmutableListMultimap call() {
ImmutableListMultimap.Builder builder = ImmutableListMultimap.builder();
for (int i = 0; i < size(); i++) {
builder.putAll(keys[i], values[i]);
}
return builder.build();
}
}
private static class ImmutableSetMultimapBuffer extends MultimapBuffer
implements DeferredValue<ImmutableSetMultimap> {
private ImmutableSetMultimapBuffer(int size) {
super(size);
}
@Override
public ImmutableSetMultimap call() {
ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder();
for (int i = 0; i < size(); i++) {
builder.putAll(keys[i], values[i]);
}
return builder.build();
}
}
private static class LinkedHashMultimapBuffer extends MultimapBuffer
implements DeferredValue<LinkedHashMultimap> {
private LinkedHashMultimapBuffer(int size) {
super(size);
}
@Override
public LinkedHashMultimap call() {
int totalValues = 0;
for (int i = 0; i < size(); i++) {
totalValues += values[i].length;
}
LinkedHashMultimap result = LinkedHashMultimap.create(size(), totalValues / size());
for (int i = 0; i < size(); i++) {
result.putAll(keys[i], Arrays.asList(values[i]));
}
return result;
}
}
private MultimapCodecs() {}
}