diff --git a/core/src/main/java/org/infinispan/protostream/BaseMarshaller.java b/core/src/main/java/org/infinispan/protostream/BaseMarshaller.java index 9489ce370..4e8bcc857 100644 --- a/core/src/main/java/org/infinispan/protostream/BaseMarshaller.java +++ b/core/src/main/java/org/infinispan/protostream/BaseMarshaller.java @@ -9,6 +9,7 @@ * @since 1.0 */ public interface BaseMarshaller { + String[] EMPTY = new String[0]; /** * Returns the Java type handled by this marshaller. This must not change over multiple invocations. @@ -24,4 +25,11 @@ public interface BaseMarshaller { * @return the full name of the message or enum type, defined in a proto file. */ String getTypeName(); + + /** + * Returns any subclass names + */ + default String[] getSubClassNames() { + return EMPTY; + } } diff --git a/core/src/main/java/org/infinispan/protostream/GeneratedSchema.java b/core/src/main/java/org/infinispan/protostream/GeneratedSchema.java index cd42b2c40..a1a985861 100644 --- a/core/src/main/java/org/infinispan/protostream/GeneratedSchema.java +++ b/core/src/main/java/org/infinispan/protostream/GeneratedSchema.java @@ -8,7 +8,7 @@ * An annotation-based generated proto schema file. This is just a more specific flavour of * {@link SerializationContextInitializer} that also exposes the generated Protobuf schema, which consists of the file * name and the file contents. Users will never implement this interface directly. Implementations are always generated - * by the annotation processor based on the {@link org.infinispan.protostream.annotations.AutoProtoSchemaBuilder} + * by the annotation processor based on the {@link org.infinispan.protostream.annotations.ProtoSchema} * annotation, identically as for {@link SerializationContextInitializer}. * * @author anistor@redhat.com diff --git a/core/src/main/java/org/infinispan/protostream/annotations/ProtoAdapter.java b/core/src/main/java/org/infinispan/protostream/annotations/ProtoAdapter.java index 286c9b3e5..4999fc2d3 100644 --- a/core/src/main/java/org/infinispan/protostream/annotations/ProtoAdapter.java +++ b/core/src/main/java/org/infinispan/protostream/annotations/ProtoAdapter.java @@ -34,4 +34,10 @@ * The actual class being marshalled. */ Class value(); + + /** + * Any sub-classes of the main class which should also be handled by this adaptor. This is useful, for example, + * when the actual class is determined by a factory method, such as {@link java.util.List#of} + */ + String[] subClassNames() default {}; } diff --git a/core/src/main/java/org/infinispan/protostream/annotations/impl/ProtoTypeMetadata.java b/core/src/main/java/org/infinispan/protostream/annotations/impl/ProtoTypeMetadata.java index a19942ad7..58c22df7c 100644 --- a/core/src/main/java/org/infinispan/protostream/annotations/impl/ProtoTypeMetadata.java +++ b/core/src/main/java/org/infinispan/protostream/annotations/impl/ProtoTypeMetadata.java @@ -1,5 +1,6 @@ package org.infinispan.protostream.annotations.impl; +import org.infinispan.protostream.annotations.ProtoAdapter; import org.infinispan.protostream.annotations.ProtoSyntax; import org.infinispan.protostream.annotations.ProtoTypeId; import org.infinispan.protostream.annotations.impl.types.XClass; @@ -89,6 +90,11 @@ public String getJavaClassName() { return canonicalName != null ? canonicalName : javaClass.getName(); } + public String[] getSubClassNames() { + ProtoAdapter adapter = getAnnotatedClass().getAnnotation(ProtoAdapter.class); + return adapter != null ? adapter.subClassNames() : null; + } + /** * At this level we pretend the Java class and the annotated class are one and the same, but subclasses * may decide otherwise. diff --git a/core/src/main/java/org/infinispan/protostream/descriptors/ResolutionContext.java b/core/src/main/java/org/infinispan/protostream/descriptors/ResolutionContext.java index 6c56d7a00..0fdc585d7 100644 --- a/core/src/main/java/org/infinispan/protostream/descriptors/ResolutionContext.java +++ b/core/src/main/java/org/infinispan/protostream/descriptors/ResolutionContext.java @@ -26,13 +26,13 @@ public class ResolutionContext { private final Map typeIds = new HashMap<>(); - private Map allGlobalTypes; + private final Map allGlobalTypes; - private Map globalTypes = new HashMap<>(); + private final Map globalTypes = new HashMap<>(); - private Map allEnumValueDescriptors; + private final Map allEnumValueDescriptors; - private Map enumValueDescriptors = new HashMap<>(); + private final Map enumValueDescriptors = new HashMap<>(); public ResolutionContext(FileDescriptorSource.ProgressCallback progressCallback, Map fileDescriptorMap, @@ -100,8 +100,7 @@ void addGenericDescriptor(GenericDescriptor genericDescriptor) { globalTypes.put(genericDescriptor.getFullName(), genericDescriptor); - if (genericDescriptor instanceof EnumDescriptor) { - EnumDescriptor enumDescriptor = (EnumDescriptor) genericDescriptor; + if (genericDescriptor instanceof EnumDescriptor enumDescriptor) { for (EnumValueDescriptor ev : enumDescriptor.getValues()) { enumValueDescriptors.put(ev.getScopedName(), ev); } @@ -127,8 +126,7 @@ private void checkUniqueName(GenericDescriptor genericDescriptor) { + " clashes with enum value " + existingEnumValueDescriptor.getFullName()); } - if (genericDescriptor instanceof EnumDescriptor) { - EnumDescriptor enumDescriptor = (EnumDescriptor) genericDescriptor; + if (genericDescriptor instanceof EnumDescriptor enumDescriptor) { for (EnumValueDescriptor ev : enumDescriptor.getValues()) { // check if this enum value constant conflicts with another enum value constant existingEnumValueDescriptor = lookup(enumValueDescriptors, allEnumValueDescriptors, ev.getScopedName()); diff --git a/core/src/main/java/org/infinispan/protostream/impl/SerializationContextImpl.java b/core/src/main/java/org/infinispan/protostream/impl/SerializationContextImpl.java index 9a2b14fde..635a25e71 100644 --- a/core/src/main/java/org/infinispan/protostream/impl/SerializationContextImpl.java +++ b/core/src/main/java/org/infinispan/protostream/impl/SerializationContextImpl.java @@ -1,7 +1,7 @@ package org.infinispan.protostream.impl; import java.util.ArrayList; -import java.util.Collections; +import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -32,6 +32,7 @@ * @since 1.0 */ public final class SerializationContextImpl implements SerializationContext { + static final Class[] EMPTY_CLASSES = new Class[0]; private static final Log log = Log.LogFactory.getLog(SerializationContextImpl.class); @@ -80,7 +81,7 @@ public Configuration getConfiguration() { public Map getFileDescriptors() { long stamp = descriptorLock.readLock(); try { - return Collections.unmodifiableMap(new HashMap<>(fileDescriptors)); + return Map.copyOf(fileDescriptors); } finally { descriptorLock.unlockRead(stamp); } @@ -248,6 +249,14 @@ public void registerMarshaller(BaseMarshaller marshaller) { throw new IllegalArgumentException("The given marshaller attempts to override an existing marshaller registered indirectly via an InstanceMarshallerProvider. Please unregister it first."); } + final Class[] subClasses; + String[] subClassNames = marshaller.getSubClassNames(); + if (subClassNames.length > 0) { + subClasses = Arrays.stream(subClassNames).map(SerializationContextImpl::classForName).toArray(Class[]::new); + } else { + subClasses = EMPTY_CLASSES; + } + if (existingByName != null) { Registration anotherByClass = marshallersByClass.get(existingByName.marshallerDelegate.getMarshaller().getJavaClass()); if (anotherByClass == null) { @@ -261,18 +270,33 @@ public void registerMarshaller(BaseMarshaller marshaller) { } } marshallersByClass.remove(existingByName.marshallerDelegate.getMarshaller().getJavaClass()); + for (Class subClass : subClasses) { + marshallersByClass.remove(subClass); + } } if (existingByClass != null) { marshallersByName.remove(existingByClass.marshallerDelegate.getMarshaller().getTypeName()); } + Registration registration = new Registration(makeMarshallerDelegate(marshaller)); marshallersByClass.put(marshaller.getJavaClass(), registration); marshallersByName.put(marshaller.getTypeName(), registration); + for (Class subClass : subClasses) { + marshallersByClass.put(subClass, registration); + } } finally { manifestLock.unlockWrite(stamp); } } + static Class classForName(String name) { + try { + return Class.forName(name); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + private BaseMarshallerDelegate makeMarshallerDelegate(BaseMarshaller marshaller) { if (marshaller.getJavaClass().isEnum() && !(marshaller instanceof EnumMarshaller)) { throw new IllegalArgumentException("Invalid marshaller (the produced class is a Java Enum, but the marshaller is not an EnumMarshaller) : " + marshaller.getClass().getName()); diff --git a/processor/src/main/java/org/infinispan/protostream/annotations/impl/processor/MarshallerSourceCodeGenerator.java b/processor/src/main/java/org/infinispan/protostream/annotations/impl/processor/MarshallerSourceCodeGenerator.java index a8268db6c..ec099b587 100644 --- a/processor/src/main/java/org/infinispan/protostream/annotations/impl/processor/MarshallerSourceCodeGenerator.java +++ b/processor/src/main/java/org/infinispan/protostream/annotations/impl/processor/MarshallerSourceCodeGenerator.java @@ -6,9 +6,11 @@ import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.Arrays; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; +import java.util.stream.Collectors; import javax.lang.model.element.Element; @@ -201,6 +203,15 @@ private void generateMessageMarshaller(ProtoMessageTypeMetadata pmtm) throws IOE iw.println("@Override"); iw.printf("public String getTypeName() { return \"%s\"; }\n", makeQualifiedTypeName(pmtm.getFullName())); iw.println(); + String[] subClassNames = pmtm.getSubClassNames(); + if (subClassNames != null && subClassNames.length > 0) { + iw.println("@Override"); + iw.println("public String[] getSubClassNames() {"); + iw.inc().print("return new String[] {"); + iw.print(Arrays.stream(subClassNames).collect(Collectors.joining(",", "\"", "\""))); + iw.println("};"); + iw.dec().println("}"); + } if (pmtm.isIndexedContainer()) { if (pmtm.isAdapter()) { diff --git a/types/src/main/java/org/infinispan/protostream/types/java/CommonContainerTypes.java b/types/src/main/java/org/infinispan/protostream/types/java/CommonContainerTypes.java index aff5a8440..a8d0f6649 100644 --- a/types/src/main/java/org/infinispan/protostream/types/java/CommonContainerTypes.java +++ b/types/src/main/java/org/infinispan/protostream/types/java/CommonContainerTypes.java @@ -1,7 +1,7 @@ package org.infinispan.protostream.types.java; import org.infinispan.protostream.GeneratedSchema; -import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder; +import org.infinispan.protostream.annotations.ProtoSchema; import org.infinispan.protostream.types.java.arrays.BooleanArrayAdapter; import org.infinispan.protostream.types.java.arrays.BoxedBooleanArrayAdapter; import org.infinispan.protostream.types.java.arrays.BoxedByteArrayAdapter; @@ -29,7 +29,7 @@ * @author anistor@redhat.com * @since 4.4 */ -@AutoProtoSchemaBuilder( +@ProtoSchema( className = "CommonContainerTypesSchema", schemaFileName = "common-java-container-types.proto", schemaFilePath = "/protostream", diff --git a/types/src/main/java/org/infinispan/protostream/types/java/CommonTypes.java b/types/src/main/java/org/infinispan/protostream/types/java/CommonTypes.java index f744975b8..568077507 100644 --- a/types/src/main/java/org/infinispan/protostream/types/java/CommonTypes.java +++ b/types/src/main/java/org/infinispan/protostream/types/java/CommonTypes.java @@ -4,6 +4,17 @@ import org.infinispan.protostream.annotations.ProtoSchema; import org.infinispan.protostream.types.java.math.BigDecimalAdapter; import org.infinispan.protostream.types.java.math.BigIntegerAdapter; +import org.infinispan.protostream.types.java.time.LocalDateAdapter; +import org.infinispan.protostream.types.java.time.LocalDateTimeAdapter; +import org.infinispan.protostream.types.java.time.LocalTimeAdapter; +import org.infinispan.protostream.types.java.time.MonthAdapter; +import org.infinispan.protostream.types.java.time.MonthDayAdapter; +import org.infinispan.protostream.types.java.time.OffsetTimeAdapter; +import org.infinispan.protostream.types.java.time.PeriodAdapter; +import org.infinispan.protostream.types.java.time.YearAdapter; +import org.infinispan.protostream.types.java.time.ZoneIdAdapter; +import org.infinispan.protostream.types.java.time.ZoneOffsetAdapter; +import org.infinispan.protostream.types.java.time.ZonedDateTimeAdapter; import org.infinispan.protostream.types.java.util.BitSetAdapter; import org.infinispan.protostream.types.java.util.UUIDAdapter; @@ -22,7 +33,18 @@ UUIDAdapter.class, BigIntegerAdapter.class, BigDecimalAdapter.class, - BitSetAdapter.class + BitSetAdapter.class, + LocalDateAdapter.class, + LocalDateTimeAdapter.class, + LocalTimeAdapter.class, + MonthAdapter.class, + MonthDayAdapter.class, + OffsetTimeAdapter.class, + PeriodAdapter.class, + YearAdapter.class, + ZonedDateTimeAdapter.class, + ZoneIdAdapter.class, + ZoneOffsetAdapter.class } ) public interface CommonTypes extends GeneratedSchema { diff --git a/types/src/main/java/org/infinispan/protostream/types/java/time/LocalDateAdapter.java b/types/src/main/java/org/infinispan/protostream/types/java/time/LocalDateAdapter.java new file mode 100644 index 000000000..44d29a86e --- /dev/null +++ b/types/src/main/java/org/infinispan/protostream/types/java/time/LocalDateAdapter.java @@ -0,0 +1,21 @@ +package org.infinispan.protostream.types.java.time; + +import java.time.LocalDate; + +import org.infinispan.protostream.annotations.ProtoAdapter; +import org.infinispan.protostream.annotations.ProtoFactory; +import org.infinispan.protostream.annotations.ProtoField; +import org.infinispan.protostream.descriptors.Type; + +@ProtoAdapter(LocalDate.class) +public class LocalDateAdapter { + @ProtoFactory + LocalDate create(Long epochDay) { + return LocalDate.ofEpochDay(epochDay); + } + + @ProtoField(number = 1, type = Type.UINT64) + Long getEpochDay(LocalDate localDate) { + return localDate.toEpochDay(); + } +} diff --git a/types/src/main/java/org/infinispan/protostream/types/java/time/LocalDateTimeAdapter.java b/types/src/main/java/org/infinispan/protostream/types/java/time/LocalDateTimeAdapter.java new file mode 100644 index 000000000..5f197c44d --- /dev/null +++ b/types/src/main/java/org/infinispan/protostream/types/java/time/LocalDateTimeAdapter.java @@ -0,0 +1,28 @@ +package org.infinispan.protostream.types.java.time; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; + +import org.infinispan.protostream.annotations.ProtoAdapter; +import org.infinispan.protostream.annotations.ProtoFactory; +import org.infinispan.protostream.annotations.ProtoField; +import org.infinispan.protostream.descriptors.Type; + +@ProtoAdapter(LocalDateTime.class) +public class LocalDateTimeAdapter { + @ProtoFactory + LocalDateTime create(LocalDate localDate, LocalTime localTime) { + return LocalDateTime.of(localDate, localTime); + } + + @ProtoField(number = 1, type = Type.MESSAGE) + LocalDate getLocalDate(LocalDateTime localDateTime) { + return localDateTime.toLocalDate(); + } + + @ProtoField(number = 2, type = Type.MESSAGE) + LocalTime getLocalTime(LocalDateTime localDateTime) { + return localDateTime.toLocalTime() ; + } +} diff --git a/types/src/main/java/org/infinispan/protostream/types/java/time/LocalTimeAdapter.java b/types/src/main/java/org/infinispan/protostream/types/java/time/LocalTimeAdapter.java new file mode 100644 index 000000000..be67b5250 --- /dev/null +++ b/types/src/main/java/org/infinispan/protostream/types/java/time/LocalTimeAdapter.java @@ -0,0 +1,20 @@ +package org.infinispan.protostream.types.java.time; + +import java.time.LocalTime; + +import org.infinispan.protostream.annotations.ProtoAdapter; +import org.infinispan.protostream.annotations.ProtoFactory; +import org.infinispan.protostream.annotations.ProtoField; +import org.infinispan.protostream.descriptors.Type; + +@ProtoAdapter(LocalTime.class) +public class LocalTimeAdapter { + @ProtoFactory + LocalTime create(Long nanoOfDay) { + return LocalTime.ofNanoOfDay(nanoOfDay); + } + @ProtoField(number = 1, type = Type.UINT64) + Long getNanoOfDay(LocalTime localTime) { + return localTime.toNanoOfDay(); + } +} diff --git a/types/src/main/java/org/infinispan/protostream/types/java/time/MonthAdapter.java b/types/src/main/java/org/infinispan/protostream/types/java/time/MonthAdapter.java new file mode 100644 index 000000000..922c588d8 --- /dev/null +++ b/types/src/main/java/org/infinispan/protostream/types/java/time/MonthAdapter.java @@ -0,0 +1,36 @@ +package org.infinispan.protostream.types.java.time; + +import java.time.Month; + +import org.infinispan.protostream.annotations.ProtoAdapter; +import org.infinispan.protostream.annotations.ProtoEnumValue; +import org.infinispan.protostream.annotations.ProtoName; + +@ProtoAdapter(Month.class) +@ProtoName("Month") +public enum MonthAdapter { + @ProtoEnumValue + JANUARY, + @ProtoEnumValue(value = 1) + FEBRUARY, + @ProtoEnumValue(value = 2) + MARCH, + @ProtoEnumValue(value = 3) + APRIL, + @ProtoEnumValue(value = 4) + MAY, + @ProtoEnumValue(value = 5) + JUNE, + @ProtoEnumValue(value = 6) + JULY, + @ProtoEnumValue(value = 7) + AUGUST, + @ProtoEnumValue(value = 8) + SEPTEMBER, + @ProtoEnumValue(value = 9) + OCTOBER, + @ProtoEnumValue(value = 10) + NOVEMBER, + @ProtoEnumValue(value = 11) + DECEMBER; +} diff --git a/types/src/main/java/org/infinispan/protostream/types/java/time/MonthDayAdapter.java b/types/src/main/java/org/infinispan/protostream/types/java/time/MonthDayAdapter.java new file mode 100644 index 000000000..e6932ff98 --- /dev/null +++ b/types/src/main/java/org/infinispan/protostream/types/java/time/MonthDayAdapter.java @@ -0,0 +1,25 @@ +package org.infinispan.protostream.types.java.time; + +import java.time.MonthDay; + +import org.infinispan.protostream.annotations.ProtoAdapter; +import org.infinispan.protostream.annotations.ProtoFactory; +import org.infinispan.protostream.annotations.ProtoField; +import org.infinispan.protostream.descriptors.Type; + +@ProtoAdapter(MonthDay.class) +public class MonthDayAdapter { + @ProtoFactory + MonthDay create(Integer month, Integer dayOfMonth) { + return MonthDay.of(month, dayOfMonth); + } + @ProtoField(number = 1, type = Type.INT32) + Integer getMonth(MonthDay monthDay) { + return monthDay.getMonthValue(); + } + + @ProtoField(number = 2, type = Type.INT32) + Integer getDayOfMonth(MonthDay monthDay) { + return monthDay.getDayOfMonth(); + } +} diff --git a/types/src/main/java/org/infinispan/protostream/types/java/time/OffsetTimeAdapter.java b/types/src/main/java/org/infinispan/protostream/types/java/time/OffsetTimeAdapter.java new file mode 100644 index 000000000..a7661d550 --- /dev/null +++ b/types/src/main/java/org/infinispan/protostream/types/java/time/OffsetTimeAdapter.java @@ -0,0 +1,28 @@ +package org.infinispan.protostream.types.java.time; + +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZoneOffset; + +import org.infinispan.protostream.annotations.ProtoAdapter; +import org.infinispan.protostream.annotations.ProtoFactory; +import org.infinispan.protostream.annotations.ProtoField; +import org.infinispan.protostream.descriptors.Type; + +@ProtoAdapter(OffsetTime.class) +public class OffsetTimeAdapter { + @ProtoFactory + OffsetTime create(LocalTime localTime, ZoneOffset offset) { + return OffsetTime.of(localTime, offset); + } + + @ProtoField(number = 1, type = Type.MESSAGE) + LocalTime getLocalTime(OffsetTime offsetTime) { + return offsetTime.toLocalTime(); + } + + @ProtoField(number = 2, type = Type.MESSAGE) + ZoneOffset getOffset(OffsetTime offsetTime) { + return offsetTime.getOffset(); + } +} diff --git a/types/src/main/java/org/infinispan/protostream/types/java/time/PeriodAdapter.java b/types/src/main/java/org/infinispan/protostream/types/java/time/PeriodAdapter.java new file mode 100644 index 000000000..0b8cf1369 --- /dev/null +++ b/types/src/main/java/org/infinispan/protostream/types/java/time/PeriodAdapter.java @@ -0,0 +1,31 @@ +package org.infinispan.protostream.types.java.time; + +import java.time.Period; + +import org.infinispan.protostream.annotations.ProtoAdapter; +import org.infinispan.protostream.annotations.ProtoFactory; +import org.infinispan.protostream.annotations.ProtoField; +import org.infinispan.protostream.descriptors.Type; + +@ProtoAdapter(Period.class) +public class PeriodAdapter { + @ProtoFactory + Period create(Integer years, Integer months, Integer days) { + return Period.of(years, months, days); + } + + @ProtoField(number = 1, type = Type.INT32) + Integer getYears(Period period) { + return period.getYears(); + } + + @ProtoField(number = 2, type = Type.INT32) + Integer getMonths(Period period) { + return period.getMonths(); + } + + @ProtoField(number = 3, type = Type.INT32) + Integer getDays(Period period) { + return period.getDays(); + } +} diff --git a/types/src/main/java/org/infinispan/protostream/types/java/time/YearAdapter.java b/types/src/main/java/org/infinispan/protostream/types/java/time/YearAdapter.java new file mode 100644 index 000000000..984355a42 --- /dev/null +++ b/types/src/main/java/org/infinispan/protostream/types/java/time/YearAdapter.java @@ -0,0 +1,20 @@ +package org.infinispan.protostream.types.java.time; + +import java.time.Year; + +import org.infinispan.protostream.annotations.ProtoAdapter; +import org.infinispan.protostream.annotations.ProtoFactory; +import org.infinispan.protostream.annotations.ProtoField; +import org.infinispan.protostream.descriptors.Type; + +@ProtoAdapter(Year.class) +public class YearAdapter { + @ProtoFactory + Year create(Integer year) { + return Year.of(year); + } + @ProtoField(number = 1, type = Type.INT32) + Integer getYear(Year year) { + return year.getValue(); + } +} diff --git a/types/src/main/java/org/infinispan/protostream/types/java/time/ZoneIdAdapter.java b/types/src/main/java/org/infinispan/protostream/types/java/time/ZoneIdAdapter.java new file mode 100644 index 000000000..4cdcf38c3 --- /dev/null +++ b/types/src/main/java/org/infinispan/protostream/types/java/time/ZoneIdAdapter.java @@ -0,0 +1,23 @@ +package org.infinispan.protostream.types.java.time; + +import java.time.ZoneId; + +import org.infinispan.protostream.annotations.ProtoAdapter; +import org.infinispan.protostream.annotations.ProtoFactory; +import org.infinispan.protostream.annotations.ProtoField; +import org.infinispan.protostream.descriptors.Type; + +@ProtoAdapter(value = ZoneId.class, subClassNames = { + "java.time.ZoneRegion" +}) +public class ZoneIdAdapter { + @ProtoFactory + ZoneId create(String zoneId) { + return ZoneId.of(zoneId); + } + + @ProtoField(number = 1, type = Type.STRING) + String getZoneId(ZoneId zid) { + return zid.getId(); + } +} diff --git a/types/src/main/java/org/infinispan/protostream/types/java/time/ZoneOffsetAdapter.java b/types/src/main/java/org/infinispan/protostream/types/java/time/ZoneOffsetAdapter.java new file mode 100644 index 000000000..c9f69f3d2 --- /dev/null +++ b/types/src/main/java/org/infinispan/protostream/types/java/time/ZoneOffsetAdapter.java @@ -0,0 +1,20 @@ +package org.infinispan.protostream.types.java.time; + +import java.time.ZoneOffset; + +import org.infinispan.protostream.annotations.ProtoAdapter; +import org.infinispan.protostream.annotations.ProtoFactory; +import org.infinispan.protostream.annotations.ProtoField; +import org.infinispan.protostream.descriptors.Type; + +@ProtoAdapter(ZoneOffset.class) +public class ZoneOffsetAdapter { + @ProtoFactory + ZoneOffset create(String id) { + return ZoneOffset.of(id); + } + @ProtoField(number = 1, type = Type.STRING) + String getId(ZoneOffset offset) { + return offset.getId(); + } +} diff --git a/types/src/main/java/org/infinispan/protostream/types/java/time/ZonedDateTimeAdapter.java b/types/src/main/java/org/infinispan/protostream/types/java/time/ZonedDateTimeAdapter.java new file mode 100644 index 000000000..b27ff0854 --- /dev/null +++ b/types/src/main/java/org/infinispan/protostream/types/java/time/ZonedDateTimeAdapter.java @@ -0,0 +1,37 @@ +package org.infinispan.protostream.types.java.time; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +import org.infinispan.protostream.annotations.ProtoAdapter; +import org.infinispan.protostream.annotations.ProtoFactory; +import org.infinispan.protostream.annotations.ProtoField; +import org.infinispan.protostream.descriptors.Type; + +/** + * An adapter for {@link ZonedDateTime}. + */ +@ProtoAdapter(ZonedDateTime.class) +public final class ZonedDateTimeAdapter { + + @ProtoFactory + ZonedDateTime create(Long epochSecond, Integer nanoAdjustment, String zoneId) { + return ZonedDateTime.ofInstant(Instant.ofEpochSecond(epochSecond, nanoAdjustment), ZoneId.of(zoneId)); + } + + @ProtoField(number = 1, type = Type.UINT64) + Long getEpochSecond(ZonedDateTime zdt) { + return zdt.toInstant().getEpochSecond(); + } + + @ProtoField(number = 2, type = Type.UINT32) + Integer getNanoAdjustment(ZonedDateTime zdt) { + return zdt.toInstant().getNano(); + } + + @ProtoField(number = 3, type = Type.STRING) + String getZoneId(ZonedDateTime zdt) { + return zdt.getZone().getId(); + } +} diff --git a/types/src/test/java/org/infinispan/protostream/types/java/BookSchema.java b/types/src/test/java/org/infinispan/protostream/types/java/BookSchema.java index 3b54bb813..dfd4b2f04 100644 --- a/types/src/test/java/org/infinispan/protostream/types/java/BookSchema.java +++ b/types/src/test/java/org/infinispan/protostream/types/java/BookSchema.java @@ -1,9 +1,9 @@ package org.infinispan.protostream.types.java; import org.infinispan.protostream.GeneratedSchema; -import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder; +import org.infinispan.protostream.annotations.ProtoSchema; -@AutoProtoSchemaBuilder( +@ProtoSchema( includeClasses = { Book.class }, diff --git a/types/src/test/java/org/infinispan/protostream/types/java/TypesMarshallingTest.java b/types/src/test/java/org/infinispan/protostream/types/java/TypesMarshallingTest.java index 3de43c109..5936c3418 100644 --- a/types/src/test/java/org/infinispan/protostream/types/java/TypesMarshallingTest.java +++ b/types/src/test/java/org/infinispan/protostream/types/java/TypesMarshallingTest.java @@ -1,14 +1,8 @@ package org.infinispan.protostream.types.java; -import org.infinispan.protostream.GeneratedSchema; -import org.infinispan.protostream.ImmutableSerializationContext; -import org.infinispan.protostream.ProtobufUtil; -import org.infinispan.protostream.SerializationContext; -import org.infinispan.protostream.config.Configuration; -import org.infinispan.protostream.impl.Log; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assume.assumeTrue; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -17,6 +11,17 @@ import java.lang.invoke.MethodHandles; import java.math.BigDecimal; import java.math.BigInteger; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.time.MonthDay; +import java.time.OffsetTime; +import java.time.Period; +import java.time.Year; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; @@ -31,220 +36,301 @@ import java.util.function.Supplier; import java.util.stream.Stream; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assume.assumeTrue; +import org.infinispan.protostream.GeneratedSchema; +import org.infinispan.protostream.ImmutableSerializationContext; +import org.infinispan.protostream.ProtobufUtil; +import org.infinispan.protostream.SerializationContext; +import org.infinispan.protostream.config.Configuration; +import org.infinispan.protostream.impl.Log; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; @RunWith(Parameterized.class) public class TypesMarshallingTest { - private static final Log log = Log.LogFactory.getLog(MethodHandles.lookup().lookupClass()); - - private final TestConfiguration testConfiguration; - private final ImmutableSerializationContext context; - - public TypesMarshallingTest(TestConfiguration testConfiguration) { - this.testConfiguration = testConfiguration; - context = newContext(true); - } - - @Parameterized.Parameters - public static Object[][] marshallingMethods() { - return Arrays.stream(MarshallingMethodType.values()) - .flatMap(t -> switch (t) { - case BYTE_ARRAY, INPUT_STREAM, JSON -> Stream.of(new TestConfiguration(t, false, false, null)); - default -> Stream.of( - new TestConfiguration(t, true, true, null), - new TestConfiguration(t, true, false, ArrayList::new), - new TestConfiguration(t, true, false, HashSet::new), - new TestConfiguration(t, true, false, LinkedHashSet::new), - new TestConfiguration(t, true, false, LinkedList::new), - new TestConfiguration(t, true, false, TreeSet::new)); - }) - .map(t -> new Object[]{t}) - .toArray(Object[][]::new); - } - - @Test - public void testUUID() throws IOException { - testConfiguration.method.marshallAndUnmarshallTest(UUID.randomUUID(), context, false); - } - - @Test - public void testBitSet() throws IOException { - var bytes = new byte[ThreadLocalRandom.current().nextInt(64)]; - ThreadLocalRandom.current().nextBytes(bytes); - testConfiguration.method.marshallAndUnmarshallTest(BitSet.valueOf(bytes), context, false); - } - - @Test - public void testBigDecimal() throws IOException { - testConfiguration.method.marshallAndUnmarshallTest(BigDecimal.valueOf(ThreadLocalRandom.current().nextDouble(-256, 256)), context, false); - } - - @Test - public void testBigInteger() throws IOException { - testConfiguration.method.marshallAndUnmarshallTest(BigInteger.valueOf(ThreadLocalRandom.current().nextInt()), context, false); - } - - @Test - public void testContainerWithString() throws IOException { - assumeTrue(testConfiguration.runTest); - if (testConfiguration.isArray) { - testConfiguration.method.marshallAndUnmarshallTest(stringArray(), context, true); - } else { - testConfiguration.method.marshallAndUnmarshallTest(stringCollection(testConfiguration.collectionBuilder), context, false); - } - } - - @Test - public void testContainerWithBooks() throws IOException { - assumeTrue(testConfiguration.runTest); - if (testConfiguration.isArray) { - testConfiguration.method.marshallAndUnmarshallTest(bookArray(), context, true); - } else { - testConfiguration.method.marshallAndUnmarshallTest(bookCollection(testConfiguration.collectionBuilder), context, false); - } - } - - @Test - public void testPrimitiveCollectionCompatibility() throws IOException { - assumeTrue(testConfiguration.method == MarshallingMethodType.WRAPPED_MESSAGE); - var list = new ArrayList<>(List.of("a1", "a2", "a3")); - - // without wrapping enabled - var oldCtx = newContext(false); - - // send with oldCtx: simulates previous version - var data = ProtobufUtil.toWrappedByteArray(oldCtx, list, 512); - // read with newCtx: simulates current version - var listCopy = ProtobufUtil.fromWrappedByteArray(context, data); - - assertEquals(list, listCopy); - - // other way around - // send with newCtx: simulates current version - data = ProtobufUtil.toWrappedByteArray(context, list, 512); - // read with oldCtx: simulates previous version - listCopy = ProtobufUtil.fromWrappedByteArray(oldCtx, data); - - assertEquals(list, listCopy); - } - - @FunctionalInterface - public interface MarshallingMethod { - void marshallAndUnmarshallTest(Object original, ImmutableSerializationContext ctx, boolean isArray) throws IOException; - } - - public record TestConfiguration(MarshallingMethod method, boolean runTest, boolean isArray, - Supplier> collectionBuilder) { - - } - - private static ImmutableSerializationContext newContext(boolean wrapCollectionElements) { - var config = Configuration.builder().wrapCollectionElements(wrapCollectionElements).build(); - var ctx = ProtobufUtil.newSerializationContext(config); - register(new CommonTypesSchema(), ctx); - register(new CommonContainerTypesSchema(), ctx); - register(new BookSchemaImpl(), ctx); - return ctx; - } - - private static void register(GeneratedSchema schema, SerializationContext context) { - schema.registerMarshallers(context); - schema.registerSchema(context); - } - - private static Collection stringCollection(Supplier> supplier) { - var collection = supplier.get(); - collection.add("a"); - collection.add("b"); - collection.add("c"); - return collection; - } - - private static Collection bookCollection(Supplier> supplier) { - var collection = supplier.get(); - collection.add(new Book("Book1", "Description1", 2020)); - collection.add(new Book("Book2", "Description2", 2021)); - collection.add(new Book("Book3", "Description3", 2022)); - return collection; - } - - private static String[] stringArray() { - return new String[]{"a", "b", "c"}; - } - - private static Object[] bookArray() { - // cannot use new Book[] because there is no marshaller for it. - return new Object[]{ - new Book("Book1", "Description1", 2020), - new Book("Book2", "Description2", 2021), - new Book("Book3", "Description3", 2022) - }; - } - - enum MarshallingMethodType implements MarshallingMethod { - WRAPPED_MESSAGE { - @Override - public void marshallAndUnmarshallTest(Object original, ImmutableSerializationContext ctx, boolean isArray) throws IOException { - var bytes = ProtobufUtil.toWrappedByteArray(ctx, original, 512); - var copy = ProtobufUtil.fromWrappedByteArray(ctx, bytes); - log.debugf("Wrapped Message: bytes length=%s, original=%s, copy=%s", bytes.length, original, copy); - if (isArray) { - assertArrayEquals((Object[]) original, (Object[]) copy); - } else { - assertEquals(original, copy); - } + private static final Log log = Log.LogFactory.getLog(MethodHandles.lookup().lookupClass()); + + private final TestConfiguration testConfiguration; + private final ImmutableSerializationContext context; + + public TypesMarshallingTest(TestConfiguration testConfiguration) { + this.testConfiguration = testConfiguration; + context = newContext(true); + } + + @Override + public String toString() { + return "TypesMarshallingTest{" + + "testConfiguration=" + testConfiguration + + ", context=" + context + + '}'; + } + + @Parameterized.Parameters + public static Object[][] marshallingMethods() { + return Arrays.stream(MarshallingMethodType.values()) + .flatMap(t -> switch (t) { + case BYTE_ARRAY, INPUT_STREAM, JSON -> Stream.of(new TestConfiguration(t, false, false, null)); + default -> Stream.of( + new TestConfiguration(t, true, true, null), + new TestConfiguration(t, true, false, ArrayList::new), + new TestConfiguration(t, true, false, HashSet::new), + new TestConfiguration(t, true, false, LinkedHashSet::new), + new TestConfiguration(t, true, false, LinkedList::new), + new TestConfiguration(t, true, false, TreeSet::new)); + }) + .map(t -> new Object[]{t}) + .toArray(Object[][]::new); + } + + @Test + public void testUUID() throws IOException { + testConfiguration.method.marshallAndUnmarshallTest(UUID.randomUUID(), context, false); + } + + @Test + public void testBitSet() throws IOException { + var bytes = new byte[ThreadLocalRandom.current().nextInt(64)]; + ThreadLocalRandom.current().nextBytes(bytes); + testConfiguration.method.marshallAndUnmarshallTest(BitSet.valueOf(bytes), context, false); + } + + @Test + public void testBigDecimal() throws IOException { + testConfiguration.method.marshallAndUnmarshallTest(BigDecimal.valueOf(ThreadLocalRandom.current().nextDouble(-256, 256)), context, false); + } + + @Test + public void testBigInteger() throws IOException { + testConfiguration.method.marshallAndUnmarshallTest(BigInteger.valueOf(ThreadLocalRandom.current().nextInt()), context, false); + } + + @Test + public void testContainerWithString() throws IOException { + assumeTrue(testConfiguration.runTest); + if (testConfiguration.isArray) { + testConfiguration.method.marshallAndUnmarshallTest(stringArray(), context, true); + } else { + testConfiguration.method.marshallAndUnmarshallTest(stringCollection(testConfiguration.collectionBuilder), context, false); + } + } + + @Test + public void testContainerWithBooks() throws IOException { + assumeTrue(testConfiguration.runTest); + if (testConfiguration.isArray) { + testConfiguration.method.marshallAndUnmarshallTest(bookArray(), context, true); + } else { + testConfiguration.method.marshallAndUnmarshallTest(bookCollection(testConfiguration.collectionBuilder), context, false); + } + } + + @Test + public void testPrimitiveCollectionCompatibility() throws IOException { + assumeTrue(testConfiguration.method == MarshallingMethodType.WRAPPED_MESSAGE); + var list = new ArrayList<>(List.of("a1", "a2", "a3")); + + // without wrapping enabled + var oldCtx = newContext(false); + + // send with oldCtx: simulates previous version + var data = ProtobufUtil.toWrappedByteArray(oldCtx, list, 512); + // read with newCtx: simulates current version + var listCopy = ProtobufUtil.fromWrappedByteArray(context, data); + + assertEquals(list, listCopy); + + // other way around + // send with newCtx: simulates current version + data = ProtobufUtil.toWrappedByteArray(context, list, 512); + // read with oldCtx: simulates previous version + listCopy = ProtobufUtil.fromWrappedByteArray(oldCtx, data); + + assertEquals(list, listCopy); + } + + @Test + public void testLocalDate() throws IOException { + LocalDate date = LocalDate.of(1985, 10, 26); + testConfiguration.method.marshallAndUnmarshallTest(date, context, false); + } + + @Test + public void testLocalDateTime() throws IOException { + LocalDateTime dateTime = LocalDateTime.of(1985, 10, 26, 0, 59, 0, 0); + testConfiguration.method.marshallAndUnmarshallTest(dateTime, context, false); + } + + @Test + public void testLocalTime() throws IOException { + LocalTime time = LocalTime.of(23, 59, 59, 59); + testConfiguration.method.marshallAndUnmarshallTest(time, context, false); + } + + @Test + public void testMonth() throws IOException { + assumeTrue(testConfiguration.method == MarshallingMethodType.WRAPPED_MESSAGE || testConfiguration.method == MarshallingMethodType.JSON); + testConfiguration.method.marshallAndUnmarshallTest(Month.OCTOBER, context, false); + } + + @Test + public void testMonthDay() throws IOException { + MonthDay monthDay = MonthDay.of(10, 26); + testConfiguration.method.marshallAndUnmarshallTest(monthDay, context, false); + } + + @Test + public void testOffsetTime() throws IOException { + OffsetTime offsetTime = OffsetTime.of(23, 59, 59, 10, ZoneOffset.UTC); + testConfiguration.method.marshallAndUnmarshallTest(offsetTime, context, false); + } + + @Test + public void testPeriod() throws IOException { + Period period = Period.of(10, 4, 3); + testConfiguration.method.marshallAndUnmarshallTest(period, context, false); + } + + @Test + public void testYear() throws IOException { + Year year = Year.of(1985); + testConfiguration.method.marshallAndUnmarshallTest(year, context, false); + } + + @Test + public void testZoneId() throws IOException { + ZoneId zid = ZoneId.systemDefault(); + testConfiguration.method.marshallAndUnmarshallTest(zid, context, false); + } + + @Test + public void testOffset() throws IOException { + ZoneOffset offset = ZoneOffset.of("+07:00"); + testConfiguration.method.marshallAndUnmarshallTest(offset, context, false); + } + + + @Test + public void testZonedTime() throws IOException { + ZonedDateTime time = ZonedDateTime.of(1985, 10, 26, 0, 59, 0, 0, ZoneId.of("+07:00")); + testConfiguration.method.marshallAndUnmarshallTest(time, context, false); + } + + @FunctionalInterface + public interface MarshallingMethod { + void marshallAndUnmarshallTest(Object original, ImmutableSerializationContext ctx, boolean isArray) throws IOException; + } + + public record TestConfiguration(MarshallingMethod method, boolean runTest, boolean isArray, + Supplier> collectionBuilder) { + + } + + private static ImmutableSerializationContext newContext(boolean wrapCollectionElements) { + var config = Configuration.builder().wrapCollectionElements(wrapCollectionElements).build(); + var ctx = ProtobufUtil.newSerializationContext(config); + register(new CommonTypesSchema(), ctx); + register(new CommonContainerTypesSchema(), ctx); + register(new BookSchemaImpl(), ctx); + return ctx; + } + + private static void register(GeneratedSchema schema, SerializationContext context) { + schema.registerSchema(context); + schema.registerMarshallers(context); + } + + private static Collection stringCollection(Supplier> supplier) { + var collection = supplier.get(); + collection.add("a"); + collection.add("b"); + collection.add("c"); + return collection; + } + + private static Collection bookCollection(Supplier> supplier) { + var collection = supplier.get(); + collection.add(new Book("Book1", "Description1", 2020)); + collection.add(new Book("Book2", "Description2", 2021)); + collection.add(new Book("Book3", "Description3", 2022)); + return collection; + } + + private static String[] stringArray() { + return new String[]{"a", "b", "c"}; + } + + private static Object[] bookArray() { + // cannot use new Book[] because there is no marshaller for it. + return new Object[]{ + new Book("Book1", "Description1", 2020), + new Book("Book2", "Description2", 2021), + new Book("Book3", "Description3", 2022) + }; + } + + enum MarshallingMethodType implements MarshallingMethod { + WRAPPED_MESSAGE { + @Override + public void marshallAndUnmarshallTest(Object original, ImmutableSerializationContext ctx, boolean isArray) throws IOException { + var bytes = ProtobufUtil.toWrappedByteArray(ctx, original, 512); + var copy = ProtobufUtil.fromWrappedByteArray(ctx, bytes); + log.debugf("Wrapped Message: bytes length=%s, original=%s, copy=%s", bytes.length, original, copy); + if (isArray) { + assertArrayEquals((Object[]) original, (Object[]) copy); + } else { + assertEquals(original, copy); } - }, - INPUT_STREAM { - @Override - public void marshallAndUnmarshallTest(Object original, ImmutableSerializationContext ctx, boolean isArray) throws IOException { - var baos = new ByteArrayOutputStream(512); - ProtobufUtil.writeTo(ctx, baos, original); - var bais = new ByteArrayInputStream(baos.toByteArray()); - var copy = ProtobufUtil.readFrom(ctx, bais, original.getClass()); - log.debugf("Input Stream: bytes length=%s, original=%s, copy=%s", baos.size(), original, copy); - if (isArray) { - assertArrayEquals((Object[]) original, (Object[]) copy); - } else { - assertEquals(original, copy); - } + } + }, + INPUT_STREAM { + @Override + public void marshallAndUnmarshallTest(Object original, ImmutableSerializationContext ctx, boolean isArray) throws IOException { + var baos = new ByteArrayOutputStream(512); + ProtobufUtil.writeTo(ctx, baos, original); + var bais = new ByteArrayInputStream(baos.toByteArray()); + var copy = ProtobufUtil.readFrom(ctx, bais, original.getClass()); + log.debugf("Input Stream: bytes length=%s, original=%s, copy=%s", baos.size(), original, copy); + if (isArray) { + assertArrayEquals((Object[]) original, (Object[]) copy); + } else { + assertEquals(original, copy); } - }, - BYTE_ARRAY { - @Override - public void marshallAndUnmarshallTest(Object original, ImmutableSerializationContext ctx, boolean isArray) throws IOException { - var baos = new ByteArrayOutputStream(512); - ProtobufUtil.writeTo(ctx, baos, original); - var copy = ProtobufUtil.fromByteArray(ctx, baos.toByteArray(), original.getClass()); - log.debugf("Byte Array: bytes length=%s, original=%s, copy=%s", baos.size(), original, copy); - if (isArray) { - assertArrayEquals((Object[]) original, (Object[]) copy); - } else { - assertEquals(original, copy); - } + } + }, + BYTE_ARRAY { + @Override + public void marshallAndUnmarshallTest(Object original, ImmutableSerializationContext ctx, boolean isArray) throws IOException { + var baos = new ByteArrayOutputStream(512); + ProtobufUtil.writeTo(ctx, baos, original); + var copy = ProtobufUtil.fromByteArray(ctx, baos.toByteArray(), original.getClass()); + log.debugf("Byte Array: bytes length=%s, original=%s, copy=%s", baos.size(), original, copy); + if (isArray) { + assertArrayEquals((Object[]) original, (Object[]) copy); + } else { + assertEquals(original, copy); } - }, - JSON { - @Override - public void marshallAndUnmarshallTest(Object original, ImmutableSerializationContext ctx, boolean isArray) throws IOException { - var bytes = ProtobufUtil.toWrappedByteArray(ctx, original, 512); - - var json = ProtobufUtil.toCanonicalJSON(ctx, bytes); - var jsonBytes = ProtobufUtil.fromCanonicalJSON(ctx, new StringReader(json)); - - var copy = ProtobufUtil.fromWrappedByteArray(ctx, jsonBytes); - - log.debugf("JSON: JSON bytes length=%s, JSON String=%s, original=%s, copy=%s", jsonBytes.length, json, original, copy); - if (isArray) { - assertArrayEquals((Object[]) original, (Object[]) copy); - } else { - assertEquals(original, copy); - } + } + }, + JSON { + @Override + public void marshallAndUnmarshallTest(Object original, ImmutableSerializationContext ctx, boolean isArray) throws IOException { + var bytes = ProtobufUtil.toWrappedByteArray(ctx, original, 512); + + var json = ProtobufUtil.toCanonicalJSON(ctx, bytes); + var jsonBytes = ProtobufUtil.fromCanonicalJSON(ctx, new StringReader(json)); + + var copy = ProtobufUtil.fromWrappedByteArray(ctx, jsonBytes); + + log.debugf("JSON: JSON bytes length=%s, JSON String=%s, original=%s, copy=%s", jsonBytes.length, json, original, copy); + if (isArray) { + assertArrayEquals((Object[]) original, (Object[]) copy); + } else { + assertEquals(original, copy); } - } - } + } + } + } }