| // Copyright 2023 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.common.base.Preconditions.checkArgument; |
| import static sun.misc.Unsafe.ARRAY_OBJECT_BASE_OFFSET; |
| import static sun.misc.Unsafe.ARRAY_OBJECT_INDEX_SCALE; |
| |
| import com.google.protobuf.CodedInputStream; |
| import com.google.protobuf.CodedOutputStream; |
| import java.io.IOException; |
| import java.util.Map; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| /** Helpers for {@link Map} serialization. */ |
| @SuppressWarnings("rawtypes") |
| final class MapHelpers { |
| |
| /** |
| * Serializes the map's entries. |
| * |
| * <p>Note: this does not include the map's size. |
| */ |
| static void serializeMapEntries(SerializationContext context, Map map, CodedOutputStream codedOut) |
| throws SerializationException, IOException { |
| for (Object next : map.entrySet()) { |
| Map.Entry entry = (Map.Entry) next; |
| context.serialize(entry.getKey(), codedOut); |
| try { |
| context.serialize(entry.getValue(), codedOut); |
| } catch (SerializationException | IOException e) { |
| throw SerializationException.propagate( |
| String.format( |
| "Exception while serializing value of type %s for key '%s'", |
| entry.getValue().getClass().getName(), entry.getKey()), |
| e); |
| } |
| } |
| } |
| |
| /** |
| * Deserializes map entries into the given {@code keys} and {@code values}. |
| * |
| * <p>There's no direct indication of when the deserialization is complete so this should be used |
| * with a {@link DeferredObjectCodec}. |
| */ |
| static void deserializeMapEntries( |
| AsyncDeserializationContext context, |
| CodedInputStream codedIn, |
| Object[] keys, |
| Object[] values) |
| throws SerializationException, IOException { |
| int size = keys.length; |
| checkArgument(values.length == size, "%s %s", keys.length, values.length); |
| long offset = ARRAY_OBJECT_BASE_OFFSET; |
| for (int i = 0; i < size; i++) { |
| // Ensures that keys are fully deserialized. |
| context.deserialize(codedIn, keys, offset); |
| try { |
| context.deserialize(codedIn, values, offset); |
| } catch (SerializationException | IOException e) { |
| Object key = keys[i]; |
| if (key != null) { |
| throw SerializationException.propagate( |
| String.format("Exception while deserializing value for key '%s'", key), e); |
| } |
| throw e; |
| } |
| offset += ARRAY_OBJECT_INDEX_SCALE; |
| } |
| } |
| |
| /** |
| * Populates the map entries with a {@code done} callback. |
| * |
| * <p>The {@code done} callback is called when all the entries have been deserialized. The {@code |
| * keys} are fully deserialized when the {@code done} callback is called. The {@code values} |
| * references will all be available but they might only be partially deserialized. |
| */ |
| static void deserializeMapEntries( |
| AsyncDeserializationContext context, |
| CodedInputStream codedIn, |
| Object[] keys, |
| Object[] values, |
| Runnable done) |
| throws SerializationException, IOException { |
| int size = keys.length; |
| checkArgument(values.length == size, "%s %s", keys.length, values.length); |
| ReferenceCounter countDown = new ReferenceCounter(2 * size, done); |
| long offset = ARRAY_OBJECT_BASE_OFFSET; |
| for (int i = 0; i < size; i++) { |
| context.deserialize(codedIn, keys, offset, /* done= */ countDown); |
| try { |
| context.deserialize(codedIn, values, offset, /* done= */ countDown); |
| } catch (SerializationException | IOException e) { |
| Object key = keys[i]; |
| if (key != null) { |
| throw SerializationException.propagate( |
| String.format("Exception while deserializing value for key '%s'", key), e); |
| } |
| throw e; |
| } |
| offset += ARRAY_OBJECT_INDEX_SCALE; |
| } |
| } |
| |
| private static class ReferenceCounter implements Runnable { |
| private final AtomicInteger remaining; |
| private final Runnable done; |
| |
| private ReferenceCounter(int size, Runnable done) { |
| this.remaining = new AtomicInteger(size); |
| this.done = done; |
| } |
| |
| @Override |
| public void run() { |
| if (remaining.decrementAndGet() == 0) { |
| done.run(); |
| } |
| } |
| } |
| |
| private MapHelpers() {} |
| } |