From 83882b0a911dd708737203bf74ebb35f56f239b8 Mon Sep 17 00:00:00 2001 From: Mark Rose Date: Sun, 16 Jul 2023 15:36:12 -0700 Subject: [PATCH 1/2] Allow overriding system time with custom time provider Add an interface for a custom time provider. Allow setting a time provider to override system time, in order to use a simulated time instead. Augment unit tests. --- src/main/java/org/yamcs/jsle/CcsdsTime.java | 24 +++++- .../org/yamcs/jsle/DefaultTimeProvider.java | 13 ++++ .../java/org/yamcs/jsle/TimeProvider.java | 16 ++++ .../java/org/yamcs/jsle/CcsdsTimeTest.java | 74 ++++++++++++++++++- 4 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 src/main/java/org/yamcs/jsle/DefaultTimeProvider.java create mode 100644 src/main/java/org/yamcs/jsle/TimeProvider.java diff --git a/src/main/java/org/yamcs/jsle/CcsdsTime.java b/src/main/java/org/yamcs/jsle/CcsdsTime.java index ae35c36..a1b2ab7 100644 --- a/src/main/java/org/yamcs/jsle/CcsdsTime.java +++ b/src/main/java/org/yamcs/jsle/CcsdsTime.java @@ -20,6 +20,7 @@ public class CcsdsTime implements Comparable { final private int numDays; + private static TimeProvider timeProvider = new DefaultTimeProvider(); final private long picosecInDay; final static DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_INSTANT; final static DateTimeFormatter FORMATTER_SEC = new DateTimeFormatterBuilder().appendInstant(0).toFormatter(); @@ -84,8 +85,26 @@ public static CcsdsTime fromCcsdsPico(byte[] ds) { * * @return the current time */ - static public CcsdsTime now() { - return fromJavaMillis(System.currentTimeMillis()); + public static synchronized CcsdsTime now() { + return fromJavaMillis(timeProvider.getSystemTime()); + } + + /** + * Gets the current time provider. + * + * @return the time provider + */ + public static synchronized TimeProvider getTimeProvider() { + return timeProvider; + } + + /** + * Sets the time provider to use. + * + * @param provider the new time provider + */ + public static synchronized void setTimeProvider(TimeProvider provider) { + timeProvider = provider; } /** @@ -264,6 +283,7 @@ public int compareTo(CcsdsTime o) { /** * Formats the time with up to nanosecond resolution */ + @Override public String toString() { Instant inst = Instant.ofEpochSecond(((long) numDays - NUM_DAYS_1958_1970) * SEC_IN_DAY, picosecInDay / 1000); return FORMATTER.format(inst); diff --git a/src/main/java/org/yamcs/jsle/DefaultTimeProvider.java b/src/main/java/org/yamcs/jsle/DefaultTimeProvider.java new file mode 100644 index 0000000..2bfa13d --- /dev/null +++ b/src/main/java/org/yamcs/jsle/DefaultTimeProvider.java @@ -0,0 +1,13 @@ +package org.yamcs.jsle; + +/** + * Implements a time provider that uses the time from the operating system. + */ +public class DefaultTimeProvider implements TimeProvider { + + @Override + public long getSystemTime() { + return System.currentTimeMillis(); + } + +} diff --git a/src/main/java/org/yamcs/jsle/TimeProvider.java b/src/main/java/org/yamcs/jsle/TimeProvider.java new file mode 100644 index 0000000..dcfa3ef --- /dev/null +++ b/src/main/java/org/yamcs/jsle/TimeProvider.java @@ -0,0 +1,16 @@ +package org.yamcs.jsle; + +/** + * Defines an interface that time providers must implement. + */ +public interface TimeProvider { + + /** + * Gets the current system time, real or simulated, as a UNIX + * time in milliseconds. + * + * @return the system time, as a UNIX time in milliseconds + */ + long getSystemTime(); + +} diff --git a/src/test/java/org/yamcs/jsle/CcsdsTimeTest.java b/src/test/java/org/yamcs/jsle/CcsdsTimeTest.java index 3cfc477..bc25842 100644 --- a/src/test/java/org/yamcs/jsle/CcsdsTimeTest.java +++ b/src/test/java/org/yamcs/jsle/CcsdsTimeTest.java @@ -1,15 +1,36 @@ package org.yamcs.jsle; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import java.time.Instant; +import org.junit.After; +import org.junit.Before; import org.junit.Test; -import org.yamcs.jsle.CcsdsTime; import io.netty.buffer.ByteBufUtil; public class CcsdsTimeTest { + + private TimeProvider savedProvider; + + /** + * Saves the current time provider prior to each test. + */ + @Before + public void setup() { + savedProvider = CcsdsTime.getTimeProvider(); + } + + /** + * Restores the time provider to the default after testing. + */ + @After + public void cleanup() { + CcsdsTime.setTimeProvider(savedProvider); + } + @Test public void test1() { long t = Instant.parse("1958-01-01T00:00:00Z").toEpochMilli(); @@ -44,4 +65,55 @@ public void test5() { CcsdsTime t = new CcsdsTime(1, 301123456789012L); assertEquals("1958-01-02T00:05:01.123456789012Z", t.toStringPico()); } + + /** + * Tests that the default time provider gives the current system time. + */ + @Test + public void testDefaultProvider() { + CcsdsTime.setTimeProvider(new DefaultTimeProvider()); + CcsdsTime time1 = CcsdsTime.fromJavaMillis(System.currentTimeMillis()); + CcsdsTime now = CcsdsTime.now(); + CcsdsTime time2 = CcsdsTime.fromJavaMillis(System.currentTimeMillis()); + assertTrue(time1.compareTo(now) <= 0); + assertTrue(now.compareTo(time2) <= 0); + } + + /** + * Tests that the time provider can be overridden by one providing + * a simulated time. + */ + @Test + public void testSimulatedTimeProvider() { + MockTimeProvider mockProvider = new MockTimeProvider(); + CcsdsTime.setTimeProvider(mockProvider); + CcsdsTime fixedTime = CcsdsTime.fromJavaMillis(12345); + mockProvider.setSystemTime(12345); + CcsdsTime now = CcsdsTime.now(); + assertEquals(0, fixedTime.compareTo(now)); + } + + /** + * Implements a time provider that returns a time set by + * a method call. + */ + private static class MockTimeProvider implements TimeProvider { + + private long time; + + @Override + public long getSystemTime() { + return time; + } + + /** + * Sets the time that should be returned. + * + * @param time a time in UNIX milliseconds + */ + public void setSystemTime(long time) { + this.time = time; + } + + } } From 33d4e1c5a23ad4a4b52e8624ee38ddc5d106e848 Mon Sep 17 00:00:00 2001 From: Mark Rose Date: Wed, 30 Aug 2023 11:35:56 -0700 Subject: [PATCH 2/2] Use service loader to find the TimeProvider implementation, if any Respond to code review comments by changing TimeProvider to return a CcsdsTime directly, rather than a Java time, and to be found by CcsdsTime by using the Java service loader facilities. --- src/main/java/org/yamcs/jsle/CcsdsTime.java | 57 +++++++++---------- .../org/yamcs/jsle/DefaultTimeProvider.java | 13 ----- .../java/org/yamcs/jsle/TimeProvider.java | 7 +-- .../java/org/yamcs/jsle/CcsdsTimeTest.java | 37 ++++-------- 4 files changed, 43 insertions(+), 71 deletions(-) delete mode 100644 src/main/java/org/yamcs/jsle/DefaultTimeProvider.java diff --git a/src/main/java/org/yamcs/jsle/CcsdsTime.java b/src/main/java/org/yamcs/jsle/CcsdsTime.java index a1b2ab7..7d5d8fb 100644 --- a/src/main/java/org/yamcs/jsle/CcsdsTime.java +++ b/src/main/java/org/yamcs/jsle/CcsdsTime.java @@ -3,6 +3,8 @@ import java.time.Instant; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; +import java.util.Optional; +import java.util.ServiceLoader; import ccsds.sle.transfer.service.common.types.ConditionalTime; import ccsds.sle.transfer.service.common.types.Time; @@ -20,11 +22,24 @@ public class CcsdsTime implements Comparable { final private int numDays; - private static TimeProvider timeProvider = new DefaultTimeProvider(); + /** An optional reference to a time provider, if supplied by the surrounding application. */ + private static Optional timeProvider = ServiceLoader.load(TimeProvider.class).findFirst(); + final private long picosecInDay; final static DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_INSTANT; final static DateTimeFormatter FORMATTER_SEC = new DateTimeFormatterBuilder().appendInstant(0).toFormatter(); + /** + * Sets the time provider to a specific optional implementation. Default scope + * for unit testing. This method can be used to set a time provider that is not + * found by the service loader. + * + * @param timeProvider an optional time provider + */ + static void setTimeProvider(Optional timeProvider) { + CcsdsTime.timeProvider = timeProvider; + } + public CcsdsTime(int numDays, long picosecInDay) { this.numDays = numDays; this.picosecInDay = picosecInDay; @@ -82,33 +97,19 @@ public static CcsdsTime fromCcsdsPico(byte[] ds) { /** * Gets the current time - * - * @return the current time - */ - public static synchronized CcsdsTime now() { - return fromJavaMillis(timeProvider.getSystemTime()); - } - - /** - * Gets the current time provider. * - * @return the time provider - */ - public static synchronized TimeProvider getTimeProvider() { - return timeProvider; - } - - /** - * Sets the time provider to use. - * - * @param provider the new time provider + * @return the current time */ - public static synchronized void setTimeProvider(TimeProvider provider) { - timeProvider = provider; + public static CcsdsTime now() { + if (timeProvider.isPresent()) { + return timeProvider.get().getSystemTime(); + } else { + return fromJavaMillis(System.currentTimeMillis()); + } } /** - * Converts a java time in milliseconds + * Converts a java time in milliseconds. * * @param javaTime * @return @@ -120,8 +121,8 @@ static public CcsdsTime fromJavaMillis(long javaTime) { } /** - * Converts a java time in milliseconds - * + * Converts a java time in milliseconds and picoseconds. + * * @param javaTime * @return */ @@ -269,8 +270,6 @@ public long toJavaMillisec() { return ((long) numDays - NUM_DAYS_1958_1970) * MS_IN_DAY + picosecInDay / 1000_000_000; } - - @Override public int compareTo(CcsdsTime o) { int x = Integer.compare(numDays, o.numDays); @@ -279,7 +278,7 @@ public int compareTo(CcsdsTime o) { } return x; } - + /** * Formats the time with up to nanosecond resolution */ @@ -288,7 +287,7 @@ public String toString() { Instant inst = Instant.ofEpochSecond(((long) numDays - NUM_DAYS_1958_1970) * SEC_IN_DAY, picosecInDay / 1000); return FORMATTER.format(inst); } - + /** * Converts to ISO8860 string with 12 digits picoseconds after dot * @return diff --git a/src/main/java/org/yamcs/jsle/DefaultTimeProvider.java b/src/main/java/org/yamcs/jsle/DefaultTimeProvider.java deleted file mode 100644 index 2bfa13d..0000000 --- a/src/main/java/org/yamcs/jsle/DefaultTimeProvider.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.yamcs.jsle; - -/** - * Implements a time provider that uses the time from the operating system. - */ -public class DefaultTimeProvider implements TimeProvider { - - @Override - public long getSystemTime() { - return System.currentTimeMillis(); - } - -} diff --git a/src/main/java/org/yamcs/jsle/TimeProvider.java b/src/main/java/org/yamcs/jsle/TimeProvider.java index dcfa3ef..204b371 100644 --- a/src/main/java/org/yamcs/jsle/TimeProvider.java +++ b/src/main/java/org/yamcs/jsle/TimeProvider.java @@ -6,11 +6,10 @@ public interface TimeProvider { /** - * Gets the current system time, real or simulated, as a UNIX - * time in milliseconds. + * Gets the current system time as a CcsdsTime. * - * @return the system time, as a UNIX time in milliseconds + * @return the system time, as a CCSDS time */ - long getSystemTime(); + CcsdsTime getSystemTime(); } diff --git a/src/test/java/org/yamcs/jsle/CcsdsTimeTest.java b/src/test/java/org/yamcs/jsle/CcsdsTimeTest.java index bc25842..0b267a8 100644 --- a/src/test/java/org/yamcs/jsle/CcsdsTimeTest.java +++ b/src/test/java/org/yamcs/jsle/CcsdsTimeTest.java @@ -4,31 +4,18 @@ import static org.junit.Assert.assertTrue; import java.time.Instant; +import java.util.Optional; import org.junit.After; -import org.junit.Before; import org.junit.Test; import io.netty.buffer.ByteBufUtil; public class CcsdsTimeTest { - private TimeProvider savedProvider; - - /** - * Saves the current time provider prior to each test. - */ - @Before - public void setup() { - savedProvider = CcsdsTime.getTimeProvider(); - } - - /** - * Restores the time provider to the default after testing. - */ @After public void cleanup() { - CcsdsTime.setTimeProvider(savedProvider); + CcsdsTime.setTimeProvider(Optional.empty()); } @Test @@ -36,30 +23,30 @@ public void test1() { long t = Instant.parse("1958-01-01T00:00:00Z").toEpochMilli(); byte[] b = CcsdsTime.fromJavaMillis(t).getDaySegmented(); assertEquals("0000000000000000", ByteBufUtil.hexDump(b)); - + assertEquals(t, CcsdsTime.fromJavaMillis(t).toJavaMillisec()); } - + @Test public void test2() { String hex = "5764045eb6d00061"; - + CcsdsTime t = CcsdsTime.fromCcsds(ByteBufUtil.decodeHexDump(hex)); assertEquals(hex, ByteBufUtil.hexDump(t.getDaySegmented())); } - + @Test public void test3() { CcsdsTime t = CcsdsTime.fromUnix(3601, 3_000_000); assertEquals("1970-01-01T01:00:01.003Z", t.toString()); } - + @Test public void test4() { CcsdsTime t = CcsdsTime.fromUnix(1588711800, 1_000_000); assertEquals("2020-05-05T20:50:00.001Z", t.toString()); } - + @Test public void test5() { CcsdsTime t = new CcsdsTime(1, 301123456789012L); @@ -71,7 +58,6 @@ public void test5() { */ @Test public void testDefaultProvider() { - CcsdsTime.setTimeProvider(new DefaultTimeProvider()); CcsdsTime time1 = CcsdsTime.fromJavaMillis(System.currentTimeMillis()); CcsdsTime now = CcsdsTime.now(); CcsdsTime time2 = CcsdsTime.fromJavaMillis(System.currentTimeMillis()); @@ -86,7 +72,7 @@ public void testDefaultProvider() { @Test public void testSimulatedTimeProvider() { MockTimeProvider mockProvider = new MockTimeProvider(); - CcsdsTime.setTimeProvider(mockProvider); + CcsdsTime.setTimeProvider(Optional.of(mockProvider)); CcsdsTime fixedTime = CcsdsTime.fromJavaMillis(12345); mockProvider.setSystemTime(12345); CcsdsTime now = CcsdsTime.now(); @@ -102,8 +88,8 @@ private static class MockTimeProvider implements TimeProvider { private long time; @Override - public long getSystemTime() { - return time; + public CcsdsTime getSystemTime() { + return CcsdsTime.fromJavaMillis(time); } /** @@ -116,4 +102,5 @@ public void setSystemTime(long time) { } } + }