/*
* 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 java.io.ByteArrayOutputStream;
import java.util.Collections;
import java.util.List;
import org.apache.beam.sdk.values.TypeDescriptor;
/**
* An abstract base class to implement a {@link Coder} that defines equality, hashing, and printing
* via the class name and recursively using {@link #getComponents}.
*
* <p>A {@link StructuredCoder} should be defined purely in terms of its component coders, and
* contain no additional configuration.
*
* <p>To extend {@link StructuredCoder}, override the following methods as appropriate:
*
* <ul>
* <li>{@link #getComponents}: the default implementation returns {@link #getCoderArguments}.
* <li>{@link #getEncodedElementByteSize} and {@link #isRegisterByteSizeObserverCheap}: the
* default implementation encodes values to bytes and counts the bytes, which is considered
* expensive. The default element byte size observer uses the value returned by
* {@link #getEncodedElementByteSize}.
* </ul>
*/
public abstract class StructuredCoder<T> extends Coder<T> {
protected StructuredCoder() {}
/**
* Returns the list of {@link Coder Coders} that are components of this {@link Coder}.
*
* <p>The default components will be equal to the value returned by {@link #getCoderArguments()}.
*/
public List<? extends Coder<?>> getComponents() {
List<? extends Coder<?>> coderArguments = getCoderArguments();
if (coderArguments == null) {
return Collections.emptyList();
} else {
return coderArguments;
}
}
/**
* {@inheritDoc}
*
* @return {@code true} if the two {@link StructuredCoder} instances have the
* same class and equal components.
*/
@Override
public boolean equals(Object o) {
if (o == null || this.getClass() != o.getClass()) {
return false;
}
StructuredCoder<?> that = (StructuredCoder<?>) o;
return this.getComponents().equals(that.getComponents());
}
@Override
public int hashCode() {
return getClass().hashCode() * 31 + getComponents().hashCode();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
String s = getClass().getName();
builder.append(s.substring(s.lastIndexOf('.') + 1));
List<? extends Coder<?>> componentCoders = getComponents();
if (!componentCoders.isEmpty()) {
builder.append('(');
boolean first = true;
for (Coder<?> componentCoder : componentCoders) {
if (first) {
first = false;
} else {
builder.append(',');
}
builder.append(componentCoder.toString());
}
builder.append(')');
}
return builder.toString();
}
/**
* {@inheritDoc}
*
* @return {@code false} for {@link StructuredCoder} unless overridden.
*/
@Override
public boolean consistentWithEquals() {
return false;
}
@Override
public Object structuralValue(T value) {
if (value != null && consistentWithEquals()) {
return value;
} else {
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
encode(value, os, Context.OUTER);
return new StructuralByteArray(os.toByteArray());
} catch (Exception exn) {
throw new IllegalArgumentException(
"Unable to encode element '" + value + "' with coder '" + this + "'.", exn);
}
}
}
@SuppressWarnings("unchecked")
@Override
public TypeDescriptor<T> getEncodedTypeDescriptor() {
return (TypeDescriptor<T>)
TypeDescriptor.of(getClass()).resolveType(new TypeDescriptor<T>() {}.getType());
}
}