// 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. * *

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 { 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 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); } } }