From 5a3ee720ba466a778083f6055f1033a7d35bc18a Mon Sep 17 00:00:00 2001 From: rriggs Date: Wed, 11 Sep 2013 10:16:21 -0400 Subject: [PATCH] 8024164: JSR310 serialization should be described in details Summary: The serialized-form.html should specify the stream format for interoperability Reviewed-by: alanb --- src/share/classes/java/time/Duration.java | 7 +- src/share/classes/java/time/Instant.java | 7 +- src/share/classes/java/time/LocalDate.java | 7 +- .../classes/java/time/LocalDateTime.java | 7 +- src/share/classes/java/time/LocalTime.java | 9 ++- src/share/classes/java/time/MonthDay.java | 9 +-- .../classes/java/time/OffsetDateTime.java | 9 +-- src/share/classes/java/time/OffsetTime.java | 9 +-- src/share/classes/java/time/Period.java | 9 +-- src/share/classes/java/time/Ser.java | 52 ++++++++++++-- src/share/classes/java/time/Year.java | 9 +-- src/share/classes/java/time/YearMonth.java | 9 +-- src/share/classes/java/time/ZoneId.java | 15 +++- src/share/classes/java/time/ZoneOffset.java | 9 +-- src/share/classes/java/time/ZoneRegion.java | 7 +- .../classes/java/time/ZonedDateTime.java | 9 +-- .../time/chrono/ChronoLocalDateTimeImpl.java | 20 ++++-- .../time/chrono/ChronoZonedDateTimeImpl.java | 25 +++++-- .../classes/java/time/chrono/Chronology.java | 19 ++++- .../java/time/chrono/HijrahChronology.java | 36 ++++++++-- .../classes/java/time/chrono/HijrahDate.java | 39 ++++++---- .../classes/java/time/chrono/HijrahEra.java | 17 ----- .../java/time/chrono/IsoChronology.java | 27 +++++++ .../java/time/chrono/JapaneseChronology.java | 27 +++++++ .../java/time/chrono/JapaneseDate.java | 25 ++++++- .../classes/java/time/chrono/JapaneseEra.java | 13 +++- .../java/time/chrono/MinguoChronology.java | 27 +++++++ .../classes/java/time/chrono/MinguoDate.java | 25 ++++++- .../classes/java/time/chrono/MinguoEra.java | 17 ----- src/share/classes/java/time/chrono/Ser.java | 67 +++++++++++------ .../time/chrono/ThaiBuddhistChronology.java | 27 +++++++ .../java/time/chrono/ThaiBuddhistDate.java | 25 ++++++- .../java/time/chrono/ThaiBuddhistEra.java | 17 ----- src/share/classes/java/time/zone/Ser.java | 25 +++++++ .../java/time/zone/ZoneOffsetTransition.java | 24 ++++++- .../time/zone/ZoneOffsetTransitionRule.java | 52 +++++++++++++- .../classes/java/time/zone/ZoneRules.java | 71 ++++++++++++++++++- .../chrono/TCKChronologySerialization.java | 4 +- 38 files changed, 646 insertions(+), 166 deletions(-) diff --git a/src/share/classes/java/time/Duration.java b/src/share/classes/java/time/Duration.java index ab7da66f8..ce2ba7781 100644 --- a/src/share/classes/java/time/Duration.java +++ b/src/share/classes/java/time/Duration.java @@ -74,7 +74,7 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.io.InvalidObjectException; -import java.io.ObjectStreamException; +import java.io.InvalidObjectException; import java.io.Serializable; import java.math.BigDecimal; import java.math.BigInteger; @@ -1299,8 +1299,9 @@ public final class Duration /** * Writes the object using a * dedicated serialized form. + * @serialData *
-     *  out.writeByte(1);  // identifies this as a Duration
+     *  out.writeByte(1);  // identifies a Duration
      *  out.writeLong(seconds);
      *  out.writeInt(nanos);
      * 
@@ -1316,7 +1317,7 @@ public final class Duration * @return never * @throws InvalidObjectException always */ - private Object readResolve() throws ObjectStreamException { + private Object readResolve() throws InvalidObjectException { throw new InvalidObjectException("Deserialization via serialization delegate"); } diff --git a/src/share/classes/java/time/Instant.java b/src/share/classes/java/time/Instant.java index aeecdbed5..9d74e29f9 100644 --- a/src/share/classes/java/time/Instant.java +++ b/src/share/classes/java/time/Instant.java @@ -76,7 +76,7 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.io.InvalidObjectException; -import java.io.ObjectStreamException; +import java.io.InvalidObjectException; import java.io.Serializable; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; @@ -1317,8 +1317,9 @@ public final class Instant /** * Writes the object using a * dedicated serialized form. + * @serialData *
-     *  out.writeByte(2);  // identifies this as an Instant
+     *  out.writeByte(2);  // identifies an Instant
      *  out.writeLong(seconds);
      *  out.writeInt(nanos);
      * 
@@ -1334,7 +1335,7 @@ public final class Instant * @return never * @throws InvalidObjectException always */ - private Object readResolve() throws ObjectStreamException { + private Object readResolve() throws InvalidObjectException { throw new InvalidObjectException("Deserialization via serialization delegate"); } diff --git a/src/share/classes/java/time/LocalDate.java b/src/share/classes/java/time/LocalDate.java index d96f5b1fd..300565836 100644 --- a/src/share/classes/java/time/LocalDate.java +++ b/src/share/classes/java/time/LocalDate.java @@ -78,7 +78,7 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.io.InvalidObjectException; -import java.io.ObjectStreamException; +import java.io.InvalidObjectException; import java.io.Serializable; import java.time.chrono.ChronoLocalDate; import java.time.chrono.Era; @@ -2019,8 +2019,9 @@ public final class LocalDate /** * Writes the object using a * dedicated serialized form. + * @serialData *
-     *  out.writeByte(3);  // identifies this as a LocalDate
+     *  out.writeByte(3);  // identifies a LocalDate
      *  out.writeInt(year);
      *  out.writeByte(month);
      *  out.writeByte(day);
@@ -2037,7 +2038,7 @@ public final class LocalDate
      * @return never
      * @throws InvalidObjectException always
      */
-    private Object readResolve() throws ObjectStreamException {
+    private Object readResolve() throws InvalidObjectException {
         throw new InvalidObjectException("Deserialization via serialization delegate");
     }
 
diff --git a/src/share/classes/java/time/LocalDateTime.java b/src/share/classes/java/time/LocalDateTime.java
index d68d6f525..de8b246d6 100644
--- a/src/share/classes/java/time/LocalDateTime.java
+++ b/src/share/classes/java/time/LocalDateTime.java
@@ -76,7 +76,7 @@ import java.io.DataInput;
 import java.io.DataOutput;
 import java.io.IOException;
 import java.io.InvalidObjectException;
-import java.io.ObjectStreamException;
+import java.io.InvalidObjectException;
 import java.io.Serializable;
 import java.time.chrono.ChronoLocalDateTime;
 import java.time.format.DateTimeFormatter;
@@ -1953,8 +1953,9 @@ public final class LocalDateTime
     /**
      * Writes the object using a
      * dedicated serialized form.
+     * @serialData
      * 
-     *  out.writeByte(5);  // identifies this as a LocalDateTime
+     *  out.writeByte(5);  // identifies a LocalDateTime
      *  // the date excluding the one byte header
      *  // the time excluding the one byte header
      * 
@@ -1970,7 +1971,7 @@ public final class LocalDateTime * @return never * @throws InvalidObjectException always */ - private Object readResolve() throws ObjectStreamException { + private Object readResolve() throws InvalidObjectException { throw new InvalidObjectException("Deserialization via serialization delegate"); } diff --git a/src/share/classes/java/time/LocalTime.java b/src/share/classes/java/time/LocalTime.java index 2bace6e7b..a6270db19 100644 --- a/src/share/classes/java/time/LocalTime.java +++ b/src/share/classes/java/time/LocalTime.java @@ -74,7 +74,7 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.io.InvalidObjectException; -import java.io.ObjectStreamException; +import java.io.InvalidObjectException; import java.io.Serializable; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; @@ -1595,8 +1595,11 @@ public final class LocalTime /** * Writes the object using a * dedicated serialized form. + * @serialData + * A twos-complement value indicates the remaining values are not in the stream + * and should be set to zero. *
-     *  out.writeByte(4);  // identifies this as a LocalTime
+     *  out.writeByte(4);  // identifies a LocalTime
      *  if (nano == 0) {
      *    if (second == 0) {
      *      if (minute == 0) {
@@ -1629,7 +1632,7 @@ public final class LocalTime
      * @return never
      * @throws InvalidObjectException always
      */
-    private Object readResolve() throws ObjectStreamException {
+    private Object readResolve() throws InvalidObjectException {
         throw new InvalidObjectException("Deserialization via serialization delegate");
     }
 
diff --git a/src/share/classes/java/time/MonthDay.java b/src/share/classes/java/time/MonthDay.java
index 06aa0437a..22807822f 100644
--- a/src/share/classes/java/time/MonthDay.java
+++ b/src/share/classes/java/time/MonthDay.java
@@ -68,7 +68,7 @@ import java.io.DataInput;
 import java.io.DataOutput;
 import java.io.IOException;
 import java.io.InvalidObjectException;
-import java.io.ObjectStreamException;
+import java.io.InvalidObjectException;
 import java.io.Serializable;
 import java.time.chrono.Chronology;
 import java.time.chrono.IsoChronology;
@@ -744,9 +744,10 @@ public final class MonthDay
     //-----------------------------------------------------------------------
     /**
      * Writes the object using a
-     * dedicated serialized form.
+     * dedicated serialized form.
+     * @serialData
      * 
-     *  out.writeByte(13);  // identifies this as a MonthDay
+     *  out.writeByte(13);  // identifies a MonthDay
      *  out.writeByte(month);
      *  out.writeByte(day);
      * 
@@ -762,7 +763,7 @@ public final class MonthDay * @return never * @throws InvalidObjectException always */ - private Object readResolve() throws ObjectStreamException { + private Object readResolve() throws InvalidObjectException { throw new InvalidObjectException("Deserialization via serialization delegate"); } diff --git a/src/share/classes/java/time/OffsetDateTime.java b/src/share/classes/java/time/OffsetDateTime.java index 5641154cf..f894e53e4 100644 --- a/src/share/classes/java/time/OffsetDateTime.java +++ b/src/share/classes/java/time/OffsetDateTime.java @@ -72,7 +72,7 @@ import java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectInput; import java.io.ObjectOutput; -import java.io.ObjectStreamException; +import java.io.InvalidObjectException; import java.io.Serializable; import java.time.chrono.IsoChronology; import java.time.format.DateTimeFormatter; @@ -1901,9 +1901,10 @@ public final class OffsetDateTime //----------------------------------------------------------------------- /** * Writes the object using a - * dedicated serialized form. + * dedicated serialized form. + * @serialData *
-     *  out.writeByte(10);  // identifies this as a OffsetDateTime
+     *  out.writeByte(10);  // identifies a OffsetDateTime
      *  out.writeObject(dateTime);
      *  out.writeObject(offset);
      * 
@@ -1919,7 +1920,7 @@ public final class OffsetDateTime * @return never * @throws InvalidObjectException always */ - private Object readResolve() throws ObjectStreamException { + private Object readResolve() throws InvalidObjectException { throw new InvalidObjectException("Deserialization via serialization delegate"); } diff --git a/src/share/classes/java/time/OffsetTime.java b/src/share/classes/java/time/OffsetTime.java index 2872cff4b..6c67ef82b 100644 --- a/src/share/classes/java/time/OffsetTime.java +++ b/src/share/classes/java/time/OffsetTime.java @@ -73,7 +73,7 @@ import java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectInput; import java.io.ObjectOutput; -import java.io.ObjectStreamException; +import java.io.InvalidObjectException; import java.io.Serializable; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; @@ -1372,9 +1372,10 @@ public final class OffsetTime //----------------------------------------------------------------------- /** * Writes the object using a - * dedicated serialized form. + * dedicated serialized form. + * @serialData *
-     *  out.writeByte(9);  // identifies this as a OffsetTime
+     *  out.writeByte(9);  // identifies a OffsetTime
      *  out.writeObject(time);
      *  out.writeObject(offset);
      * 
@@ -1390,7 +1391,7 @@ public final class OffsetTime * @return never * @throws InvalidObjectException always */ - private Object readResolve() throws ObjectStreamException { + private Object readResolve() throws InvalidObjectException { throw new InvalidObjectException("Deserialization via serialization delegate"); } diff --git a/src/share/classes/java/time/Period.java b/src/share/classes/java/time/Period.java index 45980d087..161ce49e5 100644 --- a/src/share/classes/java/time/Period.java +++ b/src/share/classes/java/time/Period.java @@ -70,7 +70,7 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.io.InvalidObjectException; -import java.io.ObjectStreamException; +import java.io.InvalidObjectException; import java.io.Serializable; import java.time.chrono.ChronoLocalDate; import java.time.chrono.Chronology; @@ -993,11 +993,12 @@ public final class Period /** * Writes the object using a * dedicated serialized form. + * @serialData *
-     *  out.writeByte(14);  // identifies this as a Period
+     *  out.writeByte(14);  // identifies a Period
      *  out.writeInt(years);
      *  out.writeInt(months);
-     *  out.writeInt(seconds);
+     *  out.writeInt(days);
      * 
* * @return the instance of {@code Ser}, not null @@ -1011,7 +1012,7 @@ public final class Period * @return never * @throws java.io.InvalidObjectException always */ - private Object readResolve() throws ObjectStreamException { + private Object readResolve() throws InvalidObjectException { throw new InvalidObjectException("Deserialization via serialization delegate"); } diff --git a/src/share/classes/java/time/Ser.java b/src/share/classes/java/time/Ser.java index 607198952..885b43abd 100644 --- a/src/share/classes/java/time/Ser.java +++ b/src/share/classes/java/time/Ser.java @@ -72,14 +72,14 @@ import java.io.StreamCorruptedException; * byte flag would be used in order to specify an alternative version of the type format. * For example {@code LOCAL_DATE_TYPE_VERSION_2 = 21}. *

- * In order to serialise the object it writes its byte and then calls back to the appropriate class where - * the serialisation is performed. In order to deserialise the object it read in the type byte, switching + * In order to serialize the object it writes its byte and then calls back to the appropriate class where + * the serialization is performed. In order to deserialize the object it read in the type byte, switching * in order to select which class to call back into. *

- * The serialisation format is determined on a per class basis. In the case of field based classes each + * The serialization format is determined on a per class basis. In the case of field based classes each * of the fields is written out with an appropriate size format in descending order of the field's size. For * example in the case of {@link LocalDate} year is written before month. Composite classes, such as - * {@link LocalDateTime} are serialised as one object. + * {@link LocalDateTime} are serialized as one object. *

* This class is mutable and should be created once per serialization. * @@ -133,6 +133,27 @@ final class Ser implements Externalizable { //----------------------------------------------------------------------- /** * Implements the {@code Externalizable} interface to write the object. + * @serialData + * + * Each serializable class is mapped to a type that is the first byte + * in the stream. Refer to each class {@code writeReplace} + * serialized form for the value of the type and sequence of values for the type. + *

* * @param out the data stream to write to, not null */ @@ -194,6 +215,29 @@ final class Ser implements Externalizable { //----------------------------------------------------------------------- /** * Implements the {@code Externalizable} interface to read the object. + * @serialData + * + * The streamed type and parameters defined by the type's {@code writeReplace} + * method are read and passed to the corresponding static factory for the type + * to create a new instance. That instance is returned as the de-serialized + * {@code Ser} object. + * + *
    + *
  • Duration - {@code Duration.ofSeconds(seconds, nanos);} + *
  • Instant - {@code Instant.ofEpochSecond(seconds, nanos);} + *
  • LocalDate - {@code LocalDate.of(year, month, day);} + *
  • LocalDateTime - {@code LocalDateTime.of(date, time);} + *
  • LocalTime - {@code LocalTime.of(hour, minute, second, nano);} + *
  • MonthDay - {@code MonthDay.of(month, day);} + *
  • OffsetTime - {@code OffsetTime.of(time, offset);} + *
  • OffsetDateTime - {@code OffsetDateTime.of(dateTime, offset);} + *
  • Period - {@code Period.of(years, months, days);} + *
  • Year - {@code Year.of(year);} + *
  • YearMonth - {@code YearMonth.of(year, month);} + *
  • ZonedDateTime - {@code ZonedDateTime.ofLenient(dateTime, offset, zone);} + *
  • ZoneId - {@code ZoneId.of(id);} + *
  • ZoneOffset - {@code (offsetByte == 127 ? ZoneOffset.ofTotalSeconds(in.readInt()) : ZoneOffset.ofTotalSeconds(offsetByte * 900));} + *
* * @param in the data to read, not null */ diff --git a/src/share/classes/java/time/Year.java b/src/share/classes/java/time/Year.java index fb180d859..f51bda696 100644 --- a/src/share/classes/java/time/Year.java +++ b/src/share/classes/java/time/Year.java @@ -74,7 +74,7 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.io.InvalidObjectException; -import java.io.ObjectStreamException; +import java.io.InvalidObjectException; import java.io.Serializable; import java.time.chrono.Chronology; import java.time.chrono.IsoChronology; @@ -1080,9 +1080,10 @@ public final class Year //----------------------------------------------------------------------- /** * Writes the object using a - * dedicated serialized form. + * dedicated serialized form. + * @serialData *
-     *  out.writeByte(11);  // identifies this as a Year
+     *  out.writeByte(11);  // identifies a Year
      *  out.writeInt(year);
      * 
* @@ -1097,7 +1098,7 @@ public final class Year * @return never * @throws InvalidObjectException always */ - private Object readResolve() throws ObjectStreamException { + private Object readResolve() throws InvalidObjectException { throw new InvalidObjectException("Deserialization via serialization delegate"); } diff --git a/src/share/classes/java/time/YearMonth.java b/src/share/classes/java/time/YearMonth.java index 1d9740953..541676117 100644 --- a/src/share/classes/java/time/YearMonth.java +++ b/src/share/classes/java/time/YearMonth.java @@ -77,7 +77,7 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.io.InvalidObjectException; -import java.io.ObjectStreamException; +import java.io.InvalidObjectException; import java.io.Serializable; import java.time.chrono.Chronology; import java.time.chrono.IsoChronology; @@ -1205,9 +1205,10 @@ public final class YearMonth //----------------------------------------------------------------------- /** * Writes the object using a - * dedicated serialized form. + * dedicated serialized form. + * @serialData *
-     *  out.writeByte(12);  // identifies this as a YearMonth
+     *  out.writeByte(12);  // identifies a YearMonth
      *  out.writeInt(year);
      *  out.writeByte(month);
      * 
@@ -1223,7 +1224,7 @@ public final class YearMonth * @return never * @throws InvalidObjectException always */ - private Object readResolve() throws ObjectStreamException { + private Object readResolve() throws InvalidObjectException { throw new InvalidObjectException("Deserialization via serialization delegate"); } diff --git a/src/share/classes/java/time/ZoneId.java b/src/share/classes/java/time/ZoneId.java index dcde85ae8..86b0c74fd 100644 --- a/src/share/classes/java/time/ZoneId.java +++ b/src/share/classes/java/time/ZoneId.java @@ -63,6 +63,7 @@ package java.time; import java.io.DataOutput; import java.io.IOException; +import java.io.InvalidObjectException; import java.io.Serializable; import java.time.format.DateTimeFormatterBuilder; import java.time.format.TextStyle; @@ -661,6 +662,15 @@ public abstract class ZoneId implements Serializable { } //----------------------------------------------------------------------- + /** + * Defend against malicious streams. + * @return never + * @throws InvalidObjectException always + */ + private Object readResolve() throws InvalidObjectException { + throw new InvalidObjectException("Deserialization via serialization delegate"); + } + /** * Outputs this zone as a {@code String}, using the ID. * @@ -675,9 +685,10 @@ public abstract class ZoneId implements Serializable { /** * Writes the object using a * dedicated serialized form. + * @serialData *
-     *  out.writeByte(7);  // identifies this as a ZoneId (not ZoneOffset)
-     *  out.writeUTF(zoneId);
+     *  out.writeByte(7);  // identifies a ZoneId (not ZoneOffset)
+     *  out.writeUTF(getId());
      * 
*

* When read back in, the {@code ZoneId} will be created as though using diff --git a/src/share/classes/java/time/ZoneOffset.java b/src/share/classes/java/time/ZoneOffset.java index c5e4d056e..2d63a978d 100644 --- a/src/share/classes/java/time/ZoneOffset.java +++ b/src/share/classes/java/time/ZoneOffset.java @@ -70,7 +70,7 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.io.InvalidObjectException; -import java.io.ObjectStreamException; +import java.io.InvalidObjectException; import java.io.Serializable; import java.time.temporal.ChronoField; import java.time.temporal.Temporal; @@ -740,12 +740,13 @@ public final class ZoneOffset /** * Writes the object using a * dedicated serialized form. + * @serialData *

-     *  out.writeByte(8);  // identifies this as a ZoneOffset
+     *  out.writeByte(8);                  // identifies a ZoneOffset
      *  int offsetByte = totalSeconds % 900 == 0 ? totalSeconds / 900 : 127;
      *  out.writeByte(offsetByte);
      *  if (offsetByte == 127) {
-     *    out.writeInt(totalSeconds);
+     *      out.writeInt(totalSeconds);
      *  }
      * 
* @@ -760,7 +761,7 @@ public final class ZoneOffset * @return never * @throws InvalidObjectException always */ - private Object readResolve() throws ObjectStreamException { + private Object readResolve() throws InvalidObjectException { throw new InvalidObjectException("Deserialization via serialization delegate"); } diff --git a/src/share/classes/java/time/ZoneRegion.java b/src/share/classes/java/time/ZoneRegion.java index 66d30709d..31669d79c 100644 --- a/src/share/classes/java/time/ZoneRegion.java +++ b/src/share/classes/java/time/ZoneRegion.java @@ -60,7 +60,7 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.io.InvalidObjectException; -import java.io.ObjectStreamException; +import java.io.InvalidObjectException; import java.io.Serializable; import java.time.zone.ZoneRules; import java.time.zone.ZoneRulesException; @@ -181,8 +181,9 @@ final class ZoneRegion extends ZoneId implements Serializable { /** * Writes the object using a * dedicated serialized form. + * @serialData *
-     *  out.writeByte(7);  // identifies this as a ZoneId (not ZoneOffset)
+     *  out.writeByte(7);  // identifies a ZoneId (not ZoneOffset)
      *  out.writeUTF(zoneId);
      * 
* @@ -197,7 +198,7 @@ final class ZoneRegion extends ZoneId implements Serializable { * @return never * @throws InvalidObjectException always */ - private Object readResolve() throws ObjectStreamException { + private Object readResolve() throws InvalidObjectException { throw new InvalidObjectException("Deserialization via serialization delegate"); } diff --git a/src/share/classes/java/time/ZonedDateTime.java b/src/share/classes/java/time/ZonedDateTime.java index 151470ecf..251ca888a 100644 --- a/src/share/classes/java/time/ZonedDateTime.java +++ b/src/share/classes/java/time/ZonedDateTime.java @@ -69,7 +69,7 @@ import java.io.DataOutput; import java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectInput; -import java.io.ObjectStreamException; +import java.io.InvalidObjectException; import java.io.Serializable; import java.time.chrono.ChronoZonedDateTime; import java.time.format.DateTimeFormatter; @@ -2192,9 +2192,10 @@ public final class ZonedDateTime /** * Writes the object using a * dedicated serialized form. + * @serialData *
-     *  out.writeByte(6);  // identifies this as a ZonedDateTime
-     *  // the date-time excluding the one byte header
+     *  out.writeByte(6);  // identifies a ZonedDateTime
+     *  // the dateTime excluding the one byte header
      *  // the offset excluding the one byte header
      *  // the zone ID excluding the one byte header
      * 
@@ -2210,7 +2211,7 @@ public final class ZonedDateTime * @return never * @throws InvalidObjectException always */ - private Object readResolve() throws ObjectStreamException { + private Object readResolve() throws InvalidObjectException { throw new InvalidObjectException("Deserialization via serialization delegate"); } diff --git a/src/share/classes/java/time/chrono/ChronoLocalDateTimeImpl.java b/src/share/classes/java/time/chrono/ChronoLocalDateTimeImpl.java index 14f7bd927..cd7f04e47 100644 --- a/src/share/classes/java/time/chrono/ChronoLocalDateTimeImpl.java +++ b/src/share/classes/java/time/chrono/ChronoLocalDateTimeImpl.java @@ -94,7 +94,7 @@ import java.util.Objects; * * @implSpec * This class is immutable and thread-safe. - * + * @serial * @param the concrete type for the date of this date-time * @since 1.8 */ @@ -157,11 +157,11 @@ final class ChronoLocalDateTimeImpl /** * The date part. */ - private final D date; + private final transient D date; /** * The time part. */ - private final LocalTime time; + private final transient LocalTime time; //----------------------------------------------------------------------- /** @@ -402,6 +402,18 @@ final class ChronoLocalDateTimeImpl } //----------------------------------------------------------------------- + /** + * Writes the ChronoLocalDateTime using a + * dedicated serialized form. + * @serialData + *
+     *  out.writeByte(2);              // identifies a ChronoLocalDateTime
+     *  out.writeObject(toLocalDate());
+     *  out.witeObject(toLocalTime());
+     * 
+ * + * @return the instance of {@code Ser}, not null + */ private Object writeReplace() { return new Ser(Ser.CHRONO_LOCAL_DATE_TIME_TYPE, this); } @@ -411,7 +423,7 @@ final class ChronoLocalDateTimeImpl * @return never * @throws InvalidObjectException always */ - private Object readResolve() throws ObjectStreamException { + private Object readResolve() throws InvalidObjectException { throw new InvalidObjectException("Deserialization via serialization delegate"); } diff --git a/src/share/classes/java/time/chrono/ChronoZonedDateTimeImpl.java b/src/share/classes/java/time/chrono/ChronoZonedDateTimeImpl.java index a39ed298d..1cc7b5b57 100644 --- a/src/share/classes/java/time/chrono/ChronoZonedDateTimeImpl.java +++ b/src/share/classes/java/time/chrono/ChronoZonedDateTimeImpl.java @@ -98,6 +98,7 @@ import java.util.Objects; * @implSpec * This class is immutable and thread-safe. * + * @serial Document the delegation of this class in the serialized-form specification. * @param the concrete type for the date of this date-time * @since 1.8 */ @@ -112,15 +113,15 @@ final class ChronoZonedDateTimeImpl /** * The local date-time. */ - private final ChronoLocalDateTimeImpl dateTime; + private final transient ChronoLocalDateTimeImpl dateTime; /** * The zone offset. */ - private final ZoneOffset offset; + private final transient ZoneOffset offset; /** * The zone ID. */ - private final ZoneId zone; + private final transient ZoneId zone; //----------------------------------------------------------------------- /** @@ -222,6 +223,7 @@ final class ChronoZonedDateTimeImpl } //----------------------------------------------------------------------- + @Override public ZoneOffset getOffset() { return offset; } @@ -256,10 +258,12 @@ final class ChronoZonedDateTimeImpl return dateTime; } + @Override public ZoneId getZone() { return zone; } + @Override public ChronoZonedDateTime withZoneSameLocal(ZoneId zone) { return ofBest(dateTime, zone, offset); } @@ -321,6 +325,19 @@ final class ChronoZonedDateTimeImpl } //----------------------------------------------------------------------- + /** + * Writes the ChronoZonedDateTime using a + * dedicated serialized form. + * @serialData + *
+     *  out.writeByte(3);                  // identifies a ChronoZonedDateTime
+     *  out.writeObject(toLocalDateTime());
+     *  out.writeObject(getOffset());
+     *  out.writeObject(getZone());
+     * 
+ * + * @return the instance of {@code Ser}, not null + */ private Object writeReplace() { return new Ser(Ser.CHRONO_ZONE_DATE_TIME_TYPE, this); } @@ -330,7 +347,7 @@ final class ChronoZonedDateTimeImpl * @return never * @throws InvalidObjectException always */ - private Object readResolve() throws ObjectStreamException { + private Object readResolve() throws InvalidObjectException { throw new InvalidObjectException("Deserialization via serialization delegate"); } diff --git a/src/share/classes/java/time/chrono/Chronology.java b/src/share/classes/java/time/chrono/Chronology.java index 561e2f794..36c9d31c3 100644 --- a/src/share/classes/java/time/chrono/Chronology.java +++ b/src/share/classes/java/time/chrono/Chronology.java @@ -1258,14 +1258,15 @@ public abstract class Chronology implements Comparable { /** * Writes the Chronology using a * dedicated serialized form. + * @serialData *
-     *  out.writeByte(1);  // identifies this as a Chronology
+     *  out.writeByte(1);  // identifies a Chronology
      *  out.writeUTF(getId());
      * 
* * @return the instance of {@code Ser}, not null */ - protected Object writeReplace() { + Object writeReplace() { return new Ser(Ser.CHRONO_TYPE, this); } @@ -1274,14 +1275,26 @@ public abstract class Chronology implements Comparable { * @return never * @throws InvalidObjectException always */ - private Object readResolve() throws ObjectStreamException { + private Object readResolve() throws InvalidObjectException { throw new InvalidObjectException("Deserialization via serialization delegate"); } + /** + * Write the Chronology id to the stream. + * @param out the output stream + * @throws IOException on any error during the write + */ void writeExternal(DataOutput out) throws IOException { out.writeUTF(getId()); } + /** + * Reads the Chronology id and creates the Chronology. + * @param in the input stream + * @return the Chronology + * @throws IOException on errors during the read + * @throws DateTimeException if the Chronology cannot be returned + */ static Chronology readExternal(DataInput in) throws IOException { String id = in.readUTF(); return Chronology.of(id); diff --git a/src/share/classes/java/time/chrono/HijrahChronology.java b/src/share/classes/java/time/chrono/HijrahChronology.java index c5061b05a..7d4485809 100644 --- a/src/share/classes/java/time/chrono/HijrahChronology.java +++ b/src/share/classes/java/time/chrono/HijrahChronology.java @@ -63,6 +63,8 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; import java.io.Serializable; import java.security.AccessController; import java.security.PrivilegedActionException; @@ -217,11 +219,11 @@ public final class HijrahChronology extends Chronology implements Serializable { /** * The Hijrah Calendar id. */ - private final String typeId; + private final transient String typeId; /** * The Hijrah calendarType. */ - private transient final String calendarType; + private final transient String calendarType; /** * Serialization version. */ @@ -236,7 +238,7 @@ public final class HijrahChronology extends Chronology implements Serializable { * Flag to indicate the initialization of configuration data is complete. * @see #checkCalendarInit() */ - private volatile boolean initComplete; + private transient volatile boolean initComplete; /** * Array of epoch days indexed by Hijrah Epoch month. * Computed by {@link #loadCalendarData}. @@ -281,7 +283,7 @@ public final class HijrahChronology extends Chronology implements Serializable { * A reference to the properties stored in * ${java.home}/lib/calendars.properties */ - private transient final static Properties calendarProperties; + private final transient static Properties calendarProperties; /** * Prefix of property names for Hijrah calendar variants. @@ -1073,4 +1075,30 @@ public final class HijrahChronology extends Chronology implements Serializable { throw new IllegalArgumentException("date must be yyyy-MM-dd", ex); } } + + //----------------------------------------------------------------------- + /** + * Writes the Chronology using a + * dedicated serialized form. + * @serialData + *
+     *  out.writeByte(1);     // identifies a Chronology
+     *  out.writeUTF(getId());
+     * 
+ * + * @return the instance of {@code Ser}, not null + */ + @Override + Object writeReplace() { + return super.writeReplace(); + } + + /** + * Defend against malicious streams. + * @return never + * @throws InvalidObjectException always + */ + private Object readResolve() throws InvalidObjectException { + throw new InvalidObjectException("Deserialization via serialization delegate"); + } } diff --git a/src/share/classes/java/time/chrono/HijrahDate.java b/src/share/classes/java/time/chrono/HijrahDate.java index ef40b6dc9..8e385c947 100644 --- a/src/share/classes/java/time/chrono/HijrahDate.java +++ b/src/share/classes/java/time/chrono/HijrahDate.java @@ -65,6 +65,7 @@ import static java.time.temporal.ChronoField.MONTH_OF_YEAR; import static java.time.temporal.ChronoField.YEAR; import java.io.IOException; +import java.io.InvalidObjectException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.io.Serializable; @@ -118,7 +119,7 @@ public final class HijrahDate /** * The Chronology of this HijrahDate. */ - private final HijrahChronology chrono; + private final transient HijrahChronology chrono; /** * The proleptic year. */ @@ -600,29 +601,41 @@ public final class HijrahDate } //----------------------------------------------------------------------- + /** + * Defend against malicious streams. + * @return never + * @throws InvalidObjectException always + */ + private Object readResolve() throws InvalidObjectException { + throw new InvalidObjectException("Deserialization via serialization delegate"); + } + + /** + * Writes the object using a + * dedicated serialized form. + * @serialData + *
+     *  out.writeByte(6);                 // identifies a HijrahDate
+     *  out.writeObject(chrono);          // the HijrahChronology variant
+     *  out.writeInt(get(YEAR));
+     *  out.writeByte(get(MONTH_OF_YEAR));
+     *  out.writeByte(get(DAY_OF_MONTH));
+     * 
+ * + * @return the instance of {@code Ser}, not null + */ private Object writeReplace() { return new Ser(Ser.HIJRAH_DATE_TYPE, this); } void writeExternal(ObjectOutput out) throws IOException { // HijrahChronology is implicit in the Hijrah_DATE_TYPE - out.writeObject(chrono); + out.writeObject(getChronology()); out.writeInt(get(YEAR)); out.writeByte(get(MONTH_OF_YEAR)); out.writeByte(get(DAY_OF_MONTH)); } - /** - * Replaces the date instance from the stream with a valid one. - * ReadExternal has already read the fields and created a new instance - * from the data. - * - * @return the resolved date, never null - */ - private Object readResolve() { - return this; - } - static HijrahDate readExternal(ObjectInput in) throws IOException, ClassNotFoundException { HijrahChronology chrono = (HijrahChronology) in.readObject(); int year = in.readInt(); diff --git a/src/share/classes/java/time/chrono/HijrahEra.java b/src/share/classes/java/time/chrono/HijrahEra.java index 1e99d6062..4390f698c 100644 --- a/src/share/classes/java/time/chrono/HijrahEra.java +++ b/src/share/classes/java/time/chrono/HijrahEra.java @@ -63,9 +63,6 @@ package java.time.chrono; import static java.time.temporal.ChronoField.ERA; -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; import java.time.DateTimeException; import java.time.temporal.ChronoField; import java.time.temporal.TemporalField; @@ -158,18 +155,4 @@ public enum HijrahEra implements Era { return Era.super.range(field); } - //----------------------------------------------------------------------- - private Object writeReplace() { - return new Ser(Ser.HIJRAH_ERA_TYPE, this); - } - - void writeExternal(DataOutput out) throws IOException { - out.writeByte(this.getValue()); - } - - static HijrahEra readExternal(DataInput in) throws IOException { - byte eraValue = in.readByte(); - return HijrahEra.of(eraValue); - } - } diff --git a/src/share/classes/java/time/chrono/IsoChronology.java b/src/share/classes/java/time/chrono/IsoChronology.java index a2f6badea..2012e64e0 100644 --- a/src/share/classes/java/time/chrono/IsoChronology.java +++ b/src/share/classes/java/time/chrono/IsoChronology.java @@ -61,6 +61,8 @@ */ package java.time.chrono; +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; import static java.time.temporal.ChronoField.DAY_OF_MONTH; import static java.time.temporal.ChronoField.ERA; import static java.time.temporal.ChronoField.MONTH_OF_YEAR; @@ -563,4 +565,29 @@ public final class IsoChronology extends Chronology implements Serializable { return field.range(); } + //----------------------------------------------------------------------- + /** + * Writes the Chronology using a + * dedicated serialized form. + * @serialData + *
+     *  out.writeByte(1);     // identifies a Chronology
+     *  out.writeUTF(getId());
+     * 
+ * + * @return the instance of {@code Ser}, not null + */ + @Override + Object writeReplace() { + return super.writeReplace(); + } + + /** + * Defend against malicious streams. + * @return never + * @throws InvalidObjectException always + */ + private Object readResolve() throws InvalidObjectException { + throw new InvalidObjectException("Deserialization via serialization delegate"); + } } diff --git a/src/share/classes/java/time/chrono/JapaneseChronology.java b/src/share/classes/java/time/chrono/JapaneseChronology.java index 68a12755e..b01707e63 100644 --- a/src/share/classes/java/time/chrono/JapaneseChronology.java +++ b/src/share/classes/java/time/chrono/JapaneseChronology.java @@ -56,6 +56,8 @@ */ package java.time.chrono; +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; import static java.time.temporal.ChronoField.DAY_OF_MONTH; import static java.time.temporal.ChronoField.DAY_OF_YEAR; import static java.time.temporal.ChronoField.ERA; @@ -503,4 +505,29 @@ public final class JapaneseChronology extends Chronology implements Serializable return dateYearDay(era, yoe, doy); // smart is same as strict } + //----------------------------------------------------------------------- + /** + * Writes the Chronology using a + * dedicated serialized form. + * @serialData + *
+     *  out.writeByte(1);     // identifies a Chronology
+     *  out.writeUTF(getId());
+     * 
+ * + * @return the instance of {@code Ser}, not null + */ + @Override + Object writeReplace() { + return super.writeReplace(); + } + + /** + * Defend against malicious streams. + * @return never + * @throws InvalidObjectException always + */ + private Object readResolve() throws InvalidObjectException { + throw new InvalidObjectException("Deserialization via serialization delegate"); + } } diff --git a/src/share/classes/java/time/chrono/JapaneseDate.java b/src/share/classes/java/time/chrono/JapaneseDate.java index e49a8f33c..24ad7a921 100644 --- a/src/share/classes/java/time/chrono/JapaneseDate.java +++ b/src/share/classes/java/time/chrono/JapaneseDate.java @@ -69,6 +69,7 @@ import static java.time.temporal.ChronoField.YEAR_OF_ERA; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; +import java.io.InvalidObjectException; import java.io.Serializable; import java.time.Clock; import java.time.DateTimeException; @@ -129,7 +130,7 @@ public final class JapaneseDate /** * The underlying ISO local date. */ - private transient final LocalDate isoDate; + private final transient LocalDate isoDate; /** * The JapaneseEra of this date. */ @@ -689,6 +690,28 @@ public final class JapaneseDate } //----------------------------------------------------------------------- + /** + * Defend against malicious streams. + * @return never + * @throws InvalidObjectException always + */ + private Object readResolve() throws InvalidObjectException { + throw new InvalidObjectException("Deserialization via serialization delegate"); + } + + /** + * Writes the object using a + * dedicated serialized form. + * @serialData + *
+     *  out.writeByte(4);                 // identifies a JapaneseDate
+     *  out.writeInt(get(YEAR));
+     *  out.writeByte(get(MONTH_OF_YEAR));
+     *  out.writeByte(get(DAY_OF_MONTH));
+     * 
+ * + * @return the instance of {@code Ser}, not null + */ private Object writeReplace() { return new Ser(Ser.JAPANESE_DATE_TYPE, this); } diff --git a/src/share/classes/java/time/chrono/JapaneseEra.java b/src/share/classes/java/time/chrono/JapaneseEra.java index 29dda6bbb..46eee0b59 100644 --- a/src/share/classes/java/time/chrono/JapaneseEra.java +++ b/src/share/classes/java/time/chrono/JapaneseEra.java @@ -155,7 +155,7 @@ public final class JapaneseEra * The era value. * @serial */ - private final int eraValue; + private final transient int eraValue; // the first day of the era private final transient LocalDate since; @@ -371,6 +371,17 @@ public final class JapaneseEra } //----------------------------------------------------------------------- + /** + * Writes the object using a + * dedicated serialized form. + * @serialData + *
+     *  out.writeByte(5);        // identifies a JapaneseEra
+     *  out.writeInt(getValue());
+     * 
+ * + * @return the instance of {@code Ser}, not null + */ private Object writeReplace() { return new Ser(Ser.JAPANESE_ERA_TYPE, this); } diff --git a/src/share/classes/java/time/chrono/MinguoChronology.java b/src/share/classes/java/time/chrono/MinguoChronology.java index 088588004..db75a8645 100644 --- a/src/share/classes/java/time/chrono/MinguoChronology.java +++ b/src/share/classes/java/time/chrono/MinguoChronology.java @@ -56,6 +56,8 @@ */ package java.time.chrono; +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; import static java.time.temporal.ChronoField.PROLEPTIC_MONTH; import static java.time.temporal.ChronoField.YEAR; @@ -333,4 +335,29 @@ public final class MinguoChronology extends Chronology implements Serializable { return (MinguoDate) super.resolveDate(fieldValues, resolverStyle); } + //----------------------------------------------------------------------- + /** + * Writes the Chronology using a + * dedicated serialized form. + * @serialData + *
+     *  out.writeByte(1);     // identifies a Chronology
+     *  out.writeUTF(getId());
+     * 
+ * + * @return the instance of {@code Ser}, not null + */ + @Override + Object writeReplace() { + return super.writeReplace(); + } + + /** + * Defend against malicious streams. + * @return never + * @throws InvalidObjectException always + */ + private Object readResolve() throws InvalidObjectException { + throw new InvalidObjectException("Deserialization via serialization delegate"); + } } diff --git a/src/share/classes/java/time/chrono/MinguoDate.java b/src/share/classes/java/time/chrono/MinguoDate.java index e15b0e90a..16585e7e9 100644 --- a/src/share/classes/java/time/chrono/MinguoDate.java +++ b/src/share/classes/java/time/chrono/MinguoDate.java @@ -64,6 +64,7 @@ import static java.time.temporal.ChronoField.YEAR; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; +import java.io.InvalidObjectException; import java.io.Serializable; import java.time.Clock; import java.time.DateTimeException; @@ -106,7 +107,7 @@ public final class MinguoDate /** * The underlying date. */ - private final LocalDate isoDate; + private final transient LocalDate isoDate; //----------------------------------------------------------------------- /** @@ -448,6 +449,28 @@ public final class MinguoDate } //----------------------------------------------------------------------- + /** + * Defend against malicious streams. + * @return never + * @throws InvalidObjectException always + */ + private Object readResolve() throws InvalidObjectException { + throw new InvalidObjectException("Deserialization via serialization delegate"); + } + + /** + * Writes the object using a + * dedicated serialized form. + * @serialData + *
+     *  out.writeByte(8);                 // identifies a MinguoDate
+     *  out.writeInt(get(YEAR));
+     *  out.writeByte(get(MONTH_OF_YEAR));
+     *  out.writeByte(get(DAY_OF_MONTH));
+     * 
+ * + * @return the instance of {@code Ser}, not null + */ private Object writeReplace() { return new Ser(Ser.MINGUO_DATE_TYPE, this); } diff --git a/src/share/classes/java/time/chrono/MinguoEra.java b/src/share/classes/java/time/chrono/MinguoEra.java index edf1ad561..6bfdab255 100644 --- a/src/share/classes/java/time/chrono/MinguoEra.java +++ b/src/share/classes/java/time/chrono/MinguoEra.java @@ -61,9 +61,6 @@ */ package java.time.chrono; -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; import java.time.DateTimeException; /** @@ -155,18 +152,4 @@ public enum MinguoEra implements Era { return ordinal(); } - //----------------------------------------------------------------------- - private Object writeReplace() { - return new Ser(Ser.MINGUO_ERA_TYPE, this); - } - - void writeExternal(DataOutput out) throws IOException { - out.writeByte(this.getValue()); - } - - static MinguoEra readExternal(DataInput in) throws IOException { - byte eraValue = in.readByte(); - return MinguoEra.of(eraValue); - } - } diff --git a/src/share/classes/java/time/chrono/Ser.java b/src/share/classes/java/time/chrono/Ser.java index ff59aec41..cc99f481f 100644 --- a/src/share/classes/java/time/chrono/Ser.java +++ b/src/share/classes/java/time/chrono/Ser.java @@ -74,14 +74,14 @@ import java.time.LocalDateTime; * byte flag would be used in order to specify an alternative version of the type format. * For example {@code CHRONO_TYPE_VERSION_2 = 21} *

- * In order to serialise the object it writes its byte and then calls back to the appropriate class where - * the serialisation is performed. In order to deserialise the object it read in the type byte, switching + * In order to serialize the object it writes its byte and then calls back to the appropriate class where + * the serialization is performed. In order to deserialize the object it read in the type byte, switching * in order to select which class to call back into. *

- * The serialisation format is determined on a per class basis. In the case of field based classes each + * The serialization format is determined on a per class basis. In the case of field based classes each * of the fields is written out with an appropriate size format in descending order of the field's size. For * example in the case of {@link LocalDate} year is written before month. Composite classes, such as - * {@link LocalDateTime} are serialised as one object. Enum classes are serialised using the index of their + * {@link LocalDateTime} are serialized as one object. Enum classes are serialized using the index of their * element. *

* This class is mutable and should be created once per serialization. @@ -102,11 +102,8 @@ final class Ser implements Externalizable { static final byte JAPANESE_DATE_TYPE = 4; static final byte JAPANESE_ERA_TYPE = 5; static final byte HIJRAH_DATE_TYPE = 6; - static final byte HIJRAH_ERA_TYPE = 7; - static final byte MINGUO_DATE_TYPE = 8; - static final byte MINGUO_ERA_TYPE = 9; - static final byte THAIBUDDHIST_DATE_TYPE = 10; - static final byte THAIBUDDHIST_ERA_TYPE = 11; + static final byte MINGUO_DATE_TYPE = 7; + static final byte THAIBUDDHIST_DATE_TYPE = 8; /** The type being serialized. */ private byte type; @@ -133,6 +130,24 @@ final class Ser implements Externalizable { //----------------------------------------------------------------------- /** * Implements the {@code Externalizable} interface to write the object. + * @serialData + * Each serializable class is mapped to a type that is the first byte + * in the stream. Refer to each class {@code writeReplace} + * serialized form for the value of the type and sequence of values for the type. + *

* * @param out the data stream to write to, not null */ @@ -162,21 +177,12 @@ final class Ser implements Externalizable { case HIJRAH_DATE_TYPE: ((HijrahDate) object).writeExternal(out); break; - case HIJRAH_ERA_TYPE: - ((HijrahEra) object).writeExternal(out); - break; case MINGUO_DATE_TYPE: ((MinguoDate) object).writeExternal(out); break; - case MINGUO_ERA_TYPE: - ((MinguoEra) object).writeExternal(out); - break; case THAIBUDDHIST_DATE_TYPE: ((ThaiBuddhistDate) object).writeExternal(out); break; - case THAIBUDDHIST_ERA_TYPE: - ((ThaiBuddhistEra) object).writeExternal(out); - break; default: throw new InvalidClassException("Unknown serialized type"); } @@ -185,8 +191,28 @@ final class Ser implements Externalizable { //----------------------------------------------------------------------- /** * Implements the {@code Externalizable} interface to read the object. + * @serialData + * The streamed type and parameters defined by the type's {@code writeReplace} + * method are read and passed to the corresponding static factory for the type + * to create a new instance. That instance is returned as the de-serialized + * {@code Ser} object. + * + * * - * @param in the data to read, not null + * @param in the data stream to read from, not null */ @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { @@ -207,11 +233,8 @@ final class Ser implements Externalizable { case JAPANESE_DATE_TYPE: return JapaneseDate.readExternal(in); case JAPANESE_ERA_TYPE: return JapaneseEra.readExternal(in); case HIJRAH_DATE_TYPE: return HijrahDate.readExternal(in); - case HIJRAH_ERA_TYPE: return HijrahEra.readExternal(in); case MINGUO_DATE_TYPE: return MinguoDate.readExternal(in); - case MINGUO_ERA_TYPE: return MinguoEra.readExternal(in); case THAIBUDDHIST_DATE_TYPE: return ThaiBuddhistDate.readExternal(in); - case THAIBUDDHIST_ERA_TYPE: return ThaiBuddhistEra.readExternal(in); default: throw new StreamCorruptedException("Unknown serialized type"); } } diff --git a/src/share/classes/java/time/chrono/ThaiBuddhistChronology.java b/src/share/classes/java/time/chrono/ThaiBuddhistChronology.java index 04f59ceb9..4ffd15bfc 100644 --- a/src/share/classes/java/time/chrono/ThaiBuddhistChronology.java +++ b/src/share/classes/java/time/chrono/ThaiBuddhistChronology.java @@ -56,6 +56,8 @@ */ package java.time.chrono; +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; import static java.time.temporal.ChronoField.PROLEPTIC_MONTH; import static java.time.temporal.ChronoField.YEAR; @@ -369,4 +371,29 @@ public final class ThaiBuddhistChronology extends Chronology implements Serializ return (ThaiBuddhistDate) super.resolveDate(fieldValues, resolverStyle); } + //----------------------------------------------------------------------- + /** + * Writes the Chronology using a + * dedicated serialized form. + * @serialData + *
+     *  out.writeByte(1);     // identifies a Chronology
+     *  out.writeUTF(getId());
+     * 
+ * + * @return the instance of {@code Ser}, not null + */ + @Override + Object writeReplace() { + return super.writeReplace(); + } + + /** + * Defend against malicious streams. + * @return never + * @throws InvalidObjectException always + */ + private Object readResolve() throws InvalidObjectException { + throw new InvalidObjectException("Deserialization via serialization delegate"); + } } diff --git a/src/share/classes/java/time/chrono/ThaiBuddhistDate.java b/src/share/classes/java/time/chrono/ThaiBuddhistDate.java index 67799ad5a..3d8f4078c 100644 --- a/src/share/classes/java/time/chrono/ThaiBuddhistDate.java +++ b/src/share/classes/java/time/chrono/ThaiBuddhistDate.java @@ -64,6 +64,7 @@ import static java.time.temporal.ChronoField.YEAR; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; +import java.io.InvalidObjectException; import java.io.Serializable; import java.time.Clock; import java.time.DateTimeException; @@ -106,7 +107,7 @@ public final class ThaiBuddhistDate /** * The underlying date. */ - private final LocalDate isoDate; + private final transient LocalDate isoDate; //----------------------------------------------------------------------- /** @@ -448,6 +449,28 @@ public final class ThaiBuddhistDate } //----------------------------------------------------------------------- + /** + * Defend against malicious streams. + * @return never + * @throws InvalidObjectException always + */ + private Object readResolve() throws InvalidObjectException { + throw new InvalidObjectException("Deserialization via serialization delegate"); + } + + /** + * Writes the object using a + * dedicated serialized form. + * @serialData + *
+     *  out.writeByte(10);                // identifies a ThaiBuddhistDate
+     *  out.writeInt(get(YEAR));
+     *  out.writeByte(get(MONTH_OF_YEAR));
+     *  out.writeByte(get(DAY_OF_MONTH));
+     * 
+ * + * @return the instance of {@code Ser}, not null + */ private Object writeReplace() { return new Ser(Ser.THAIBUDDHIST_DATE_TYPE, this); } diff --git a/src/share/classes/java/time/chrono/ThaiBuddhistEra.java b/src/share/classes/java/time/chrono/ThaiBuddhistEra.java index d91eb81d7..c549229d3 100644 --- a/src/share/classes/java/time/chrono/ThaiBuddhistEra.java +++ b/src/share/classes/java/time/chrono/ThaiBuddhistEra.java @@ -61,9 +61,6 @@ */ package java.time.chrono; -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; import java.time.DateTimeException; /** @@ -155,18 +152,4 @@ public enum ThaiBuddhistEra implements Era { return ordinal(); } - //----------------------------------------------------------------------- - private Object writeReplace() { - return new Ser(Ser.THAIBUDDHIST_ERA_TYPE, this); - } - - void writeExternal(DataOutput out) throws IOException { - out.writeByte(this.getValue()); - } - - static ThaiBuddhistEra readExternal(DataInput in) throws IOException { - byte eraValue = in.readByte(); - return ThaiBuddhistEra.of(eraValue); - } - } diff --git a/src/share/classes/java/time/zone/Ser.java b/src/share/classes/java/time/zone/Ser.java index e34126436..b2c3b259f 100644 --- a/src/share/classes/java/time/zone/Ser.java +++ b/src/share/classes/java/time/zone/Ser.java @@ -119,9 +119,20 @@ final class Ser implements Externalizable { //----------------------------------------------------------------------- /** * Implements the {@code Externalizable} interface to write the object. + * @serialData + * Each serializable class is mapped to a type that is the first byte + * in the stream. Refer to each class {@code writeReplace} + * serialized form for the value of the type and sequence of values for the type. + * + * * * @param out the data stream to write to, not null */ + @Override public void writeExternal(ObjectOutput out) throws IOException { writeInternal(type, object, out); } @@ -150,9 +161,23 @@ final class Ser implements Externalizable { //----------------------------------------------------------------------- /** * Implements the {@code Externalizable} interface to read the object. + * @serialData + * The streamed type and parameters defined by the type's {@code writeReplace} + * method are read and passed to the corresponding static factory for the type + * to create a new instance. That instance is returned as the de-serialized + * {@code Ser} object. * + * * @param in the data to read, not null */ + @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { type = in.readByte(); object = readInternal(type, in); diff --git a/src/share/classes/java/time/zone/ZoneOffsetTransition.java b/src/share/classes/java/time/zone/ZoneOffsetTransition.java index f2eab7c88..04192dab8 100644 --- a/src/share/classes/java/time/zone/ZoneOffsetTransition.java +++ b/src/share/classes/java/time/zone/ZoneOffsetTransition.java @@ -64,6 +64,7 @@ package java.time.zone; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; +import java.io.InvalidObjectException; import java.io.Serializable; import java.time.Duration; import java.time.Instant; @@ -170,8 +171,29 @@ public final class ZoneOffsetTransition //----------------------------------------------------------------------- /** - * Uses a serialization delegate. + * Defend against malicious streams. + * @return never + * @throws InvalidObjectException always + */ + private Object readResolve() throws InvalidObjectException { + throw new InvalidObjectException("Deserialization via serialization delegate"); + } + + /** + * Writes the object using a + * dedicated serialized form. + * @serialData + * Refer to the serialized form of + * ZoneRules.writeReplace + * for the encoding of epoch seconds and offsets. + *
{@code
      *
+     *   out.writeByte(2);                // identifies a ZoneOffsetTransition
+     *   out.writeEpochSec(toEpochSecond);
+     *   out.writeOffset(offsetBefore);
+     *   out.writeOfset(offsetAfter);
+     * }
+     * 
* @return the replacing object, not null */ private Object writeReplace() { diff --git a/src/share/classes/java/time/zone/ZoneOffsetTransitionRule.java b/src/share/classes/java/time/zone/ZoneOffsetTransitionRule.java index ad52f82de..20e97f023 100644 --- a/src/share/classes/java/time/zone/ZoneOffsetTransitionRule.java +++ b/src/share/classes/java/time/zone/ZoneOffsetTransitionRule.java @@ -67,6 +67,7 @@ import static java.time.temporal.TemporalAdjuster.previousOrSame; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; +import java.io.InvalidObjectException; import java.io.Serializable; import java.time.DayOfWeek; import java.time.LocalDate; @@ -231,7 +232,56 @@ public final class ZoneOffsetTransitionRule implements Serializable { //----------------------------------------------------------------------- /** - * Uses a serialization delegate. + * Defend against malicious streams. + * @return never + * @throws InvalidObjectException always + */ + private Object readResolve() throws InvalidObjectException { + throw new InvalidObjectException("Deserialization via serialization delegate"); + } + + /** + * Writes the object using a + * dedicated serialized form. + * @serialData + * Refer to the serialized form of + * ZoneRules.writeReplace + * for the encoding of epoch seconds and offsets. + *
{@code
+     *
+     *      out.writeByte(3);                // identifies a ZoneOffsetTransition
+     *      final int timeSecs = (timeEndOfDay ? 86400 : time.toSecondOfDay());
+     *      final int stdOffset = standardOffset.getTotalSeconds();
+     *      final int beforeDiff = offsetBefore.getTotalSeconds() - stdOffset;
+     *      final int afterDiff = offsetAfter.getTotalSeconds() - stdOffset;
+     *      final int timeByte = (timeSecs % 3600 == 0 ? (timeEndOfDay ? 24 : time.getHour()) : 31);
+     *      final int stdOffsetByte = (stdOffset % 900 == 0 ? stdOffset / 900 + 128 : 255);
+     *      final int beforeByte = (beforeDiff == 0 || beforeDiff == 1800 || beforeDiff == 3600 ? beforeDiff / 1800 : 3);
+     *      final int afterByte = (afterDiff == 0 || afterDiff == 1800 || afterDiff == 3600 ? afterDiff / 1800 : 3);
+     *      final int dowByte = (dow == null ? 0 : dow.getValue());
+     *      int b = (month.getValue() << 28) +          // 4 bits
+     *              ((dom + 32) << 22) +                // 6 bits
+     *              (dowByte << 19) +                   // 3 bits
+     *              (timeByte << 14) +                  // 5 bits
+     *              (timeDefinition.ordinal() << 12) +  // 2 bits
+     *              (stdOffsetByte << 4) +              // 8 bits
+     *              (beforeByte << 2) +                 // 2 bits
+     *              afterByte;                          // 2 bits
+     *      out.writeInt(b);
+     *      if (timeByte == 31) {
+     *          out.writeInt(timeSecs);
+     *      }
+     *      if (stdOffsetByte == 255) {
+     *          out.writeInt(stdOffset);
+     *      }
+     *      if (beforeByte == 3) {
+     *          out.writeInt(offsetBefore.getTotalSeconds());
+     *      }
+     *      if (afterByte == 3) {
+     *          out.writeInt(offsetAfter.getTotalSeconds());
+     *      }
+     * }
+     * 
* * @return the replacing object, not null */ diff --git a/src/share/classes/java/time/zone/ZoneRules.java b/src/share/classes/java/time/zone/ZoneRules.java index 070ad6e83..064a2d8f8 100644 --- a/src/share/classes/java/time/zone/ZoneRules.java +++ b/src/share/classes/java/time/zone/ZoneRules.java @@ -64,6 +64,7 @@ package java.time.zone; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; +import java.io.InvalidObjectException; import java.io.Serializable; import java.time.Duration; import java.time.Instant; @@ -145,7 +146,7 @@ public final class ZoneRules implements Serializable { /** * The map of recent transitions. */ - private final ConcurrentMap lastRulesCache = + private final transient ConcurrentMap lastRulesCache = new ConcurrentHashMap(); /** * The zero-length long array. @@ -315,8 +316,74 @@ public final class ZoneRules implements Serializable { } /** - * Uses a serialization delegate. + * Defend against malicious streams. + * @return never + * @throws InvalidObjectException always + */ + private Object readResolve() throws InvalidObjectException { + throw new InvalidObjectException("Deserialization via serialization delegate"); + } + + /** + * Writes the object using a + * dedicated serialized form. + * @serialData + *
{@code
+     *
+     *   out.writeByte(1);  // identifies a ZoneRules
+     *   out.writeInt(standardTransitions.length);
+     *   for (long trans : standardTransitions) {
+     *       Ser.writeEpochSec(trans, out);
+     *   }
+     *   for (ZoneOffset offset : standardOffsets) {
+     *       Ser.writeOffset(offset, out);
+     *   }
+     *   out.writeInt(savingsInstantTransitions.length);
+     *   for (long trans : savingsInstantTransitions) {
+     *       Ser.writeEpochSec(trans, out);
+     *   }
+     *   for (ZoneOffset offset : wallOffsets) {
+     *       Ser.writeOffset(offset, out);
+     *   }
+     *   out.writeByte(lastRules.length);
+     *   for (ZoneOffsetTransitionRule rule : lastRules) {
+     *       rule.writeExternal(out);
+     *   }
+     * }
+     * 
+ *

+ * Epoch second values used for offsets are encoded in a variable + * length form to make the common cases put fewer bytes in the stream. + *

{@code
+     *
+     *  static void writeEpochSec(long epochSec, DataOutput out) throws IOException {
+     *     if (epochSec >= -4575744000L && epochSec < 10413792000L && epochSec % 900 == 0) {  // quarter hours between 1825 and 2300
+     *         int store = (int) ((epochSec + 4575744000L) / 900);
+     *         out.writeByte((store >>> 16) & 255);
+     *         out.writeByte((store >>> 8) & 255);
+     *         out.writeByte(store & 255);
+     *      } else {
+     *          out.writeByte(255);
+     *          out.writeLong(epochSec);
+     *      }
+     *  }
+     * }
+     * 
+ *

+ * ZoneOffset values are encoded in a variable length form so the + * common cases put fewer bytes in the stream. + *

{@code
      *
+     *  static void writeOffset(ZoneOffset offset, DataOutput out) throws IOException {
+     *     final int offsetSecs = offset.getTotalSeconds();
+     *     int offsetByte = offsetSecs % 900 == 0 ? offsetSecs / 900 : 127;  // compress to -72 to +72
+     *     out.writeByte(offsetByte);
+     *     if (offsetByte == 127) {
+     *         out.writeInt(offsetSecs);
+     *     }
+     * }
+     *}
+     * 
* @return the replacing object, not null */ private Object writeReplace() { diff --git a/test/java/time/tck/java/time/chrono/TCKChronologySerialization.java b/test/java/time/tck/java/time/chrono/TCKChronologySerialization.java index e9d5fb4f1..35620ad75 100644 --- a/test/java/time/tck/java/time/chrono/TCKChronologySerialization.java +++ b/test/java/time/tck/java/time/chrono/TCKChronologySerialization.java @@ -97,7 +97,9 @@ public class TCKChronologySerialization { ObjectOutputStream out = new ObjectOutputStream(baos); out.writeObject(chrono); out.close(); - ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + + byte[] bytes = baos.toByteArray(); + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ObjectInputStream in = new ObjectInputStream(bais); @SuppressWarnings("unchecked") -- GitLab