/* * 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.base.MoreObjects; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.List; import org.apache.beam.sdk.values.TypeDescriptor; /** * Static utility methods for creating and working with {@link CoderProvider}s. */ public final class CoderProviders { private CoderProviders() { } // Static utility class /** * Creates a {@link CoderProvider} from a class's * {@code static <T> Coder<T> of(TypeDescriptor<T>, List<Coder<?>>}) method. */ public static CoderProvider fromStaticMethods(Class<?> rawType, Class<?> coderClazz) { checkArgument( Coder.class.isAssignableFrom(coderClazz), "%s is not a subtype of %s", coderClazz.getName(), Coder.class.getSimpleName()); return new CoderProviderFromStaticMethods(rawType, coderClazz); } /** * Creates a {@link CoderProvider} that always returns the * given coder for the specified type. */ public static CoderProvider forCoder(TypeDescriptor<?> type, Coder<?> coder) { return new CoderProviderForCoder(type, coder); } /** * See {@link #fromStaticMethods} for a detailed description * of the characteristics of this {@link CoderProvider}. */ private static class CoderProviderFromStaticMethods extends CoderProvider { @Override public <T> Coder<T> coderFor(TypeDescriptor<T> type, List<? extends Coder<?>> componentCoders) throws CannotProvideCoderException { if (!this.rawType.equals(type.getRawType())) { throw new CannotProvideCoderException(String.format( "Unable to provide coder for %s, this factory can only provide coders for %s", type, this.rawType)); } try { return (Coder) factoryMethod.invoke( null /* static */, componentCoders.toArray()); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NullPointerException | ExceptionInInitializerError exn) { throw new IllegalStateException( "error when invoking Coder factory method " + factoryMethod, exn); } } //////////////////////////////////////////////////////////////////////////////// // Type raw type used to filter the incoming type on. private final Class<?> rawType; // Method to create a coder given component coders // For a Coder class of kind * -> * -> ... n times ... -> * // this has type Coder<?> -> Coder<?> -> ... n times ... -> Coder<T> private final Method factoryMethod; /** * Returns a CoderProvider that invokes the given static factory method * to create the Coder. */ private CoderProviderFromStaticMethods(Class<?> rawType, Class<?> coderClazz) { this.rawType = rawType; this.factoryMethod = getFactoryMethod(coderClazz); } /** * Returns the static {@code of} constructor method on {@code coderClazz} * if it exists. It is assumed to have one {@link Coder} parameter for * each type parameter of {@code coderClazz}. */ private Method getFactoryMethod(Class<?> coderClazz) { Method factoryMethodCandidate; // Find the static factory method of coderClazz named 'of' with // the appropriate number of type parameters. int numTypeParameters = coderClazz.getTypeParameters().length; Class<?>[] factoryMethodArgTypes = new Class<?>[numTypeParameters]; Arrays.fill(factoryMethodArgTypes, Coder.class); try { factoryMethodCandidate = coderClazz.getDeclaredMethod("of", factoryMethodArgTypes); } catch (NoSuchMethodException | SecurityException exn) { throw new IllegalArgumentException( "cannot register Coder " + coderClazz + ": " + "does not have an accessible method named 'of' with " + numTypeParameters + " arguments of Coder type", exn); } if (!Modifier.isStatic(factoryMethodCandidate.getModifiers())) { throw new IllegalArgumentException( "cannot register Coder " + coderClazz + ": " + "method named 'of' with " + numTypeParameters + " arguments of Coder type is not static"); } if (!coderClazz.isAssignableFrom(factoryMethodCandidate.getReturnType())) { throw new IllegalArgumentException( "cannot register Coder " + coderClazz + ": " + "method named 'of' with " + numTypeParameters + " arguments of Coder type does not return a " + coderClazz); } try { if (!factoryMethodCandidate.isAccessible()) { factoryMethodCandidate.setAccessible(true); } } catch (SecurityException exn) { throw new IllegalArgumentException( "cannot register Coder " + coderClazz + ": " + "method named 'of' with " + numTypeParameters + " arguments of Coder type is not accessible", exn); } return factoryMethodCandidate; } @Override public String toString() { return MoreObjects.toStringHelper(getClass()) .add("rawType", rawType) .add("factoryMethod", factoryMethod) .toString(); } } /** * See {@link #forCoder} for a detailed description of this {@link CoderProvider}. */ private static class CoderProviderForCoder extends CoderProvider { private final Coder<?> coder; private final TypeDescriptor<?> type; public CoderProviderForCoder(TypeDescriptor<?> type, Coder<?> coder){ this.type = type; this.coder = coder; } @Override public <T> Coder<T> coderFor(TypeDescriptor<T> type, List<? extends Coder<?>> componentCoders) throws CannotProvideCoderException { if (!this.type.equals(type)) { throw new CannotProvideCoderException(String.format( "Unable to provide coder for %s, this factory can only provide coders for %s", type, this.type)); } return (Coder) coder; } @Override public String toString() { return MoreObjects.toStringHelper(getClass()) .add("type", type) .add("coder", coder) .toString(); } } }