blob: 1127308ca9bc5cf0d29d353b310b6912006ab3e7 [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 com.google.protobuf.CodedInputStream;
import com.google.protobuf.CodedOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;
/**
* A codec for Java 8 serializable lambdas. Lambdas that are tagged as {@link Serializable} have a
* generated method, {@code writeReplace}, that converts them into a {@link SerializedLambda}, which
* can then be serialized like a normal object. On deserialization, we call {@link
* SerializedLambda#readResolve}, which converts the object back into a lambda.
*
* <p>Since lambdas do not share a common base class, choosing this codec for serializing them must
* be special-cased in {@link ObjectCodecRegistry}. We must also make a somewhat arbitrary choice
* around the generic parameter. Since all of our lambdas are {@link Serializable}, we use that.
* Because {@link Serializable} is an interface, not a class, this codec will never be chosen for
* any object without special-casing.
*/
class LambdaCodec implements ObjectCodec<Serializable> {
private final Method readResolveMethod;
LambdaCodec() {
try {
this.readResolveMethod = SerializedLambda.class.getDeclaredMethod("readResolve");
} catch (NoSuchMethodException e) {
throw new IllegalStateException(e);
}
readResolveMethod.setAccessible(true);
}
static boolean isProbablyLambda(Class<?> type) {
return type.isSynthetic() && !type.isLocalClass() && !type.isAnonymousClass();
}
@Override
public Class<? extends Serializable> getEncodedClass() {
return Serializable.class;
}
@Override
public void serialize(SerializationContext context, Serializable obj, CodedOutputStream codedOut)
throws SerializationException, IOException {
Class<?> objClass = obj.getClass();
if (!isProbablyLambda(objClass)) {
throw new SerializationException(obj + " is not a lambda: " + objClass);
}
Method writeReplaceMethod;
try {
// TODO(janakr): We could cache these methods if retrieval shows up as a hotspot.
writeReplaceMethod = objClass.getDeclaredMethod("writeReplace");
} catch (NoSuchMethodException e) {
throw new SerializationException(
"No writeReplace method for " + obj + " with " + objClass, e);
}
writeReplaceMethod.setAccessible(true);
SerializedLambda serializedLambda;
try {
serializedLambda = (SerializedLambda) writeReplaceMethod.invoke(obj);
} catch (ReflectiveOperationException e) {
throw new SerializationException(
"Exception invoking writeReplace for " + obj + " with " + objClass, e);
}
context.serialize(serializedLambda, codedOut);
}
@Override
public Serializable deserialize(DeserializationContext context, CodedInputStream codedIn)
throws SerializationException, IOException {
SerializedLambda serializedLambda = context.deserialize(codedIn);
try {
return (Serializable) readResolveMethod.invoke(serializedLambda);
} catch (ReflectiveOperationException e) {
throw new IllegalStateException("Error read-resolving " + serializedLambda, e);
}
}
}