io.permazen.encoding.DefaultEncodingRegistry 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.Preconditions;
import com.google.common.reflect.TypeToken;
import java.io.File;
import java.net.URI;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* Permazen's default {@link EncodingRegistry}.
*
*
* Instances automatically register all of Permazen's built-in {@link Encoding}s (see below).
*
*
Array Types
*
*
* Because this class is a subclass of {@link SimpleEncodingRegistry}, encodings for array types
* (other than primitive arrays) are created on demand, so they don't need to be explicitly registered.
*
*
Enum Types
*
*
* Encodings for {@link Enum} types are not registered in an {@link EncodingRegistry}. Instead, {@link Enum}
* values are represented as {@link io.permazen.core.EnumValue} instances, and {@link Enum} fields are specially
* defined in the schema with an explicit identifier list. In turn, {@link io.permazen.core.EnumValue}'s are
* encoded by {@link io.permazen.core.EnumValueEncoding}.
*
*
Custom Encodings
*
*
* During construction, instances scan the class path for custom {@link EncodingRegistry} implementations
* and will delegate to them when an encoding is not found. Custom {@link EncodingRegistry} implementations are specified via
* {@code META-INF/services/io.permazen.encoding.EncodingRegistry} files or by module exports; see {@link ServiceLoader}.
* Custom implementations are only queried for non-array types.
*
*
* If multiple custom {@link EncodingRegistry} implementations advertise the same encoding, one will be
* chosen arbitrarily.
*
*
Built-in Encodings
*
*
* Permazen provides built-in {@link Encoding}s covering the following Java types:
*
* - Primitive types ({@code boolean}, {@code int}, etc.)
* - Primitive array types ({@code boolean[]}, {@code int[]}, etc.)
* - Primitive wrapper types ({@link Boolean}, {@link Integer}, etc.)
* - {@link String}
* - {@link java.math.BigDecimal}
* - {@link java.math.BigInteger}
* - {@link java.util.BitSet}
* - {@link java.util.Date}
* - {@link java.util.UUID}
* - {@link java.util.IntSummaryStatistics}
* - {@link java.util.LongSummaryStatistics}
* - {@link java.util.DoubleSummaryStatistics}
* - {@link java.net.URI}
* - {@link java.io.File}
* - {@link java.net.InetAddress}
* - {@link java.net.Inet4Address}
* - {@link java.net.Inet6Address}
* - {@link java.util.regex.Pattern}
* - {@link java.time java.time.*}
* - {@link jakarta.mail.internet.InternetAddress} (if present on the classpath)
*
*/
public class DefaultEncodingRegistry extends SimpleEncodingRegistry {
protected final ArrayList customEncodingRegistries = new ArrayList<>();
// Constructor
/**
* Constructor.
*
*
* This constructor invokes {@link #initialize}.
*/
@SuppressWarnings("this-escape")
public DefaultEncodingRegistry() {
this.initialize();
// System.out.println("ENCODINGS BY ID:");
// new java.util.TreeMap<>(this.byId).forEach((k, v) -> System.out.println(String.format(" %-40s -> %s", k, v)));
// System.out.println("ENCODINGS BY TYPETOKEN:");
// this.byType.forEach((k, v) -> System.out.println(String.format("%50s -> %s", k, v)));
// System.out.println("CUSTOM REGISTRIES:");
// this.customEncodingRegistries.forEach(r -> System.out.println(" " + r));
}
// EncodingRegistry
@Override
public EncodingId idForAlias(String alias) {
return EncodingIds.idForAlias(alias);
}
@Override
public String aliasForId(EncodingId encodingId) {
return EncodingIds.aliasForId(encodingId);
}
@Override
public synchronized Encoding> getEncoding(EncodingId encodingId) {
// See if we have it
Encoding> encoding = super.getEncoding(encodingId);
if (encoding != null)
return encoding;
// Try custom registries
encoding = this.customEncodingRegistries.stream()
.map(registry -> registry.getEncoding(encodingId))
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
if (encoding != null)
this.register(encodingId, encoding);
// Done
return encoding;
}
@Override
public synchronized List> getEncodings(TypeToken typeToken) {
// See if we have it
List> encodingList = super.getEncodings(typeToken);
if (!encodingList.isEmpty())
return encodingList;
// Try custom registries
return this.customEncodingRegistries.stream()
.map(registry -> registry.getEncodings(typeToken))
.flatMap(List::stream)
.peek(encoding -> this.register(encoding.getEncodingId(), encoding))
.collect(Collectors.toList());
}
// Internal Methods
/**
* Initialize this instance.
*
*
* This method is invoked by the default constructor. The implementation in {@link DefaultEncodingRegistry}
* invokes {@link #addBuiltinEncodings} and then {@link #findCustomEncodingRegistries}.
*/
protected void initialize() {
this.addBuiltinEncodings();
this.findCustomEncodingRegistries();
}
/**
* Register Permazen's built-in encodings.
*
*
* The implementation in {@link DefaultEncodingRegistry} invokes {@link #addStandardBuiltinEncodings}
* and then {@link #addOptionalBuiltinEncodings}.
*/
protected void addBuiltinEncodings() {
this.addStandardBuiltinEncodings();
this.addOptionalBuiltinEncodings();
}
/**
* Register Permazen's standard built-in encodings.
*/
protected void addStandardBuiltinEncodings() {
// Get primitive type EncodingId's
final EncodingId z = EncodingIds.builtin("boolean");
final EncodingId b = EncodingIds.builtin("byte");
final EncodingId c = EncodingIds.builtin("char");
final EncodingId d = EncodingIds.builtin("double");
final EncodingId f = EncodingIds.builtin("float");
final EncodingId i = EncodingIds.builtin("int");
final EncodingId j = EncodingIds.builtin("long");
final EncodingId s = EncodingIds.builtin("short");
// Add primitive types
this.add(new BooleanEncoding(z));
this.add(new ByteEncoding(b));
this.add(new CharacterEncoding(c));
this.add(new DoubleEncoding(d));
this.add(new FloatEncoding(f));
this.add(new IntegerEncoding(i));
this.add(new LongEncoding(j));
this.add(new ShortEncoding(s));
// Add primitive wrapper types
this.add(new PrimitiveWrapperEncoding<>(new BooleanEncoding(null)));
this.add(new PrimitiveWrapperEncoding<>(new ByteEncoding(null)));
this.add(new PrimitiveWrapperEncoding<>(new CharacterEncoding(null)));
this.add(new PrimitiveWrapperEncoding<>(new DoubleEncoding(null)));
this.add(new PrimitiveWrapperEncoding<>(new FloatEncoding(null)));
this.add(new PrimitiveWrapperEncoding<>(new IntegerEncoding(null)));
this.add(new PrimitiveWrapperEncoding<>(new LongEncoding(null)));
this.add(new PrimitiveWrapperEncoding<>(new ShortEncoding(null)));
this.add(new PrimitiveWrapperEncoding<>(new VoidEncoding()));
// Add primitive array types
this.addNullSafe(z.getArrayId(), new BooleanArrayEncoding());
this.addNullSafe(b.getArrayId(), new ByteArrayEncoding());
this.addNullSafe(c.getArrayId(), new CharacterArrayEncoding());
this.addNullSafe(d.getArrayId(), new DoubleArrayEncoding());
this.addNullSafe(f.getArrayId(), new FloatArrayEncoding());
this.addNullSafe(i.getArrayId(), new IntegerArrayEncoding());
this.addNullSafe(j.getArrayId(), new LongArrayEncoding());
this.addNullSafe(s.getArrayId(), new ShortArrayEncoding());
// Built-in types in java.lang
this.addWrappedBuiltin(new StringEncoding());
// Built-in types in java.math
this.addWrappedBuiltin(new BigDecimalEncoding());
this.addWrappedBuiltin(new BigIntegerEncoding());
// Built-in types in java.io
this.addBuiltin(File.class, FileEncoding::new);
// Built-in types in java.util
this.addWrappedBuiltin(new BitSetEncoding());
this.addWrappedBuiltin(new DateEncoding());
this.addWrappedBuiltin(new UUIDEncoding());
this.addWrappedBuiltin(new IntSummaryStatisticsEncoding());
this.addWrappedBuiltin(new LongSummaryStatisticsEncoding());
this.addWrappedBuiltin(new DoubleSummaryStatisticsEncoding());
// Built-in types in java.util.regex
this.addBuiltin(Pattern.class, PatternEncoding::new);
// Built-in types in java.net
this.addWrappedBuiltin(new Inet4AddressEncoding());
this.addWrappedBuiltin(new Inet6AddressEncoding());
this.addWrappedBuiltin(new InetAddressEncoding());
this.addBuiltin(URI.class, URIEncoding::new);
// Built-in types in java.time
this.addWrappedBuiltin(new DurationEncoding());
this.addWrappedBuiltin(new InstantEncoding());
this.addWrappedBuiltin(new LocalDateTimeEncoding());
this.addWrappedBuiltin(new LocalDateEncoding());
this.addWrappedBuiltin(new LocalTimeEncoding());
this.addWrappedBuiltin(new MonthDayEncoding());
this.addWrappedBuiltin(new OffsetDateTimeEncoding());
this.addWrappedBuiltin(new OffsetTimeEncoding());
this.addWrappedBuiltin(new PeriodEncoding());
this.addWrappedBuiltin(new YearMonthEncoding());
this.addWrappedBuiltin(new YearEncoding());
this.addWrappedBuiltin(new ZoneOffsetEncoding());
this.addWrappedBuiltin(new ZonedDateTimeEncoding());
this.addBuiltin(ZoneId.class, ZoneIdEncoding::new);
}
private void addBuiltin(Class type, Function> ctor) {
final Encoding encoding = ctor.apply(EncodingIds.builtin(type.getSimpleName()));
Preconditions.checkArgument(encoding.supportsNull(), "encoding does not support null");
this.add(encoding);
}
private void addWrappedBuiltin(Encoding> encoding) {
final Class> javaType = encoding.getTypeToken().getRawType();
final EncodingId encodingId = EncodingIds.builtin(javaType.getSimpleName());
this.addNullSafe(encodingId, encoding);
}
/**
* Register Permazen's optional built-in encodings.
*
*
* The optional built-in encodings are ones that depend on optional dependencies. An example is
* {@link InternetAddressEncoding} which depends on the Jakarta Mail API.
*/
protected void addOptionalBuiltinEncodings() {
this.addOptionalBuiltinEncoding("InternetAddress", InternetAddressEncoding::new);
}
/**
* Register a built-in encoding, but only if its target class is found on the classpath.
*
*
* The implementation in {@link DefaultEncodingRegistry} invokes the given {@code builder} but will catch
* and ignore any {@link NoClassDefFoundError} thrown. Otherwise, the encoding is registered.
*
*
* No attempt to initialize
* the encoding class should have occurred prior to invoking this method.
*
* @param name builtin encoding ID suffix
* @param builder builder for encoding
*/
protected void addOptionalBuiltinEncoding(String name, Function> builder) {
Preconditions.checkArgument(name != null, "null name");
Preconditions.checkArgument(builder != null, "null builder");
final EncodingId encodingId = EncodingIds.builtin(name);
final Encoding> encoding;
try {
encoding = builder.apply(encodingId);
} catch (NoClassDefFoundError e) {
this.log.debug("{}: not adding optional encoding \"{}\": {}",
this.getClass().getSimpleName(), encodingId, e.toString());
return;
}
Preconditions.checkArgument(encoding != null, "null encoding returned from builder");
this.add(encoding);
}
/**
* Scan the class path (via {@link ServiceLoader} and the current thread's context class loader)
* for custom {@link EncodingRegistry} implementations
*/
protected void findCustomEncodingRegistries() {
for (Iterator i = ServiceLoader.load(EncodingRegistry.class).iterator(); i.hasNext(); ) {
final EncodingRegistry customEncodingRegistry = i.next();
this.log.debug("{}: including custom encoding registry {}", this.getClass().getSimpleName(), customEncodingRegistry);
this.customEncodingRegistries.add(customEncodingRegistry);
}
}
}