io.permazen.encoding.ConvertedEncoding Maven / Gradle / Ivy
Show all versions of permazen-encoding Show documentation
/*
* Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
*/
package io.permazen.encoding;
import com.google.common.base.Converter;
import com.google.common.base.Preconditions;
import com.google.common.reflect.TypeToken;
import io.permazen.util.ByteReader;
import io.permazen.util.ByteWriter;
import java.util.OptionalInt;
import java.util.function.Supplier;
/**
* An {@link Encoding} that relies on some other {@link Encoding}, converting values to/from
* the other {@link Encoding}'s Java type.
*
*
* This class provides a convenient way to implement custom {@link Encoding}s when the target values can be converted
* into some other form for which another {@link Encoding} (the "delegate") already exists. A Guava {@link Converter}
* converts values between the type supported by this encoding and the type supported by the delegate encoding.
* It's {@link Converter#convert convert()} method must throw {@link IllegalArgumentException} if an unsupported
* value is encountered. The {@link Converter} should be non-lossy when supported values are round-tripped.
*
*
* This encoding will sort values in the same order as the delegate sorts the corresponding converted values.
* Null values are supported by this encoding when the delegate supports them; if so, null always converts to/from null.
*
*
* This encoding has a default value when the delegate has one, namely, the conversion of the delegate's default value.
*
*
* By default, the {@link String} form of a value is just the {@link String} form of its conversion. Subclasses may
* therefore want to override {@link #toString(T) toString()} and {@link #fromString fromString()}.
*
*
* The {@link Converter} must be {@link java.io.Serializable} in order for an instance of this class to also be
* {@link java.io.Serializable}.
*
* @param This encoder's value type
* @param The delegate encoder's value type
*/
public class ConvertedEncoding extends AbstractEncoding {
private static final long serialVersionUID = -1873774754387478399L;
protected final Encoding delegate;
@SuppressWarnings("serial")
protected final Converter converter;
private final boolean sortsNaturally;
// Constructors
/**
* Primary constructor.
*
* @param encodingId encoding ID for this encoding, or null to be anonymous
* @param typeToken represented Java type
* @param delegate delegate encoder
* @param converter value converter
* @param sortsNaturally true if this encoding {@linkplain Encoding#sortsNaturally sorts naturally}, otherwise false
* @throws IllegalArgumentException if any parameter other than {@code defaultValueSupplier} is null
*/
public ConvertedEncoding(EncodingId encodingId, TypeToken typeToken,
Encoding delegate, Converter converter, boolean sortsNaturally) {
super(encodingId, typeToken, ConvertedEncoding.convertedDefault(delegate, converter));
Preconditions.checkArgument(converter.convert(null) == null && converter.reverse().convert(null) == null,
"converter does not convert null to null");
this.delegate = delegate;
this.converter = converter;
this.sortsNaturally = sortsNaturally;
}
/**
* Convenience constructor taking {@link Class} instead of {@link TypeToken}.
*
* @param encodingId encoding ID for this encoding, or null to be anonymous
* @param type represented Java type
* @param delegate delegate encoder
* @param converter converts between native form and {@link String} form; should be {@link java.io.Serializable}
* @param sortsNaturally true if this encoding {@linkplain Encoding#sortsNaturally sorts naturally}, otherwise false
* @throws IllegalArgumentException if any parameter other than {@code defaultValueSupplier} is null
*/
public ConvertedEncoding(EncodingId encodingId, Class type,
Encoding delegate, Converter converter, boolean sortsNaturally) {
this(encodingId, TypeToken.of(AbstractEncoding.noNull(type, "type")), delegate, converter, sortsNaturally);
}
/**
* Convenience constructor for when the new {@link ConvertedEncoding} does not sort naturally.
*
* @param encodingId encoding ID for this encoding, or null to be anonymous
* @param type represented Java type
* @param delegate delegate encoder
* @param converter value converter
* @throws IllegalArgumentException if any parameter is null
*/
public ConvertedEncoding(EncodingId encodingId, Class type, Encoding delegate, Converter converter) {
this(encodingId, type, delegate, converter, false);
}
private static Supplier extends T> convertedDefault(Encoding delegate, Converter converter) {
Preconditions.checkArgument(delegate != null, "null delegate");
Preconditions.checkArgument(converter != null, "null converter");
final S defaultValue;
try {
defaultValue = delegate.getDefaultValue();
} catch (UnsupportedOperationException e) {
return null;
}
final Converter reverseConverter = converter.reverse();
return () -> reverseConverter.convert(defaultValue);
}
// Encoding
@Override
public T read(ByteReader reader) {
return this.converter.reverse().convert(this.delegate.read(reader));
}
@Override
public void write(ByteWriter writer, T obj) {
this.delegate.write(writer, this.converter.convert(obj));
}
@Override
public void skip(ByteReader reader) {
this.delegate.skip(reader);
}
@Override
public String toString(T obj) {
Preconditions.checkArgument(obj != null, "null value");
return this.delegate.toString(this.converter.convert(obj));
}
@Override
public T fromString(String string) {
Preconditions.checkArgument(string != null, "null string");
return this.converter.reverse().convert(this.delegate.fromString(string));
}
@Override
public boolean sortsNaturally() {
return this.sortsNaturally;
}
@Override
public final boolean supportsNull() {
return this.delegate.supportsNull();
}
@Override
public final OptionalInt getFixedWidth() {
return this.delegate.getFixedWidth();
}
@Override
public T validate(Object obj) {
final T value = super.validate(obj);
this.delegate.validate(this.converter.convert(value));
return value;
}
@Override
public int compare(T obj1, T obj2) {
return this.delegate.compare(this.converter.convert(obj1), this.converter.convert(obj2));
}
@Override
public boolean hasPrefix0x00() {
return this.delegate.hasPrefix0x00();
}
@Override
public boolean hasPrefix0xff() {
return this.delegate.hasPrefix0xff();
}
// Object
@Override
public int hashCode() {
return super.hashCode()
^ this.delegate.hashCode()
^ this.converter.hashCode()
^ Boolean.hashCode(this.sortsNaturally);
}
@Override
public boolean equals(Object obj) {
if (obj == this)
return true;
if (!super.equals(obj))
return false;
final ConvertedEncoding, ?> that = (ConvertedEncoding, ?>)obj;
return this.delegate.equals(that.delegate)
&& this.converter.equals(that.converter)
&& this.sortsNaturally == that.sortsNaturally;
}
}