/*
* 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.beam.sdk.coders;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import javax.annotation.Nullable;
import org.apache.beam.sdk.annotations.Internal;
import org.apache.beam.sdk.coders.CannotProvideCoderException.ReasonCode;
import org.apache.beam.sdk.transforms.SerializableFunction;
import org.apache.beam.sdk.transforms.windowing.IntervalWindow;
import org.apache.beam.sdk.util.CoderUtils;
import org.apache.beam.sdk.util.common.ReflectHelpers;
import org.apache.beam.sdk.util.common.ReflectHelpers.ObjectsClassComparator;
import org.apache.beam.sdk.values.KV;
import org.apache.beam.sdk.values.TimestampedValue;
import org.apache.beam.sdk.values.TypeDescriptor;
import org.joda.time.Instant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A {@link CoderRegistry} allows creating a {@link Coder} for a given Java {@link Class class} or
* {@link TypeDescriptor type descriptor}.
*
* <p>Creation of the {@link Coder} is delegated to one of the many registered
* {@link CoderProvider coder providers} based upon the registration order.
*
* <p>By default, the {@link CoderProvider coder provider} precedence order is as follows:
* <ul>
* <li>Coder providers registered programmatically with
* {@link CoderRegistry#registerCoderProvider(CoderProvider)}.</li>
* <li>A default coder provider for common Java (Byte, Double, List, ...) and
* Apache Beam (KV, ...) types.</li>
* <li>Coder providers registered automatically through a {@link CoderProviderRegistrar} using
* a {@link ServiceLoader}. Note that the {@link ServiceLoader} registration order is
* consistent but may change due to the addition or removal of libraries exposed
* to the application. This can impact the coder returned if multiple coder providers
* are capable of supplying a coder for the specified type.</li>
* </ul>
*
* <p>Note that if multiple {@link CoderProvider coder providers} can provide a {@link Coder} for
* a given type, the precedence order above defines which {@link CoderProvider} is chosen.
*/
public class CoderRegistry {
private static final Logger LOG = LoggerFactory.getLogger(CoderRegistry.class);
private static final List<CoderProvider> REGISTERED_CODER_FACTORIES;
/** A {@link CoderProvider} for common Java SDK and Apache Beam SDK types. */
private static class CommonTypes extends CoderProvider {
private final Map<Class<?>, CoderProvider> commonTypesToCoderProviders;
private CommonTypes() {
ImmutableMap.Builder<Class<?>, CoderProvider> builder = ImmutableMap.builder();
builder.put(Byte.class,
CoderProviders.fromStaticMethods(Byte.class, ByteCoder.class));
builder.put(BitSet.class,
CoderProviders.fromStaticMethods(BitSet.class, BitSetCoder.class));
builder.put(Double.class,
CoderProviders.fromStaticMethods(Double.class, DoubleCoder.class));
builder.put(Instant.class,
CoderProviders.fromStaticMethods(Instant.class, InstantCoder.class));
builder.put(Integer.class,
CoderProviders.fromStaticMethods(Integer.class, VarIntCoder.class));
builder.put(Iterable.class,
CoderProviders.fromStaticMethods(Iterable.class, IterableCoder.class));
builder.put(KV.class,
CoderProviders.fromStaticMethods(KV.class, KvCoder.class));
builder.put(List.class,
CoderProviders.fromStaticMethods(List.class, ListCoder.class));
builder.put(Long.class,
CoderProviders.fromStaticMethods(Long.class, VarLongCoder.class));
builder.put(Map.class,
CoderProviders.fromStaticMethods(Map.class, MapCoder.class));
builder.put(Set.class,
CoderProviders.fromStaticMethods(Set.class, SetCoder.class));
builder.put(String.class,
CoderProviders.fromStaticMethods(String.class, StringUtf8Coder.class));
builder.put(TimestampedValue.class,
CoderProviders.fromStaticMethods(
TimestampedValue.class, TimestampedValue.TimestampedValueCoder.class));
builder.put(Void.class,
CoderProviders.fromStaticMethods(Void.class, VoidCoder.class));
builder.put(byte[].class,
CoderProviders.fromStaticMethods(byte[].class, ByteArrayCoder.class));
builder.put(IntervalWindow.class,
CoderProviders.forCoder(
TypeDescriptor.of(IntervalWindow.class), IntervalWindow.getCoder()));
commonTypesToCoderProviders = builder.build();
}
@Override
public <T> Coder<T> coderFor(TypeDescriptor<T> typeDescriptor,
List<? extends Coder<?>> componentCoders) throws CannotProvideCoderException {
CoderProvider factory = commonTypesToCoderProviders.get(typeDescriptor.getRawType());
if (factory == null) {
throw new CannotProvideCoderException(
String.format("%s is not one of the common types.", typeDescriptor));
}
return factory.coderFor(typeDescriptor, componentCoders);
}
}
static {
// Register the standard coders first so they are chosen over ServiceLoader ones
List<CoderProvider> codersToRegister = new ArrayList<>();
codersToRegister.add(new CommonTypes());
// Enumerate all the CoderRegistrars in a deterministic order, adding all coders to register
Set<CoderProviderRegistrar> registrars = Sets.newTreeSet(ObjectsClassComparator.INSTANCE);
registrars.addAll(Lists.newArrayList(
ServiceLoader.load(CoderProviderRegistrar.class, ReflectHelpers.findClassLoader())));
for (CoderProviderRegistrar registrar : registrars) {
codersToRegister.addAll(registrar.getCoderProviders());
}
REGISTERED_CODER_FACTORIES = ImmutableList.copyOf(codersToRegister);
}
/**
* Creates a CoderRegistry containing registrations for all standard coders part of the core Java
* Apache Beam SDK and also any registrations provided by
* {@link CoderProviderRegistrar coder registrars}.
*
* <p>Multiple registrations which can produce a coder for a given type result in a Coder created
* by the (in order of precedence):
* <ul>
* <li>{@link CoderProvider coder providers} registered programmatically through
* {@link CoderRegistry#registerCoderProvider}.</li>
* <li>{@link CoderProvider coder providers} for core types found within the Apache Beam Java
* SDK being used.</li>
* <li>The {@link CoderProvider coder providers} from the {@link CoderProviderRegistrar}
* with the lexicographically smallest {@link Class#getName() class name} being used.</li>
* </ul>
*/
public static CoderRegistry createDefault() {
return new CoderRegistry();
}
private CoderRegistry() {
coderProviders = new LinkedList<>(REGISTERED_CODER_FACTORIES);
}
/**
* Registers {@code coderProvider} as a potential {@link CoderProvider} which can produce
* {@code Coder} instances.
*
* <p>This method prioritizes this {@link CoderProvider} over all prior registered coders.
*
* <p>See {@link CoderProviders} for common {@link CoderProvider} patterns.
*/
public void registerCoderProvider(CoderProvider coderProvider) {
coderProviders.addFirst(coderProvider);
}
/**
* Registers the provided {@link Coder} for the given class.
*
* <p>Note that this is equivalent to {@code registerCoderForType(TypeDescriptor.of(clazz))}. See
* {@link #registerCoderForType(TypeDescriptor, Coder)} for further details.
*/
public void registerCoderForClass(Class<?> clazz, Coder<?> coder) {
registerCoderForType(TypeDescriptor.of(clazz), coder);
}
/**
* Registers the provided {@link Coder} for the given type.
*
* <p>Note that this is equivalent to
* {@code registerCoderProvider(CoderProviders.forCoder(type, coder))}. See
* {@link #registerCoderProvider} and {@link CoderProviders#forCoder} for further details.
*/
public void registerCoderForType(TypeDescriptor<?> type, Coder<?> coder) {
registerCoderProvider(CoderProviders.forCoder(type, coder));
}
/**
* Returns the {@link Coder} to use for values of the given class.
* @throws CannotProvideCoderException if a {@link Coder} cannot be provided
*/
public <T> Coder<T> getCoder(Class<T> clazz) throws CannotProvideCoderException {
return getCoder(TypeDescriptor.of(clazz));
}
/**
* Returns the {@link Coder} to use for values of the given type.
*
* @throws CannotProvideCoderException if a {@link Coder} cannot be provided
*/
public <T> Coder<T> getCoder(TypeDescriptor<T> type) throws CannotProvideCoderException {
return getCoderFromTypeDescriptor(type, Collections.<Type, Coder<?>>emptyMap());
}
/**
* Returns the {@link Coder} for values of the given type, where the given input
* type uses the given {@link Coder}.
*
* @throws CannotProvideCoderException if a {@link Coder} cannot be provided
*/
@Deprecated
@Internal
public <InputT, OutputT> Coder<OutputT> getCoder(
TypeDescriptor<OutputT> typeDescriptor,
TypeDescriptor<InputT> inputTypeDescriptor,
Coder<InputT> inputCoder)
throws CannotProvideCoderException {
checkArgument(typeDescriptor != null);
checkArgument(inputTypeDescriptor != null);
checkArgument(inputCoder != null);
return getCoderFromTypeDescriptor(
typeDescriptor, getTypeToCoderBindings(inputTypeDescriptor.getType(), inputCoder));
}
/**
* Returns the {@link Coder} to use on elements produced by this function, given the {@link Coder}
* used for its input elements.
*
* @throws CannotProvideCoderException if a {@link Coder} cannot be provided
*/
@Deprecated
@Internal
public <InputT, OutputT> Coder<OutputT> getOutputCoder(
SerializableFunction<InputT, OutputT> fn, Coder<InputT> inputCoder)
throws CannotProvideCoderException {
ParameterizedType fnType = (ParameterizedType)
TypeDescriptor.of(fn.getClass()).getSupertype(SerializableFunction.class).getType();
return getCoder(
fn.getClass(),
SerializableFunction.class,
ImmutableMap.of(fnType.getActualTypeArguments()[0], inputCoder),
SerializableFunction.class.getTypeParameters()[1]);
}
/**
* Returns the {@link Coder} to use for the specified type parameter specialization of the
* subclass, given {@link Coder Coders} to use for all other type parameters (if any).
*
* @throws CannotProvideCoderException if a {@link Coder} cannot be provided
*/
@Deprecated
@Internal
public <T, OutputT> Coder<OutputT> getCoder(
Class<? extends T> subClass,
Class<T> baseClass,
Map<Type, ? extends Coder<?>> knownCoders,
TypeVariable<?> param)
throws CannotProvideCoderException {
Map<Type, Coder<?>> inferredCoders = getDefaultCoders(subClass, baseClass, knownCoders);
@SuppressWarnings("unchecked")
Coder<OutputT> paramCoderOrNull = (Coder<OutputT>) inferredCoders.get(param);
if (paramCoderOrNull != null) {
return paramCoderOrNull;
} else {
throw new CannotProvideCoderException(
"Cannot infer coder for type parameter " + param.getName());
}
}
/////////////////////////////////////////////////////////////////////////////
/**
* Returns a {@code Map} from each of {@code baseClass}'s type parameters to the {@link Coder} to
* use for it, in the context of {@code subClass}'s specialization of {@code baseClass}.
*
* <p>If no {@link Coder} can be inferred for a particular type parameter, then that type variable
* will be absent from the returned {@code Map}.
*
* <p>For example, if {@code baseClass} is {@code Map.class}, where {@code Map<K, V>} has type
* parameters {@code K} and {@code V}, and {@code subClass} extends {@code Map<String, Integer>}
* then the result will map the type variable {@code K} to a {@code Coder<String>} and the
* type variable {@code V} to a {@code Coder<Integer>}.
*
* <p>The {@code knownCoders} parameter can be used to provide known {@link Coder Coders} for any
* of the parameters; these will be used to infer the others.
*
* <p>Note that inference is attempted for every type variable. For a type
* {@code MyType<One, Two, Three>} inference will be attempted for all of {@code One},
* {@code Two}, {@code Three}, even if the requester only wants a {@link Coder} for {@code Two}.
*
* <p>For this reason {@code getDefaultCoders} (plural) does not throw an exception if a
* {@link Coder} for a particular type variable cannot be inferred, but merely omits the entry
* from the returned {@code Map}. It is the responsibility of the caller (usually
* {@link #getCoderFromTypeDescriptor} to extract the desired coder or throw a
* {@link CannotProvideCoderException} when appropriate.
*
* @param subClass the concrete type whose specializations are being inferred
* @param baseClass the base type, a parameterized class
* @param knownCoders a map corresponding to the set of known {@link Coder Coders} indexed by
* parameter name
*/
private <T> Map<Type, Coder<?>> getDefaultCoders(
Class<? extends T> subClass,
Class<T> baseClass,
Map<Type, ? extends Coder<?>> knownCoders) {
TypeVariable<Class<T>>[] typeParams = baseClass.getTypeParameters();
Coder<?>[] knownCodersArray = new Coder<?>[typeParams.length];
for (int i = 0; i < typeParams.length; i++) {
knownCodersArray[i] = knownCoders.get(typeParams[i]);
}
Coder<?>[] resultArray = getDefaultCoders(
subClass, baseClass, knownCodersArray);
Map<Type, Coder<?>> result = new HashMap<>();
for (int i = 0; i < typeParams.length; i++) {
if (resultArray[i] != null) {
result.put(typeParams[i], resultArray[i]);
}
}
return result;
}
/**
* Returns an array listing, for each of {@code baseClass}'s type parameters, the {@link Coder} to
* use for it, in the context of {@code subClass}'s specialization of {@code baseClass}.
*
* <p>If a {@link Coder} cannot be inferred for a type variable, its slot in the resulting array
* will be {@code null}.
*
* <p>For example, if {@code baseClass} is {@code Map.class}, where {@code Map<K, V>} has type
* parameters {@code K} and {@code V} in that order, and {@code subClass} extends
* {@code Map<String, Integer>} then the result will contain a {@code Coder<String>} and a
* {@code Coder<Integer>}, in that order.
*
* <p>The {@code knownCoders} parameter can be used to provide known {@link Coder Coders} for any
* of the type parameters. These will be used to infer the others. If non-null, the length of this
* array must match the number of type parameters of {@code baseClass}, and simply be filled with
* {@code null} values for each type parameters without a known {@link Coder}.
*
* <p>Note that inference is attempted for every type variable. For a type
* {@code MyType<One, Two, Three>} inference will will be attempted for all of {@code One},
* {@code Two}, {@code Three}, even if the requester only wants a {@link Coder} for {@code Two}.
*
* <p>For this reason {@code getDefaultCoders} (plural) does not throw an exception if a
* {@link Coder} for a particular type variable cannot be inferred. Instead, it results in a
* {@code null} in the array. It is the responsibility of the caller (usually
* {@link #getCoderFromTypeDescriptor} to extract the desired coder or throw a
* {@link CannotProvideCoderException} when appropriate.
*
* @param subClass the concrete type whose specializations are being inferred
* @param baseClass the base type, a parameterized class
* @param knownCoders an array corresponding to the set of base class type parameters. Each entry
* can be either a {@link Coder} (in which case it will be used for inference) or
* {@code null} (in which case it will be inferred). May be {@code null} to indicate the
* entire set of parameters should be inferred.
* @throws IllegalArgumentException if baseClass doesn't have type parameters or if the length of
* {@code knownCoders} is not equal to the number of type parameters of {@code baseClass}.
*/
private <T> Coder<?>[] getDefaultCoders(
Class<? extends T> subClass,
Class<T> baseClass,
@Nullable Coder<?>[] knownCoders) {
Type type = TypeDescriptor.of(subClass).getSupertype(baseClass).getType();
if (!(type instanceof ParameterizedType)) {
throw new IllegalArgumentException(type + " is not a ParameterizedType");
}
ParameterizedType parameterizedType = (ParameterizedType) type;
Type[] typeArgs = parameterizedType.getActualTypeArguments();
if (knownCoders == null) {
knownCoders = new Coder<?>[typeArgs.length];
} else if (typeArgs.length != knownCoders.length) {
throw new IllegalArgumentException(
String.format("Class %s has %d parameters, but %d coders are requested.",
baseClass.getCanonicalName(), typeArgs.length, knownCoders.length));
}
Map<Type, Coder<?>> context = new HashMap<>();
for (int i = 0; i < knownCoders.length; i++) {
if (knownCoders[i] != null) {
try {
verifyCompatible(knownCoders[i], typeArgs[i]);
} catch (IncompatibleCoderException exn) {
throw new IllegalArgumentException(
String.format("Provided coders for type arguments of %s contain incompatibilities:"
+ " Cannot encode elements of type %s with coder %s",
baseClass,
typeArgs[i], knownCoders[i]),
exn);
}
context.putAll(getTypeToCoderBindings(typeArgs[i], knownCoders[i]));
}
}
Coder<?>[] result = new Coder<?>[typeArgs.length];
for (int i = 0; i < knownCoders.length; i++) {
if (knownCoders[i] != null) {
result[i] = knownCoders[i];
} else {
try {
result[i] = getCoderFromTypeDescriptor(TypeDescriptor.of(typeArgs[i]), context);
} catch (CannotProvideCoderException exc) {
result[i] = null;
}
}
}
return result;
}
/**
* Thrown when a {@link Coder} cannot possibly encode a type, yet has been proposed as a
* {@link Coder} for that type.
*/
@VisibleForTesting static class IncompatibleCoderException extends RuntimeException {
private Coder<?> coder;
private Type type;
IncompatibleCoderException(String message, Coder<?> coder, Type type) {
super(message);
this.coder = coder;
this.type = type;
}
IncompatibleCoderException(String message, Coder<?> coder, Type type, Throwable cause) {
super(message, cause);
this.coder = coder;
this.type = type;
}
public Coder<?> getCoder() {
return coder;
}
public Type getType() {
return type;
}
}
/**
* Returns {@code true} if the given {@link Coder} can possibly encode elements
* of the given type.
*/
@VisibleForTesting static <T, CoderT extends Coder<T>, CandidateT>
void verifyCompatible(CoderT coder, Type candidateType) throws IncompatibleCoderException {
// Various representations of the coder's class
@SuppressWarnings("unchecked")
Class<CoderT> coderClass = (Class<CoderT>) coder.getClass();
TypeDescriptor<CoderT> coderDescriptor = TypeDescriptor.of(coderClass);
// Various representations of the actual coded type
@SuppressWarnings("unchecked")
TypeDescriptor<T> codedDescriptor = CoderUtils.getCodedType(coderDescriptor);
@SuppressWarnings("unchecked")
Class<T> codedClass = (Class<T>) codedDescriptor.getRawType();
Type codedType = codedDescriptor.getType();
// Various representations of the candidate type
@SuppressWarnings("unchecked")
TypeDescriptor<CandidateT> candidateDescriptor =
(TypeDescriptor<CandidateT>) TypeDescriptor.of(candidateType);
@SuppressWarnings("unchecked")
Class<CandidateT> candidateClass = (Class<CandidateT>) candidateDescriptor.getRawType();
// If coder has type Coder<T> where the actual value of T is lost
// to erasure, then we cannot rule it out.
if (candidateType instanceof TypeVariable) {
return;
}
// If the raw types are not compatible, we can certainly rule out
// coder compatibility
if (!codedClass.isAssignableFrom(candidateClass)) {
throw new IncompatibleCoderException(
String.format("Cannot encode elements of type %s with coder %s because the"
+ " coded type %s is not assignable from %s",
candidateType, coder, codedClass, candidateType),
coder, candidateType);
}
// we have established that this is a covariant upcast... though
// coders are invariant, we are just checking one direction
@SuppressWarnings("unchecked")
TypeDescriptor<T> candidateOkDescriptor = (TypeDescriptor<T>) candidateDescriptor;
// If the coded type is a parameterized type where any of the actual
// type parameters are not compatible, then the whole thing is certainly not
// compatible.
if ((codedType instanceof ParameterizedType) && !isNullOrEmpty(coder.getCoderArguments())) {
ParameterizedType parameterizedSupertype = ((ParameterizedType)
candidateOkDescriptor.getSupertype(codedClass).getType());
Type[] typeArguments = parameterizedSupertype.getActualTypeArguments();
List<? extends Coder<?>> typeArgumentCoders = coder.getCoderArguments();
if (typeArguments.length < typeArgumentCoders.size()) {
throw new IncompatibleCoderException(
String.format("Cannot encode elements of type %s with coder %s:"
+ " the generic supertype %s has %s type parameters, which is less than the"
+ " number of coder arguments %s has (%s).",
candidateOkDescriptor, coder,
parameterizedSupertype, typeArguments.length,
coder, typeArgumentCoders.size()),
coder, candidateOkDescriptor.getType());
}
for (int i = 0; i < typeArgumentCoders.size(); i++) {
try {
verifyCompatible(
typeArgumentCoders.get(i),
candidateDescriptor.resolveType(typeArguments[i]).getType());
} catch (IncompatibleCoderException exn) {
throw new IncompatibleCoderException(
String.format("Cannot encode elements of type %s with coder %s"
+ " because some component coder is incompatible",
candidateType, coder),
coder, candidateType, exn);
}
}
}
}
private static boolean isNullOrEmpty(Collection<?> c) {
return c == null || c.size() == 0;
}
/**
* The list of {@link CoderProvider coder providers} to use to provide Coders.
*/
private LinkedList<CoderProvider> coderProviders;
/**
* Returns a {@link Coder} to use for values of the given type,
* in a context where the given types use the given coders.
*
* @throws CannotProvideCoderException if a coder cannot be provided
*/
private <T> Coder<T> getCoderFromTypeDescriptor(
TypeDescriptor<T> typeDescriptor, Map<Type, Coder<?>> typeCoderBindings)
throws CannotProvideCoderException {
Type type = typeDescriptor.getType();
Coder<?> coder;
if (typeCoderBindings.containsKey(type)) {
coder = typeCoderBindings.get(type);
} else if (type instanceof Class<?>) {
coder = getCoderFromFactories(typeDescriptor, Collections.<Coder<?>>emptyList());
} else if (type instanceof ParameterizedType) {
coder = getCoderFromParameterizedType((ParameterizedType) type, typeCoderBindings);
} else if (type instanceof TypeVariable) {
coder = getCoderFromFactories(typeDescriptor, Collections.<Coder<?>>emptyList());
} else if (type instanceof WildcardType) {
// No coder for an unknown generic type.
throw new CannotProvideCoderException(
String.format("Cannot provide a coder for type variable %s"
+ " (declared by %s) because the actual type is unknown due to erasure.",
type,
((TypeVariable<?>) type).getGenericDeclaration()),
ReasonCode.TYPE_ERASURE);
} else {
throw new RuntimeException(
"Internal error: unexpected kind of Type: " + type);
}
LOG.debug("Coder for {}: {}", typeDescriptor, coder);
@SuppressWarnings("unchecked")
Coder<T> result = (Coder<T>) coder;
return result;
}
/**
* Returns a {@link Coder} to use for values of the given
* parameterized type, in a context where the given types use the
* given {@link Coder Coders}.
*
* @throws CannotProvideCoderException if no coder can be provided
*/
private Coder<?> getCoderFromParameterizedType(
ParameterizedType type,
Map<Type, Coder<?>> typeCoderBindings)
throws CannotProvideCoderException {
List<Coder<?>> typeArgumentCoders = new ArrayList<>();
for (Type typeArgument : type.getActualTypeArguments()) {
try {
Coder<?> typeArgumentCoder =
getCoderFromTypeDescriptor(TypeDescriptor.of(typeArgument), typeCoderBindings);
typeArgumentCoders.add(typeArgumentCoder);
} catch (CannotProvideCoderException exc) {
throw new CannotProvideCoderException(
String.format("Cannot provide coder for parameterized type %s: %s",
type,
exc.getMessage()),
exc);
}
}
return getCoderFromFactories(TypeDescriptor.of(type), typeArgumentCoders);
}
/**
* Attempts to create a {@link Coder} from any registered {@link CoderProvider} returning
* the first successfully created instance.
*/
private Coder<?> getCoderFromFactories(
TypeDescriptor<?> typeDescriptor, List<Coder<?>> typeArgumentCoders)
throws CannotProvideCoderException {
List<CannotProvideCoderException> suppressedExceptions = new ArrayList<>();
for (CoderProvider coderProvider : coderProviders) {
try {
return coderProvider.coderFor(typeDescriptor, typeArgumentCoders);
} catch (CannotProvideCoderException e) {
// Add all failures as suppressed exceptions.
suppressedExceptions.add(e);
}
}
// Build up the error message and list of causes.
StringBuilder messageBuilder = new StringBuilder()
.append("Unable to provide a Coder for ").append(typeDescriptor).append(".\n")
.append(" Building a Coder using a registered CoderProvider failed.\n")
.append(" See suppressed exceptions for detailed failures.");
CannotProvideCoderException exceptionOnFailure =
new CannotProvideCoderException(messageBuilder.toString());
for (CannotProvideCoderException suppressedException : suppressedExceptions) {
exceptionOnFailure.addSuppressed(suppressedException);
}
throw exceptionOnFailure;
}
/**
* Returns an immutable {@code Map} from each of the type variables
* embedded in the given type to the corresponding types
* in the given {@link Coder}.
*/
private Map<Type, Coder<?>> getTypeToCoderBindings(Type type, Coder<?> coder) {
checkArgument(type != null);
checkArgument(coder != null);
if (type instanceof TypeVariable || type instanceof Class) {
return ImmutableMap.<Type, Coder<?>>of(type, coder);
} else if (type instanceof ParameterizedType) {
return getTypeToCoderBindings((ParameterizedType) type, coder);
} else {
return ImmutableMap.of();
}
}
/**
* Returns an immutable {@code Map} from the type arguments of the parameterized type to their
* corresponding {@link Coder Coders}, and so on recursively for their type parameters.
*
* <p>This method is simply a specialization to break out the most
* elaborate case of {@link #getTypeToCoderBindings(Type, Coder)}.
*/
private Map<Type, Coder<?>> getTypeToCoderBindings(ParameterizedType type, Coder<?> coder) {
List<Type> typeArguments = Arrays.asList(type.getActualTypeArguments());
List<? extends Coder<?>> coderArguments = coder.getCoderArguments();
if ((coderArguments == null) || (typeArguments.size() != coderArguments.size())) {
return ImmutableMap.of();
} else {
Map<Type, Coder<?>> typeToCoder = Maps.newHashMap();
typeToCoder.put(type, coder);
for (int i = 0; i < typeArguments.size(); i++) {
Type typeArgument = typeArguments.get(i);
Coder<?> coderArgument = coderArguments.get(i);
if (coderArgument != null) {
typeToCoder.putAll(getTypeToCoderBindings(typeArgument, coderArgument));
}
}
return ImmutableMap.<Type, Coder<?>>builder().putAll(typeToCoder).build();
}
}
}