From bc97c74f08c5b99750fd4f770c231658decd60fb Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Wed, 17 Apr 2024 09:26:03 -0600 Subject: [PATCH 01/54] Only initialize BusinessCalendar caches when data is accessed. --- .../time/calendar/BusinessCalendar.java | 96 +++++++++---------- 1 file changed, 47 insertions(+), 49 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java index c68543e832a..be3964f2b0d 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java @@ -59,25 +59,8 @@ public InvalidDateException(final String message, final Throwable cause) { private final Map> cachedSchedules = new HashMap<>(); private final Map cachedYearData = new HashMap<>(); - - private void populateSchedules() { - LocalDate date = firstValidDate; - - while (!date.isAfter(lastValidDate)) { - - final CalendarDay s = holidays.get(date); - - if (s != null) { - cachedSchedules.put(date, s); - } else if (weekendDays.contains(date.getDayOfWeek())) { - cachedSchedules.put(date, CalendarDay.toInstant(CalendarDay.HOLIDAY, date, timeZone())); - } else { - cachedSchedules.put(date, CalendarDay.toInstant(standardBusinessDay, date, timeZone())); - } - - date = date.plusDays(1); - } - } + private final int yearCacheStart; + private final int yearCacheEnd; private static class YearData { private final Instant start; @@ -91,41 +74,34 @@ public YearData(final Instant start, final Instant end, final long businessTimeN } } - private void populateCachedYearData() { - // Only cache complete years, since incomplete years can not be fully computed. - - final int yearStart = - firstValidDate.getDayOfYear() == 1 ? firstValidDate.getYear() : firstValidDate.getYear() + 1; - final int yearEnd = ((lastValidDate.isLeapYear() && lastValidDate.getDayOfYear() == 366) - || lastValidDate.getDayOfYear() == 365) ? lastValidDate.getYear() : lastValidDate.getYear() - 1; + private YearData getYearData(final int year) { - for (int year = yearStart; year <= yearEnd; year++) { - final LocalDate startDate = LocalDate.ofYearDay(year, 1); - final LocalDate endDate = LocalDate.ofYearDay(year + 1, 1); - final ZonedDateTime start = startDate.atTime(0, 0).atZone(timeZone()); - final ZonedDateTime end = endDate.atTime(0, 0).atZone(timeZone()); + if (year < yearCacheStart || year > yearCacheEnd) { + throw new InvalidDateException("Business calendar does not contain a complete year for: year=" + year); + } - LocalDate date = startDate; - long businessTimeNanos = 0; + final YearData cached = cachedYearData.get(year); - while (date.isBefore(endDate)) { - final CalendarDay bs = this.calendarDay(date); - businessTimeNanos += bs.businessNanos(); - date = date.plusDays(1); - } - - final YearData yd = new YearData(start.toInstant(), end.toInstant(), businessTimeNanos); - cachedYearData.put(year, yd); + if (cached != null) { + return cached; } - } - private YearData getYearData(final int year) { - final YearData yd = cachedYearData.get(year); + final LocalDate startDate = LocalDate.ofYearDay(year, 1); + final LocalDate endDate = LocalDate.ofYearDay(year + 1, 1); + final ZonedDateTime start = startDate.atTime(0, 0).atZone(timeZone()); + final ZonedDateTime end = endDate.atTime(0, 0).atZone(timeZone()); - if (yd == null) { - throw new InvalidDateException("Business calendar does not contain a complete year for: year=" + year); + LocalDate date = startDate; + long businessTimeNanos = 0; + + while (date.isBefore(endDate)) { + final CalendarDay bs = this.calendarDay(date); + businessTimeNanos += bs.businessNanos(); + date = date.plusDays(1); } + final YearData yd = new YearData(start.toInstant(), end.toInstant(), businessTimeNanos); + cachedYearData.put(year, yd); return yd; } @@ -157,8 +133,12 @@ public BusinessCalendar(final String name, final String description, final ZoneI this.standardBusinessDay = Require.neqNull(standardBusinessDay, "standardBusinessDay"); this.weekendDays = Set.copyOf(Require.neqNull(weekendDays, "weekendDays")); this.holidays = Map.copyOf(Require.neqNull(holidays, "holidays")); - populateSchedules(); - populateCachedYearData(); + + // Only cache complete years, since incomplete years can not be fully computed. + yearCacheStart = + firstValidDate.getDayOfYear() == 1 ? firstValidDate.getYear() : firstValidDate.getYear() + 1; + yearCacheEnd = ((lastValidDate.isLeapYear() && lastValidDate.getDayOfYear() == 366) + || lastValidDate.getDayOfYear() == 365) ? lastValidDate.getYear() : lastValidDate.getYear() - 1; } // endregion @@ -254,7 +234,25 @@ public CalendarDay calendarDay(final LocalDate date) { + " lastValidDate=" + lastValidDate); } - return cachedSchedules.get(date); + final CalendarDay cached = cachedSchedules.get(date); + + if (cached != null) { + return cached; + } + + final CalendarDay h = holidays.get(date); + final CalendarDay s; + + if (h != null) { + s = h; + } else if (weekendDays.contains(date.getDayOfWeek())) { + s = CalendarDay.toInstant(CalendarDay.HOLIDAY, date, timeZone()); + } else { + s = CalendarDay.toInstant(standardBusinessDay, date, timeZone()); + } + + cachedSchedules.put(date, s); + return s; } /** From 4e2e878047c20a1fee436b45036ceb456f312539 Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Wed, 17 Apr 2024 15:08:03 -0600 Subject: [PATCH 02/54] Move the calendar to lazy ConcurrentHashMap initialization plus start improving caching. --- .../time/calendar/BusinessCalendar.java | 121 ++++++++++++------ .../io/deephaven/time/calendar/Calendar.java | 11 ++ 2 files changed, 90 insertions(+), 42 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java index be3964f2b0d..e026f41e5dc 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java @@ -10,6 +10,7 @@ import java.time.*; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import static io.deephaven.util.QueryConstants.*; @@ -57,35 +58,53 @@ public InvalidDateException(final String message, final Throwable cause) { // region Cache - private final Map> cachedSchedules = new HashMap<>(); - private final Map cachedYearData = new HashMap<>(); - private final int yearCacheStart; - private final int yearCacheEnd; - - private static class YearData { + private static class SummaryData { private final Instant start; private final Instant end; private final long businessTimeNanos; - - public YearData(final Instant start, final Instant end, final long businessTimeNanos) { + private final long days; + private final long businessDays; + private final long nonBusinessDays; + private final ArrayList dates; //TODO: needed? -- or add caching to Calendar? + private final ArrayList businessDates; + private final ArrayList nonBusinessDates; + + public SummaryData( + final Instant start, + final Instant end, + final long businessTimeNanos, + final long days, + final long businessDays, + final long nonBusinessDays, + final ArrayList dates, + final ArrayList businessDates, + final ArrayList nonBusinessDates) { this.start = start; this.end = end; this.businessTimeNanos = businessTimeNanos; + this.days = days; + this.businessDays = businessDays; + this.nonBusinessDays = nonBusinessDays; + this.dates = dates; + this.businessDates = businessDates; + this.nonBusinessDates = nonBusinessDates; } } - private YearData getYearData(final int year) { + private final Map> cachedSchedules = new ConcurrentHashMap<>(); + private final Map cachedYearData = new ConcurrentHashMap<>(); + private final int yearCacheStart; + private final int yearCacheEnd; - if (year < yearCacheStart || year > yearCacheEnd) { - throw new InvalidDateException("Business calendar does not contain a complete year for: year=" + year); - } + @Override + public void clearCache() { + super.clearCache(); + cachedSchedules.clear(); + cachedYearData.clear(); + } - final YearData cached = cachedYearData.get(year); - - if (cached != null) { - return cached; - } + private SummaryData computeYearSummary(final int year){ final LocalDate startDate = LocalDate.ofYearDay(year, 1); final LocalDate endDate = LocalDate.ofYearDay(year + 1, 1); final ZonedDateTime start = startDate.atTime(0, 0).atZone(timeZone()); @@ -93,16 +112,52 @@ private YearData getYearData(final int year) { LocalDate date = startDate; long businessTimeNanos = 0; + long days = 0; + long businessDays = 0; + final ArrayList dates = new ArrayList<>(); + final ArrayList businessDates = new ArrayList<>(); + final ArrayList nonBusinessDates = new ArrayList<>(); while (date.isBefore(endDate)) { - final CalendarDay bs = this.calendarDay(date); + final CalendarDay bs = calendarDay(date); + final boolean ibd = bs.isBusinessDay(); + days += 1; + businessDays += ibd ? 1 : 0; businessTimeNanos += bs.businessNanos(); + dates.add(date); + + if(ibd) { + businessDates.add(date); + } else { + nonBusinessDates.add(date); + } + date = date.plusDays(1); } - final YearData yd = new YearData(start.toInstant(), end.toInstant(), businessTimeNanos); - cachedYearData.put(year, yd); - return yd; + return new SummaryData(start.toInstant(), end.toInstant(), businessTimeNanos, days, businessDays, days - businessDays, dates, businessDates, nonBusinessDates); + } + + private SummaryData getYearData(final int year) { + + if (year < yearCacheStart || year > yearCacheEnd) { + throw new InvalidDateException("Business calendar does not contain a complete year for: year=" + year); + } + + return cachedYearData.computeIfAbsent(year, this::computeYearSummary); + } + + + private CalendarDay computeCalendarDay(final LocalDate date) { + final CalendarDay h = holidays.get(date); + + if (h != null) { + return h; + } else if (weekendDays.contains(date.getDayOfWeek())) { + return CalendarDay.toInstant(CalendarDay.HOLIDAY, date, timeZone()); + } else { + return CalendarDay.toInstant(standardBusinessDay, date, timeZone()); + } } // endregion @@ -234,25 +289,7 @@ public CalendarDay calendarDay(final LocalDate date) { + " lastValidDate=" + lastValidDate); } - final CalendarDay cached = cachedSchedules.get(date); - - if (cached != null) { - return cached; - } - - final CalendarDay h = holidays.get(date); - final CalendarDay s; - - if (h != null) { - s = h; - } else if (weekendDays.contains(date.getDayOfWeek())) { - s = CalendarDay.toInstant(CalendarDay.HOLIDAY, date, timeZone()); - } else { - s = CalendarDay.toInstant(standardBusinessDay, date, timeZone()); - } - - cachedSchedules.put(date, s); - return s; + return cachedSchedules.computeIfAbsent(date, this::computeCalendarDay); } /** @@ -1679,8 +1716,8 @@ public double diffBusinessYears(final Instant start, final Instant end) { return (double) diffBusinessNanos(start, end) / (double) getYearData(yearStart).businessTimeNanos; } - final YearData yearDataStart = getYearData(yearStart); - final YearData yearDataEnd = getYearData(yearEnd); + final SummaryData yearDataStart = getYearData(yearStart); + final SummaryData yearDataEnd = getYearData(yearEnd); return (double) diffBusinessNanos(start, yearDataStart.end) / (double) yearDataStart.businessTimeNanos + (double) diffBusinessNanos(yearDataEnd.start, end) / (double) yearDataEnd.businessTimeNanos + diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java index 7f73bd99cb0..50d8d2876c1 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java @@ -48,6 +48,17 @@ public class Calendar { // endregion + // region Cache + + /** + * Clears the cache. This should not generally be used and is provided for benchmarking. + */ + public void clearCache() { + //TODO: cache.clear(); + } + + // endregion + // region Getters /** From f3fadc9107d9fd0809bb9d2fe5f35206fd50d44a Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Thu, 18 Apr 2024 14:01:22 -0600 Subject: [PATCH 03/54] Added a thread-safe lazily initialized summary data cache by month and year. --- .../time/calendar/YearMonthSummaryCache.java | 170 +++++++++++ .../calendar/TestYearMonthSummaryCache.java | 284 ++++++++++++++++++ 2 files changed, 454 insertions(+) create mode 100644 engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java create mode 100644 engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java new file mode 100644 index 00000000000..49b87e7e202 --- /dev/null +++ b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java @@ -0,0 +1,170 @@ +package io.deephaven.time.calendar; + +import java.time.LocalDate; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +/** + * A thread-safe lazily initialized cache for year and month summaries. + * + * @param the type of the summary + */ +class YearMonthSummaryCache { + + private final Map yearCache = new ConcurrentHashMap<>(); + private final Map monthCache = new ConcurrentHashMap<>(); + + private final Function computeMonthSummary; + private final Function computeYearSummary; + + /** + * Creates a new cache. + * + * @param computeMonthSummary the function to compute a month summary + * @param computeYearSummary the function to compute a year summary + */ + YearMonthSummaryCache(Function computeMonthSummary, Function computeYearSummary) { + this.computeMonthSummary = computeMonthSummary; + this.computeYearSummary = computeYearSummary; + } + + /** + * Clears the cache. + */ + void clear() { + yearCache.clear(); + monthCache.clear(); + } + + /** + * Gets the month summary for the specified year and month. + * + * @param yearMonth the year and month + * @return the month summary + */ + T getMonthSummary(int yearMonth) { + return monthCache.computeIfAbsent(yearMonth, computeMonthSummary); + } + + /** + * Gets the month summary for the specified year and month. + * + * @param year the year + * @param month the month + * @return the month summary + */ + T getMonthSummary(int year, int month) { + return getMonthSummary(year * 100 + month); + } + + /** + * Gets the year summary for the specified year. + * + * @param year the year + * @return the year summary + */ + T getYearSummary(int year) { + return yearCache.computeIfAbsent(year, computeYearSummary); + } + + private class YearMonthSummaryIterator implements Iterator { + + private int currentYear; + private int currentMonth; + private int currentYearMonth; + private int finalYear; + private int finalMonth; + final private int finalYearMonth; + + YearMonthSummaryIterator(LocalDate start, LocalDate end) { + int startYear = start.getYear(); + int startMonth = start.getMonthValue(); + int endYear = end.getYear(); + int endMonth = end.getMonthValue(); + + currentMonth = startMonth; + currentYear = startYear; + + if(start.getDayOfMonth() != 1) { + incrementCurrentByMonth(); + } + + currentYearMonth = currentYear * 100 + currentMonth; + + final LocalDate endPlus1 = end.plusDays(1); + final int endPlus1Month = endPlus1.getMonthValue(); + + finalMonth = endMonth; + finalYear = endYear; + + if(endPlus1Month == endMonth) { + if(finalMonth == 1) { + finalMonth = 12; + finalYear = finalYear - 1; + } else { + finalMonth = finalMonth - 1; + } + } + + finalYearMonth = finalYear * 100 + finalMonth; + } + + private void incrementCurrentByMonth() { + if(currentMonth == 12) { + currentMonth = 1; + currentYear += 1; + } else { + currentMonth = currentMonth + 1; + } + + currentYearMonth = currentYear * 100 + currentMonth; + } + + private void incrementCurrentByYear() { + currentYear++; + currentYearMonth = currentYear * 100 + currentMonth; + } + + @Override + public boolean hasNext() { + return currentYearMonth <= finalYearMonth; + } + + @Override + public T next() { + final T val; + + if(currentMonth == 1 && (currentYear != finalYear || finalMonth == 12)) { + val = getYearSummary(currentYear); + incrementCurrentByYear(); + } else { + val = getMonthSummary(currentYear * 100 + currentMonth); + incrementCurrentByMonth(); + } + + return val; + } + } + + /** + * Gets an iterator over the summaries for the specified range. The returned iterator will include the start + * date if {@code startInclusive} is true, and the end date if {@code endInclusive} is true. + * If the start date is after the end date, the iterator will be empty. + *

+ * The iterator will return summaries in chronological order, + * and these summaries can be a mix of month and year summaries. + * Dates not represented by complete summaries will be skipped (e.g. partial months). + * + * @param start the start date + * @param end the end date + * @param startInclusive whether the start date is inclusive + * @param endInclusive whether the end date is inclusive + * @return the iterator + */ + Iterator iterator(final LocalDate start, final LocalDate end, + final boolean startInclusive, final boolean endInclusive) { + return new YearMonthSummaryIterator(startInclusive ? start : start.plusDays(1), endInclusive ? end : end.minusDays(1)); + } +} diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java new file mode 100644 index 00000000000..a6499ed1360 --- /dev/null +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java @@ -0,0 +1,284 @@ +package io.deephaven.time.calendar; + +import io.deephaven.base.testing.BaseArrayTestCase; + +import java.time.LocalDate; +import java.util.Iterator; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Function; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +@SuppressWarnings({"DataFlowIssue", "ConstantValue"}) +public class TestYearMonthSummaryCache extends BaseArrayTestCase { + + public void testGetters() { + final int[] monthCount = new int[]{0}; + final int[] yearCount = new int[]{0}; + + final Function monthSummary = i -> { + monthCount[0]++; + return "month" + i; + }; + + final Function yearSummary = i -> { + yearCount[0]++; + return "year" + i; + }; + + final YearMonthSummaryCache cache = new YearMonthSummaryCache<>(monthSummary, yearSummary); + assertEquals("month202101", cache.getMonthSummary(202101)); + assertEquals(1, monthCount[0]); + assertEquals(0, yearCount[0]); + assertEquals("year2021", cache.getYearSummary(2021)); + assertEquals(1, monthCount[0]); + assertEquals(1, yearCount[0]); + assertEquals("month202101", cache.getMonthSummary(202101)); + assertEquals(1, monthCount[0]); + assertEquals(1, yearCount[0]); + assertEquals("year2021", cache.getYearSummary(2021)); + assertEquals(1, monthCount[0]); + assertEquals(1, yearCount[0]); + + assertEquals("month202102", cache.getMonthSummary(202102)); + assertEquals(2, monthCount[0]); + assertEquals(1, yearCount[0]); + assertEquals("year2022", cache.getYearSummary(2022)); + assertEquals(2, monthCount[0]); + assertEquals(2, yearCount[0]); + + cache.clear(); + + assertEquals("month202101", cache.getMonthSummary(202101)); + assertEquals(3, monthCount[0]); + assertEquals(2, yearCount[0]); + assertEquals("year2021", cache.getYearSummary(2021)); + assertEquals(3, monthCount[0]); + assertEquals(3, yearCount[0]); + assertEquals("month202101", cache.getMonthSummary(202101)); + assertEquals(3, monthCount[0]); + assertEquals(3, yearCount[0]); + assertEquals("year2021", cache.getYearSummary(2021)); + assertEquals(3, monthCount[0]); + assertEquals(3, yearCount[0]); + + assertEquals("month202102", cache.getMonthSummary(202102)); + assertEquals(4, monthCount[0]); + assertEquals(3, yearCount[0]); + assertEquals("year2022", cache.getYearSummary(2022)); + assertEquals(4, monthCount[0]); + assertEquals(4, yearCount[0]); + + assertEquals(cache.getMonthSummary(202101), cache.getMonthSummary(2021, 1)); + } + + private static Stream + iteratorToStream(Iterator iterator) { + Spliterator spliterator = Spliterators.spliteratorUnknownSize(iterator, 0); + return StreamSupport.stream(spliterator, false); + } + + public void testIteratorInclusive() { + final YearMonthSummaryCache cache = new YearMonthSummaryCache<>(i -> "month" + i, i -> "year" + i); + final boolean startInclusive = true; + final boolean endInclusive = true; + + // end before start + LocalDate start = LocalDate.of(2021, 1, 2); + LocalDate end = LocalDate.of(2021, 1, 1); + String[] target = {}; + String[] actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + assertEquals(target, actual); + + // same month + start = LocalDate.of(2021, 1, 1); + end = LocalDate.of(2021, 1, 11); + target = new String[]{}; + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + assertEquals(target, actual); + + // adjacent partial months + start = LocalDate.of(2021, 1, 3); + end = LocalDate.of(2021, 2, 11); + target = new String[]{}; + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + assertEquals(target, actual); + + // full month + partial month + start = LocalDate.of(2021, 1, 1); + end = LocalDate.of(2021, 2, 11); + target = new String[]{"month202101"}; + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + assertEquals(target, actual); + + // full month + few days + start = LocalDate.of(2020, 12, 12); + end = LocalDate.of(2021, 2, 11); + target = new String[]{"month202101"}; + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + assertEquals(target, actual); + + // multiple months + few days + start = LocalDate.of(2020, 11, 12); + end = LocalDate.of(2021, 4, 11); + target = new String[]{"month202012", "month202101", "month202102", "month202103"}; + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + assertEquals(target, actual); + + // partial month + full month + start = LocalDate.of(2021, 1, 3); + end = LocalDate.of(2021, 2, 28); + target = new String[]{"month202102"}; + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + assertEquals(target, actual); + + // full year + start = LocalDate.of(2021, 1, 1); + end = LocalDate.of(2021, 12, 31); + target = new String[]{"year2021"}; + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + assertEquals(target, actual); + + // full year + few days + start = LocalDate.of(2020, 12, 11); + end = LocalDate.of(2022, 1, 3); + target = new String[]{"year2021"}; + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + assertEquals(target, actual); + + // multiple years + few days + start = LocalDate.of(2018, 12, 11); + end = LocalDate.of(2022, 1, 3); + target = new String[]{"year2019", "year2020", "year2021"}; + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + assertEquals(target, actual); + + // mixed + start = LocalDate.of(2018, 10, 11); + end = LocalDate.of(2022, 3, 3); + target = new String[]{"month201811", "month201812", "year2019", "year2020", "year2021", "month202201", "month202202"}; + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + assertEquals(target, actual); + } + + public void testIteratorExclusiveInclusive() { + final YearMonthSummaryCache cache = new YearMonthSummaryCache<>(i -> "month" + i, i -> "year" + i); + + // start and end of month + + LocalDate start = LocalDate.of(2021, 12, 1); + LocalDate end = LocalDate.of(2021, 12, 31); + + boolean startInclusive = true; + boolean endInclusive = true; + String[] target = new String[]{"month202112"}; + String[] actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + assertEquals(target, actual); + + startInclusive = false; + endInclusive = true; + target = new String[]{}; + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + assertEquals(target, actual); + + startInclusive = true; + endInclusive = false; + target = new String[]{}; + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + assertEquals(target, actual); + + startInclusive = false; + endInclusive = false; + target = new String[]{}; + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + assertEquals(target, actual); + + // day before start of month + + start = LocalDate.of(2021, 11, 30); + end = LocalDate.of(2021, 12, 31); + + startInclusive = true; + endInclusive = true; + target = new String[]{"month202112"}; + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + assertEquals(target, actual); + + startInclusive = false; + endInclusive = true; + target = new String[]{"month202112"}; + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + assertEquals(target, actual); + + startInclusive = true; + endInclusive = false; + target = new String[]{}; + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + assertEquals(target, actual); + + startInclusive = false; + endInclusive = false; + target = new String[]{}; + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + assertEquals(target, actual); + + // day after end of month + + start = LocalDate.of(2021, 12, 1); + end = LocalDate.of(2022, 1, 1); + + startInclusive = true; + endInclusive = true; + target = new String[]{"month202112"}; + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + assertEquals(target, actual); + + startInclusive = false; + endInclusive = true; + target = new String[]{}; + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + assertEquals(target, actual); + + startInclusive = true; + endInclusive = false; + target = new String[]{"month202112"}; + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + assertEquals(target, actual); + + startInclusive = false; + endInclusive = false; + target = new String[]{}; + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + assertEquals(target, actual); + + // day before and after end of month + + start = LocalDate.of(2021, 11, 30); + end = LocalDate.of(2022, 1, 1); + + startInclusive = true; + endInclusive = true; + target = new String[]{"month202112"}; + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + assertEquals(target, actual); + + startInclusive = false; + endInclusive = true; + target = new String[]{"month202112"}; + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + assertEquals(target, actual); + + startInclusive = true; + endInclusive = false; + target = new String[]{"month202112"}; + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + assertEquals(target, actual); + + startInclusive = false; + endInclusive = false; + target = new String[]{"month202112"}; + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + assertEquals(target, actual); + } +} From d39dec7f447b5fe6d6aff6e3019b395eb816be87 Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Thu, 18 Apr 2024 14:05:15 -0600 Subject: [PATCH 04/54] Using advanced caching in the BusinessCalendar v1. --- .../time/calendar/BusinessCalendar.java | 269 +++++++++++++----- .../time/calendar/TestBusinessCalendar.java | 73 ++++- 2 files changed, 270 insertions(+), 72 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java index e026f41e5dc..38fc7090ef1 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java @@ -59,40 +59,41 @@ public InvalidDateException(final String message, final Throwable cause) { // region Cache private static class SummaryData { - private final Instant start; - private final Instant end; + private final Instant startInstant; + private final LocalDate startDate; + private final Instant endInstant; + private final LocalDate endDate; private final long businessTimeNanos; - private final long days; - private final long businessDays; - private final long nonBusinessDays; - private final ArrayList dates; //TODO: needed? -- or add caching to Calendar? + private final int businessDays; + private final int nonBusinessDays; private final ArrayList businessDates; private final ArrayList nonBusinessDates; public SummaryData( - final Instant start, - final Instant end, + final Instant startInstant, + final LocalDate startDate, + final Instant endInstant, + final LocalDate endDate, final long businessTimeNanos, - final long days, - final long businessDays, - final long nonBusinessDays, - final ArrayList dates, + final int days, + final int businessDays, + final int nonBusinessDays, final ArrayList businessDates, final ArrayList nonBusinessDates) { - this.start = start; - this.end = end; + this.startInstant = startInstant; + this.startDate = startDate; + this.endInstant = endInstant; + this.endDate = endDate; this.businessTimeNanos = businessTimeNanos; - this.days = days; this.businessDays = businessDays; this.nonBusinessDays = nonBusinessDays; - this.dates = dates; this.businessDates = businessDates; this.nonBusinessDates = nonBusinessDates; } } private final Map> cachedSchedules = new ConcurrentHashMap<>(); - private final Map cachedYearData = new ConcurrentHashMap<>(); + private final YearMonthSummaryCache summaryCache = new YearMonthSummaryCache<>(this::computeMonthSummary, this::computeYearSummary); private final int yearCacheStart; private final int yearCacheEnd; @@ -100,21 +101,18 @@ public SummaryData( public void clearCache() { super.clearCache(); cachedSchedules.clear(); - cachedYearData.clear(); + summaryCache.clear(); } - - private SummaryData computeYearSummary(final int year){ - final LocalDate startDate = LocalDate.ofYearDay(year, 1); - final LocalDate endDate = LocalDate.ofYearDay(year + 1, 1); + private SummaryData summarize(final LocalDate startDate, final LocalDate endDate) { final ZonedDateTime start = startDate.atTime(0, 0).atZone(timeZone()); final ZonedDateTime end = endDate.atTime(0, 0).atZone(timeZone()); LocalDate date = startDate; long businessTimeNanos = 0; - long days = 0; - long businessDays = 0; - final ArrayList dates = new ArrayList<>(); + int days = 0; + int businessDays = 0; + int nonBusinessDays = 0; final ArrayList businessDates = new ArrayList<>(); final ArrayList nonBusinessDates = new ArrayList<>(); @@ -123,8 +121,8 @@ private SummaryData computeYearSummary(final int year){ final boolean ibd = bs.isBusinessDay(); days += 1; businessDays += ibd ? 1 : 0; + nonBusinessDays += ibd ? 0 : 1; businessTimeNanos += bs.businessNanos(); - dates.add(date); if(ibd) { businessDates.add(date); @@ -135,16 +133,40 @@ private SummaryData computeYearSummary(final int year){ date = date.plusDays(1); } - return new SummaryData(start.toInstant(), end.toInstant(), businessTimeNanos, days, businessDays, days - businessDays, dates, businessDates, nonBusinessDates); + return new SummaryData( + start.toInstant(), + start.toLocalDate(), + end.toInstant(), + end.toLocalDate(), + businessTimeNanos, + days, + businessDays, + nonBusinessDays, + businessDates, + nonBusinessDates); } - private SummaryData getYearData(final int year) { + private SummaryData computeMonthSummary(final int yearMonth) { + final int year = yearMonth / 100; + final int month = yearMonth % 100; + final LocalDate startDate = LocalDate.of(year, month, 1); + final LocalDate endDate = startDate.plusMonths(1); //todo end date is exclusive --- check code + return summarize(startDate, endDate); + } + + private SummaryData computeYearSummary(final int year){ + final LocalDate startDate = LocalDate.ofYearDay(year, 1); + final LocalDate endDate = LocalDate.ofYearDay(year + 1, 1); + return summarize(startDate, endDate); + } + + private SummaryData getYearSummary(final int year) { if (year < yearCacheStart || year > yearCacheEnd) { throw new InvalidDateException("Business calendar does not contain a complete year for: year=" + year); } - return cachedYearData.computeIfAbsent(year, this::computeYearSummary); + return summaryCache.getYearSummary(year); } @@ -924,31 +946,61 @@ public double fractionBusinessDayRemaining() { // region Ranges - /** - * Returns the number of business dates in a given range. - * - * @param start start of a time range - * @param end end of a time range - * @param startInclusive true to include {@code start} in the result; false to exclude {@code start} - * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} - * @return number of business dates between {@code start} and {@code end}. {@link QueryConstants#NULL_INT} if any - * input is {@code null}. - * @throws InvalidDateException if the dates are not in the valid range - */ + public int numberBusinessDatesInternal(final LocalDate start, final LocalDate end, final boolean startInclusive, + final boolean endInclusive) { + int days = 0; + + for (LocalDate day = start; !day.isAfter(end); day = day.plusDays(1)) { + final boolean skip = (!startInclusive && day.equals(start)) || (!endInclusive && day.equals(end)); + + if (!skip && isBusinessDay(day)) { + days++; + } + } + + return days; + } + + /** + * Returns the number of business dates in a given range. + * + * @param start start of a time range + * @param end end of a time range + * @param startInclusive true to include {@code start} in the result; false to exclude {@code start} + * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} + * @return number of business dates between {@code start} and {@code end}. {@link QueryConstants#NULL_INT} if any + * input is {@code null}. + * @throws InvalidDateException if the dates are not in the valid range + */ public int numberBusinessDates(final LocalDate start, final LocalDate end, final boolean startInclusive, final boolean endInclusive) { if (start == null || end == null) { return NULL_INT; } + if (start.isAfter(end)) { + return 0; + } + + SummaryData summaryFirst = null; + SummaryData summary = null; int days = 0; - for (LocalDate day = start; !day.isAfter(end); day = day.plusDays(1)) { - final boolean skip = (!startInclusive && day.equals(start)) || (!endInclusive && day.equals(end)); + for (Iterator it = summaryCache.iterator(start, end, startInclusive, endInclusive); it.hasNext(); ) { + summary = it.next(); - if (!skip && isBusinessDay(day)) { - days++; + if(summaryFirst == null) { + summaryFirst = summary; } + + days += summary.businessDays; + } + + if(summaryFirst == null){ + return numberBusinessDatesInternal(start, end, startInclusive, endInclusive); + } else { + days += numberBusinessDatesInternal(start, summaryFirst.startDate, startInclusive, false); + days += numberBusinessDatesInternal(summary.endDate, end, false, endInclusive); } return days; @@ -1209,6 +1261,17 @@ public int numberNonBusinessDates(final Instant start, final Instant end) { return numberNonBusinessDates(start, end, true, true); } + private void businessDatesInternal(final ArrayList result, final LocalDate start, final LocalDate end, final boolean startInclusive, + final boolean endInclusive) { + for (LocalDate day = start; !day.isAfter(end); day = day.plusDays(1)) { + final boolean skip = (!startInclusive && day.equals(start)) || (!endInclusive && day.equals(end)); + + if (!skip && isBusinessDay(day)) { + result.add(day); + } + } + } + /** * Returns the business dates in a given range. * @@ -1225,14 +1288,30 @@ public LocalDate[] businessDates(final LocalDate start, final LocalDate end, fin return null; } - List dateList = new ArrayList<>(); + if (start.isAfter(end)) { + return new LocalDate[0]; + } - for (LocalDate day = start; !day.isAfter(end); day = day.plusDays(1)) { - final boolean skip = (!startInclusive && day.equals(start)) || (!endInclusive && day.equals(end)); + final ArrayList dateList = new ArrayList<>(); - if (!skip && isBusinessDay(day)) { - dateList.add(day); + SummaryData summaryFirst = null; + SummaryData summary = null; + + for (Iterator it = summaryCache.iterator(start, end, startInclusive, endInclusive); it.hasNext(); ) { + summary = it.next(); + + if(summaryFirst == null) { + summaryFirst = summary; + businessDatesInternal(dateList, start, summaryFirst.startDate, startInclusive, false); } + + dateList.addAll(summary.businessDates); + } + + if(summaryFirst == null){ + businessDatesInternal(dateList, start, end, startInclusive, endInclusive); + } else { + businessDatesInternal(dateList, summary.endDate, end, false, endInclusive); } return dateList.toArray(new LocalDate[0]); @@ -1354,6 +1433,17 @@ public LocalDate[] businessDates(final Instant start, final Instant end) { return businessDates(start, end, true, true); } + private void nonBusinessDatesInternal(final ArrayList result, final LocalDate start, final LocalDate end, final boolean startInclusive, + final boolean endInclusive) { + for (LocalDate day = start; !day.isAfter(end); day = day.plusDays(1)) { + final boolean skip = (!startInclusive && day.equals(start)) || (!endInclusive && day.equals(end)); + + if (!skip && !isBusinessDay(day)) { + result.add(day); + } + } + } + /** * Returns the non-business dates in a given range. * @@ -1370,14 +1460,30 @@ public LocalDate[] nonBusinessDates(final LocalDate start, final LocalDate end, return null; } - List dateList = new ArrayList<>(); + if (start.isAfter(end)) { + return new LocalDate[0]; + } - for (LocalDate day = start; !day.isAfter(end); day = day.plusDays(1)) { - final boolean skip = (!startInclusive && day.equals(start)) || (!endInclusive && day.equals(end)); + final ArrayList dateList = new ArrayList<>(); - if (!skip && !isBusinessDay(day)) { - dateList.add(day); + SummaryData summaryFirst = null; + SummaryData summary = null; + + for (Iterator it = summaryCache.iterator(start, end, startInclusive, endInclusive); it.hasNext(); ) { + summary = it.next(); + + if(summaryFirst == null) { + summaryFirst = summary; + nonBusinessDatesInternal(dateList, start, summaryFirst.startDate, startInclusive, false); } + + dateList.addAll(summary.nonBusinessDates); + } + + if(summaryFirst == null){ + nonBusinessDatesInternal(dateList, start, end, startInclusive, endInclusive); + } else { + nonBusinessDatesInternal(dateList, summary.endDate, end, false, endInclusive); } return dateList.toArray(new LocalDate[0]); @@ -1503,6 +1609,24 @@ public LocalDate[] nonBusinessDates(final Instant start, final Instant end) { // region Differences + public long diffBusinessNanosInternal(final Instant start, final LocalDate startDate, final Instant end, final LocalDate endDate) { + + if (startDate.equals(endDate)) { + final CalendarDay schedule = this.calendarDay(startDate); + return schedule.businessNanosElapsed(end) - schedule.businessNanosElapsed(start); + } + + long rst = this.calendarDay(startDate).businessNanosRemaining(start) + + this.calendarDay(endDate).businessNanosElapsed(end); + + for (LocalDate d = startDate.plusDays(1); d.isBefore(endDate); d = d.plusDays(1)) { + rst += this.calendarDay(d).businessNanos(); + } + + return rst; + } + + /** * Returns the amount of business time in nanoseconds between two times. * @@ -1527,19 +1651,28 @@ public long diffBusinessNanos(final Instant start, final Instant end) { assert startDate != null; assert endDate != null; - if (startDate.equals(endDate)) { - final CalendarDay schedule = this.calendarDay(startDate); - return schedule.businessNanosElapsed(end) - schedule.businessNanosElapsed(start); - } + SummaryData summaryFirst = null; + SummaryData summary = null; + long nanos = 0; - long rst = this.calendarDay(startDate).businessNanosRemaining(start) - + this.calendarDay(endDate).businessNanosElapsed(end); + for (Iterator it = summaryCache.iterator(startDate, endDate, false, false); it.hasNext(); ) { + summary = it.next(); - for (LocalDate d = startDate.plusDays(1); d.isBefore(endDate); d = d.plusDays(1)) { - rst += this.calendarDay(d).businessNanos(); + if(summaryFirst == null) { + summaryFirst = summary; + } + + nanos += summary.businessTimeNanos; } - return rst; + if(summaryFirst == null){ + return diffBusinessNanosInternal(start, startDate, end, endDate); + } else { + nanos += diffBusinessNanosInternal(start, startDate, summaryFirst.startInstant, summaryFirst.startDate); + nanos += diffBusinessNanosInternal(summary.endInstant, summary.endDate, end, endDate); + } + + return nanos; } /** @@ -1713,14 +1846,14 @@ public double diffBusinessYears(final Instant start, final Instant end) { final int yearEnd = DateTimeUtils.year(end, timeZone()); if (yearStart == yearEnd) { - return (double) diffBusinessNanos(start, end) / (double) getYearData(yearStart).businessTimeNanos; + return (double) diffBusinessNanos(start, end) / (double) getYearSummary(yearStart).businessTimeNanos; } - final SummaryData yearDataStart = getYearData(yearStart); - final SummaryData yearDataEnd = getYearData(yearEnd); + final SummaryData yearDataStart = getYearSummary(yearStart); + final SummaryData yearDataEnd = getYearSummary(yearEnd); - return (double) diffBusinessNanos(start, yearDataStart.end) / (double) yearDataStart.businessTimeNanos + - (double) diffBusinessNanos(yearDataEnd.start, end) / (double) yearDataEnd.businessTimeNanos + + return (double) diffBusinessNanos(start, yearDataStart.endInstant) / (double) yearDataStart.businessTimeNanos + + (double) diffBusinessNanos(yearDataEnd.startInstant, end) / (double) yearDataEnd.businessTimeNanos + yearEnd - yearStart - 1; } diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestBusinessCalendar.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestBusinessCalendar.java index 92da0b7eacf..bec390fcc9c 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestBusinessCalendar.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestBusinessCalendar.java @@ -7,10 +7,7 @@ import java.time.*; import java.time.temporal.ChronoUnit; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; +import java.util.*; import static io.deephaven.util.QueryConstants.*; @@ -692,6 +689,22 @@ public void testBusinessDates() { assertNull(bCalendar.businessDates(start.atTime(1, 2).atZone(timeZone).toInstant(), null, true, true)); assertNull(bCalendar.businessDates(null, end.atTime(1, 2).atZone(timeZone), true, true)); assertNull(bCalendar.businessDates(start.atTime(1, 2).atZone(timeZone), null, true, true)); + + // end before start + assertEquals(new LocalDate[0], bCalendar.businessDates(end, start)); + + // long span of dates + final LocalDate startLong = LocalDate.of(2019, 2, 1); + final LocalDate endLong = LocalDate.of(2023, 12, 31); + final ArrayList targetLong = new ArrayList<>(); + + for (LocalDate d = startLong; !d.isAfter(endLong); d = d.plusDays(1)) { + if (bCalendar.isBusinessDay(d)) { + targetLong.add(d); + } + } + + assertEquals(targetLong.toArray(LocalDate[]::new), bCalendar.businessDates(startLong, endLong)); } public void testNumberBusinessDates() { @@ -758,6 +771,22 @@ public void testNumberBusinessDates() { bCalendar.numberBusinessDates(start.atTime(1, 2).atZone(timeZone).toInstant(), null, true, true)); assertEquals(NULL_INT, bCalendar.numberBusinessDates(null, end.atTime(1, 2).atZone(timeZone), true, true)); assertEquals(NULL_INT, bCalendar.numberBusinessDates(start.atTime(1, 2).atZone(timeZone), null, true, true)); + + // end before start + assertEquals(0, bCalendar.numberBusinessDates(end, start)); + + // long span of dates + final LocalDate startLong = LocalDate.of(2019, 2, 1); + final LocalDate endLong = LocalDate.of(2023, 12, 31); + final ArrayList targetLong = new ArrayList<>(); + + for (LocalDate d = startLong; !d.isAfter(endLong); d = d.plusDays(1)) { + if (bCalendar.isBusinessDay(d)) { + targetLong.add(d); + } + } + + assertEquals(targetLong.size(), bCalendar.numberBusinessDates(startLong, endLong)); } public void testNonBusinessDates() { @@ -840,6 +869,22 @@ public void testNonBusinessDates() { assertNull(bCalendar.nonBusinessDates(start.atTime(1, 2).atZone(timeZone).toInstant(), null, true, true)); assertNull(bCalendar.nonBusinessDates(null, end.atTime(1, 2).atZone(timeZone), true, true)); assertNull(bCalendar.nonBusinessDates(start.atTime(1, 2).atZone(timeZone), null, true, true)); + + // end before start + assertEquals(new LocalDate[0], bCalendar.nonBusinessDates(end, start)); + + // long span of dates + final LocalDate startLong = LocalDate.of(2019, 2, 1); + final LocalDate endLong = LocalDate.of(2023, 12, 31); + final ArrayList targetLong = new ArrayList<>(); + + for (LocalDate d = startLong; !d.isAfter(endLong); d = d.plusDays(1)) { + if (!bCalendar.isBusinessDay(d)) { + targetLong.add(d); + } + } + + assertEquals(targetLong.toArray(LocalDate[]::new), bCalendar.nonBusinessDates(startLong, endLong)); } public void testNumberNonBusinessDates() { @@ -1547,4 +1592,24 @@ public void testPastNonBusinessDate() { assertNull(bCalendar.pastNonBusinessDate(NULL_INT)); } + public void testClearCache() { + final LocalDate start = LocalDate.of(2023, 7, 3); + final LocalDate end = LocalDate.of(2025, 7, 10); + + final CalendarDay v1 = bCalendar.calendarDay(); + final CalendarDay v2 = bCalendar.calendarDay(); + assertEquals(v1, v2); + final int i1 = bCalendar.numberBusinessDates(start, end); + final int i2 = bCalendar.numberBusinessDates(start, end); + assertEquals(i1, i2); + + bCalendar.clearCache(); + + final CalendarDay v3 = bCalendar.calendarDay(); + final CalendarDay v4 = bCalendar.calendarDay(); + assertEquals(v3, v4); + final int i3 = bCalendar.numberBusinessDates(start, end); + final int i4 = bCalendar.numberBusinessDates(start, end); + assertEquals(i3, i4); + } } From 52a452175b8c86f6cf7f1d0eae19aa35dcff0c8c Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Thu, 18 Apr 2024 14:07:52 -0600 Subject: [PATCH 05/54] Fix javadoc warnings. --- .../time/calendar/BusinessCalendar.java | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java index 38fc7090ef1..70901c67ada 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java @@ -407,7 +407,7 @@ public boolean isBusinessDay(final String date) { /** * Is the time on a business day? - * + *

* As long as the time occurs on a business day, it is considered a business day. The time does not have to be * within the business day schedule. To determine if a time is within the business day schedule, use * {@link #isBusinessTime(ZonedDateTime)}. @@ -426,7 +426,7 @@ public boolean isBusinessDay(final ZonedDateTime time) { /** * Is the time on a business day? - * + *

* As long as the time occurs on a business day, it is considered a business day. The time does not have to be * within the business day schedule. To determine if a time is within the business day schedule, use * {@link #isBusinessTime(Instant)}. @@ -487,7 +487,7 @@ boolean isLastBusinessDayOfMonth(final LocalDate date) { /** * Is the time on the last business day of the month? - * + *

* As long as the time occurs on a business day, it is considered a business day. The time does not have to be * within the business day schedule. * @@ -507,7 +507,7 @@ public boolean isLastBusinessDayOfMonth(final ZonedDateTime time) { /** * Is the time on the last business day of the month? - * + *

* As long as the time occurs on a business day, it is considered a business day. The time does not have to be * within the business day schedule. * @@ -570,7 +570,7 @@ public boolean isLastBusinessDayOfWeek(final LocalDate date) { /** * Is the time on the last business day of the week? - * + *

* As long as the time occurs on a business day, it is considered a business day. The time does not have to be * within the business day schedule. * @@ -589,7 +589,7 @@ public boolean isLastBusinessDayOfWeek(final ZonedDateTime time) { /** * Is the time on the last business day of the week? - * + *

* As long as the time occurs on a business day, it is considered a business day. The time does not have to be * within the business day schedule. * @@ -652,7 +652,7 @@ boolean isLastBusinessDayOfYear(final LocalDate date) { /** * Is the time on the last business day of the year? - * + *

* As long as the time occurs on a business day, it is considered a business day. The time does not have to be * within the business day schedule. * @@ -671,7 +671,7 @@ public boolean isLastBusinessDayOfYear(final ZonedDateTime time) { /** * Is the time on the last business day of the year? - * + *

* As long as the time occurs on a business day, it is considered a business day. The time does not have to be * within the business day schedule. * @@ -708,7 +708,7 @@ boolean isLastBusinessDayOfYear(final String date) { /** * Is the current date the last business day of the year? - * + *

* As long as the current time occurs on a business day, it is considered a business day. The time does not have to * be within the business day schedule. * @@ -1932,7 +1932,7 @@ public String plusBusinessDays(final String date, final int days) { /** * Adds a specified number of business days to an input time. Adding negative days is equivalent to subtracting * days. - * + *

* Day additions are not always 24 hours. The resultant time will have the same local time as the input time, as * determined by the calendar's time zone. This accounts for Daylight Savings Time. For example, 2023-11-05 has a * daylight savings time adjustment, so '2023-11-04T14:00 ET' plus 1 day will result in '2023-11-05T15:00 ET', which @@ -1956,12 +1956,12 @@ public Instant plusBusinessDays(final Instant time, final int days) { /** * Adds a specified number of business days to an input time. Adding negative days is equivalent to subtracting * days. - * + *

* Day additions are not always 24 hours. The resultant time will have the same local time as the input time, as * determined by the calendar's time zone. This accounts for Daylight Savings Time. For example, 2023-11-05 has a * daylight savings time adjustment, so '2023-11-04T14:00 ET' plus 1 day will result in '2023-11-05T15:00 ET', which * is a 25-hour difference. - * + *

* The resultant time will have the same time zone as the calendar. This could be different than the time zone of * the input {@link ZonedDateTime}. * @@ -2024,7 +2024,7 @@ public String minusBusinessDays(final String date, final int days) { /** * Subtracts a specified number of business days from an input time. Subtracting negative days is equivalent to * adding days. - * + *

* Day subtractions are not always 24 hours. The resultant time will have the same local time as the input time, as * determined by the calendar's time zone. This accounts for Daylight Savings Time. For example, 2023-11-05 has a * daylight savings time adjustment, so '2023-11-04T14:00 ET' plus 1 day will result in '2023-11-05T15:00 ET', which @@ -2047,12 +2047,12 @@ public Instant minusBusinessDays(final Instant time, final int days) { /** * Subtracts a specified number of business days from an input time. Subtracting negative days is equivalent to * adding days. - * + *

* Day subtraction are not always 24 hours. The resultant time will have the same local time as the input time, as * determined by the calendar's time zone. This accounts for Daylight Savings Time. For example, 2023-11-05 has a * daylight savings time adjustment, so '2023-11-04T14:00 ET' plus 1 day will result in '2023-11-05T15:00 ET', which * is a 25-hour difference. - * + *

* The resultant time will have the same time zone as the calendar. This could be different than the time zone of * the input {@link ZonedDateTime}. * @@ -2124,12 +2124,12 @@ public String plusNonBusinessDays(final String date, final int days) { /** * Adds a specified number of non-business days to an input time. Adding negative days is equivalent to subtracting * days. - * + *

* Day additions are not always 24 hours. The resultant time will have the same local time as the input time, as * determined by the calendar's time zone. This accounts for Daylight Savings Time. For example, 2023-11-05 has a * daylight savings time adjustment, so '2023-11-04T14:00 ET' plus 1 day will result in '2023-11-05T15:00 ET', which * is a 25-hour difference. - * + *

* The resultant time will have the same time zone as the calendar. This could be different than the time zone of * the input {@link ZonedDateTime}. * @@ -2151,12 +2151,12 @@ public Instant plusNonBusinessDays(final Instant time, final int days) { /** * Adds a specified number of non-business days to an input time. Adding negative days is equivalent to subtracting * days. - * + *

* Day additions are not always 24 hours. The resultant time will have the same local time as the input time, as * determined by the calendar's time zone. This accounts for Daylight Savings Time. For example, 2023-11-05 has a * daylight savings time adjustment, so '2023-11-04T14:00 ET' plus 1 day will result in '2023-11-05T15:00 ET', which * is a 25-hour difference. - * + *

* The resultant time will have the same time zone as the calendar. This could be different than the time zone of * the input {@link ZonedDateTime}. * @@ -2219,7 +2219,7 @@ public String minusNonBusinessDays(final String date, final int days) { /** * Subtracts a specified number of non-business days to an input time. Subtracting negative days is equivalent to * adding days. - * + *

* Day subtractions are not always 24 hours. The resultant time will have the same local time as the input time, as * determined by the calendar's time zone. This accounts for Daylight Savings Time. For example, 2023-11-05 has a * daylight savings time adjustment, so '2023-11-04T14:00 ET' plus 1 day will result in '2023-11-05T15:00 ET', which @@ -2242,12 +2242,12 @@ public Instant minusNonBusinessDays(final Instant time, final int days) { /** * Subtracts a specified number of non-business days to an input time. Subtracting negative days is equivalent to * adding days. - * + *

* Day subtractions are not always 24 hours. The resultant time will have the same local time as the input time, as * determined by the calendar's time zone. This accounts for Daylight Savings Time. For example, 2023-11-05 has a * daylight savings time adjustment, so '2023-11-04T14:00 ET' plus 1 day will result in '2023-11-05T15:00 ET', which * is a 25-hour difference. - * + *

* The resultant time will have the same time zone as the calendar. This could be different than the time zone of * the input {@link ZonedDateTime}. * From ccd9025959efae8d2926070347b944a9d3d4b8e5 Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Thu, 18 Apr 2024 14:12:51 -0600 Subject: [PATCH 06/54] Using advanced caching in the BusinessCalendar v2. --- .../time/calendar/BusinessCalendar.java | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java index 70901c67ada..3dad83971b1 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java @@ -1123,6 +1123,21 @@ public int numberBusinessDates(final Instant start, final Instant end) { return numberBusinessDates(start, end, true, true); } + public int numberNonBusinessDatesInternal(final LocalDate start, final LocalDate end, final boolean startInclusive, + final boolean endInclusive) { + int days = 0; + + for (LocalDate day = start; !day.isAfter(end); day = day.plusDays(1)) { + final boolean skip = (!startInclusive && day.equals(start)) || (!endInclusive && day.equals(end)); + + if (!skip && !isBusinessDay(day)) { + days++; + } + } + + return days; + } + /** * Returns the number of non-business dates in a given range. * @@ -1140,8 +1155,32 @@ public int numberNonBusinessDates(final LocalDate start, final LocalDate end, fi return NULL_INT; } - return numberCalendarDates(start, end, startInclusive, endInclusive) - - numberBusinessDates(start, end, startInclusive, endInclusive); + if (start.isAfter(end)) { + return 0; + } + + SummaryData summaryFirst = null; + SummaryData summary = null; + int days = 0; + + for (Iterator it = summaryCache.iterator(start, end, startInclusive, endInclusive); it.hasNext(); ) { + summary = it.next(); + + if(summaryFirst == null) { + summaryFirst = summary; + } + + days += summary.nonBusinessDays; + } + + if(summaryFirst == null){ + return numberNonBusinessDatesInternal(start, end, startInclusive, endInclusive); + } else { + days += numberNonBusinessDatesInternal(start, summaryFirst.startDate, startInclusive, false); + days += numberNonBusinessDatesInternal(summary.endDate, end, false, endInclusive); + } + + return days; } /** From 9f07d917c81a3ddf5595a796cbdcd81f8ccc7172 Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Thu, 18 Apr 2024 14:19:49 -0600 Subject: [PATCH 07/54] Using advanced caching in the BusinessCalendar v3. --- .../time/calendar/BusinessCalendar.java | 44 ++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java index 3dad83971b1..8add81a3e81 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java @@ -75,7 +75,6 @@ public SummaryData( final Instant endInstant, final LocalDate endDate, final long businessTimeNanos, - final int days, final int businessDays, final int nonBusinessDays, final ArrayList businessDates, @@ -139,7 +138,6 @@ private SummaryData summarize(final LocalDate startDate, final LocalDate endDate end.toInstant(), end.toLocalDate(), businessTimeNanos, - days, businessDays, nonBusinessDays, businessDates, @@ -155,9 +153,45 @@ private SummaryData computeMonthSummary(final int yearMonth) { } private SummaryData computeYearSummary(final int year){ - final LocalDate startDate = LocalDate.ofYearDay(year, 1); - final LocalDate endDate = LocalDate.ofYearDay(year + 1, 1); - return summarize(startDate, endDate); + Instant startInstant = null; + LocalDate startDate = null; + Instant endInstant = null; + LocalDate endDate = null; + long businessTimeNanos = 0; + int businessDays = 0; + int nonBusinessDays = 0; + ArrayList businessDates = new ArrayList<>(); + ArrayList nonBusinessDates = new ArrayList<>(); + + for(int month=1; month<=12; month++){ + SummaryData ms = summaryCache.getMonthSummary(year, month); + if(month == 1){ + startInstant = ms.startInstant; + startDate = ms.startDate; + } + + if (month == 12) { + endInstant = ms.endInstant; + endDate = ms.endDate; + } + + businessTimeNanos += ms.businessTimeNanos; + businessDays += ms.businessDays; + nonBusinessDays += ms.nonBusinessDays; + businessDates.addAll(ms.businessDates); + nonBusinessDates.addAll(ms.nonBusinessDates); + } + + return new SummaryData( + startInstant, + startDate, + endInstant, + endDate, + businessTimeNanos, + businessDays, + nonBusinessDays, + businessDates, + nonBusinessDates); } private SummaryData getYearSummary(final int year) { From 01377abdf86c88c563accb881f155bcabd91e5a7 Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Thu, 18 Apr 2024 14:36:55 -0600 Subject: [PATCH 08/54] Using advanced caching in the Calendar v4. Fixed unit tests. --- .../libs/StaticCalendarMethodsGenerator.java | 1 + .../time/calendar/BusinessCalendar.java | 6 +- .../io/deephaven/time/calendar/Calendar.java | 105 ++++++++++++++++-- .../deephaven/time/calendar/TestCalendar.java | 15 +++ .../calendar/TestStaticCalendarMethods.java | 1 + 5 files changed, 115 insertions(+), 13 deletions(-) diff --git a/Generators/src/main/java/io/deephaven/libs/StaticCalendarMethodsGenerator.java b/Generators/src/main/java/io/deephaven/libs/StaticCalendarMethodsGenerator.java index 75c3c6183d0..77f912b4d5a 100644 --- a/Generators/src/main/java/io/deephaven/libs/StaticCalendarMethodsGenerator.java +++ b/Generators/src/main/java/io/deephaven/libs/StaticCalendarMethodsGenerator.java @@ -78,6 +78,7 @@ public static void main(String[] args) throws ClassNotFoundException, IOExceptio excludes.add("description"); excludes.add("firstValidDate"); excludes.add("lastValidDate"); + excludes.add("clearCache"); StaticCalendarMethodsGenerator gen = new StaticCalendarMethodsGenerator(gradleTask, packageName, className, imports, diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java index 8add81a3e81..cc02eb42c99 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java @@ -980,7 +980,7 @@ public double fractionBusinessDayRemaining() { // region Ranges - public int numberBusinessDatesInternal(final LocalDate start, final LocalDate end, final boolean startInclusive, + private int numberBusinessDatesInternal(final LocalDate start, final LocalDate end, final boolean startInclusive, final boolean endInclusive) { int days = 0; @@ -1157,7 +1157,7 @@ public int numberBusinessDates(final Instant start, final Instant end) { return numberBusinessDates(start, end, true, true); } - public int numberNonBusinessDatesInternal(final LocalDate start, final LocalDate end, final boolean startInclusive, + private int numberNonBusinessDatesInternal(final LocalDate start, final LocalDate end, final boolean startInclusive, final boolean endInclusive) { int days = 0; @@ -1682,7 +1682,7 @@ public LocalDate[] nonBusinessDates(final Instant start, final Instant end) { // region Differences - public long diffBusinessNanosInternal(final Instant start, final LocalDate startDate, final Instant end, final LocalDate endDate) { + private long diffBusinessNanosInternal(final Instant start, final LocalDate startDate, final Instant end, final LocalDate endDate) { if (startDate.equals(endDate)) { final CalendarDay schedule = this.calendarDay(startDate); diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java index 50d8d2876c1..d82daf5e991 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java @@ -9,9 +9,7 @@ import java.time.*; import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import java.util.*; import static io.deephaven.util.QueryConstants.NULL_INT; @@ -30,6 +28,66 @@ public class Calendar { private final String description; private final ZoneId timeZone; + // region Cache + + private static class SummaryData { + final LocalDate startDate; + final LocalDate endDate; //todo inclusive? + final List dates; + + SummaryData(LocalDate startDate, LocalDate endDate, List dates) { + this.startDate = startDate; + this.endDate = endDate; + this.dates = dates; + } + + } + + private final YearMonthSummaryCache summaryCache = new YearMonthSummaryCache<>(this::computeMonthSummary, this::computeYearSummary); + + private SummaryData summarize(final LocalDate startDate, final LocalDate endDate) { + LocalDate date = startDate; + final ArrayList dates = new ArrayList<>(); + + while (date.isBefore(endDate)) { + dates.add(date); + date = date.plusDays(1); + } + + return new SummaryData(startDate, endDate, dates); + } + + private SummaryData computeMonthSummary(final int yearMonth) { + final int year = yearMonth / 100; + final int month = yearMonth % 100; + final LocalDate startDate = LocalDate.of(year, month, 1); + final LocalDate endDate = startDate.plusMonths(1); //todo end date is exclusive --- check code + return summarize(startDate, endDate); + } + + private SummaryData computeYearSummary(final int year){ + LocalDate startDate = null; + LocalDate endDate = null; + ArrayList dates = new ArrayList<>(); + + for(int month=1; month<=12; month++){ + SummaryData ms = summaryCache.getMonthSummary(year, month); + if(month == 1){ + startDate = ms.startDate; + } + + if (month == 12) { + endDate = ms.endDate; + } + + dates.addAll(ms.dates); + } + + return new SummaryData(startDate, endDate, dates); + } + + // endregion + // region Constructors /** @@ -54,7 +112,7 @@ public class Calendar { * Clears the cache. This should not generally be used and is provided for benchmarking. */ public void clearCache() { - //TODO: cache.clear(); + summaryCache.clear(); } // endregion @@ -307,7 +365,7 @@ public Instant plusDays(final Instant time, final int days) { * determined by the calendar's time zone. This accounts for Daylight Savings Time. For example, 2023-11-05 has a * daylight savings time adjustment, so '2023-11-04T14:00 ET' plus 1 day will result in '2023-11-05T15:00 ET', which * is a 25-hour difference. - * + *

* The resultant time will have the same time zone as the calendar. This could be different than the time zone of * the input {@link ZonedDateTime}. * @@ -433,6 +491,17 @@ public LocalDate pastDate(final int days) { // region Ranges + private void calendarDatesInternal(final ArrayList result, final LocalDate start, final LocalDate end, final boolean startInclusive, + final boolean endInclusive) { + for (LocalDate day = start; !day.isAfter(end); day = day.plusDays(1)) { + final boolean skip = (!startInclusive && day.equals(start)) || (!endInclusive && day.equals(end)); + + if (!skip) { + result.add(day); + } + } + } + /** * Returns the dates in a given range. * @@ -448,14 +517,30 @@ public LocalDate[] calendarDates(final LocalDate start, final LocalDate end, fin return null; } - List dateList = new ArrayList<>(); + if (start.isAfter(end)) { + return new LocalDate[0]; + } - for (LocalDate day = start; !day.isAfter(end); day = day.plusDays(1)) { - final boolean skip = (!startInclusive && day.equals(start)) || (!endInclusive && day.equals(end)); + final ArrayList dateList = new ArrayList<>(); - if (!skip) { - dateList.add(day); + SummaryData summaryFirst = null; + SummaryData summary = null; + + for (Iterator it = summaryCache.iterator(start, end, startInclusive, endInclusive); it.hasNext(); ) { + summary = it.next(); + + if(summaryFirst == null) { + summaryFirst = summary; + calendarDatesInternal(dateList, start, summaryFirst.startDate, startInclusive, false); } + + dateList.addAll(summary.dates); + } + + if(summaryFirst == null){ + calendarDatesInternal(dateList, start, end, startInclusive, endInclusive); + } else { + calendarDatesInternal(dateList, summary.endDate, end, false, endInclusive); } return dateList.toArray(new LocalDate[0]); diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestCalendar.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestCalendar.java index 892f23baed6..c60fb479618 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestCalendar.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestCalendar.java @@ -4,6 +4,7 @@ import io.deephaven.time.DateTimeUtils; import java.time.*; +import java.util.ArrayList; import static io.deephaven.util.QueryConstants.NULL_INT; @@ -221,6 +222,20 @@ public void testCalendarDates() { assertNull(calendar.calendarDates(start.atTime(3, 15).atZone(timeZone).toInstant(), null, false, false)); assertNull(calendar.calendarDates(null, end.atTime(3, 15).atZone(timeZone), false, false)); assertNull(calendar.calendarDates(start.atTime(3, 15).atZone(timeZone), null, false, false)); + + // end before start + assertEquals(new LocalDate[0], calendar.calendarDates(end, start)); + + // long span of dates + final LocalDate startLong = LocalDate.of(2019, 2, 1); + final LocalDate endLong = LocalDate.of(2023, 12, 31); + final ArrayList targetLong = new ArrayList<>(); + + for (LocalDate d = startLong; !d.isAfter(endLong); d = d.plusDays(1)) { + targetLong.add(d); + } + + assertEquals(targetLong.toArray(LocalDate[]::new), calendar.calendarDates(startLong, endLong)); } public void testNumberCalendarDates() { diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestStaticCalendarMethods.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestStaticCalendarMethods.java index f013bbe62f2..6ebb243edf7 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestStaticCalendarMethods.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestStaticCalendarMethods.java @@ -106,6 +106,7 @@ public void testAll() { excludes.add("description"); excludes.add("firstValidDate"); excludes.add("lastValidDate"); + excludes.add("clearCache"); for (Method m1 : BusinessCalendar.class.getMethods()) { if (m1.getDeclaringClass() == Object.class || From c9be43359fe72919414ef95dea9d0f91bac45847 Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Thu, 18 Apr 2024 14:44:04 -0600 Subject: [PATCH 09/54] Fixed exclusive/inclusive problems. --- .../io/deephaven/time/calendar/BusinessCalendar.java | 12 ++++++------ .../java/io/deephaven/time/calendar/Calendar.java | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java index cc02eb42c99..1325f6c768c 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java @@ -62,7 +62,7 @@ private static class SummaryData { private final Instant startInstant; private final LocalDate startDate; private final Instant endInstant; - private final LocalDate endDate; + private final LocalDate endDate; // exclusive private final long businessTimeNanos; private final int businessDays; private final int nonBusinessDays; @@ -148,7 +148,7 @@ private SummaryData computeMonthSummary(final int yearMonth) { final int year = yearMonth / 100; final int month = yearMonth % 100; final LocalDate startDate = LocalDate.of(year, month, 1); - final LocalDate endDate = startDate.plusMonths(1); //todo end date is exclusive --- check code + final LocalDate endDate = startDate.plusMonths(1); // exclusive return summarize(startDate, endDate); } @@ -1034,7 +1034,7 @@ public int numberBusinessDates(final LocalDate start, final LocalDate end, final return numberBusinessDatesInternal(start, end, startInclusive, endInclusive); } else { days += numberBusinessDatesInternal(start, summaryFirst.startDate, startInclusive, false); - days += numberBusinessDatesInternal(summary.endDate, end, false, endInclusive); + days += numberBusinessDatesInternal(summary.endDate, end, true, endInclusive); } return days; @@ -1211,7 +1211,7 @@ public int numberNonBusinessDates(final LocalDate start, final LocalDate end, fi return numberNonBusinessDatesInternal(start, end, startInclusive, endInclusive); } else { days += numberNonBusinessDatesInternal(start, summaryFirst.startDate, startInclusive, false); - days += numberNonBusinessDatesInternal(summary.endDate, end, false, endInclusive); + days += numberNonBusinessDatesInternal(summary.endDate, end, true, endInclusive); } return days; @@ -1384,7 +1384,7 @@ public LocalDate[] businessDates(final LocalDate start, final LocalDate end, fin if(summaryFirst == null){ businessDatesInternal(dateList, start, end, startInclusive, endInclusive); } else { - businessDatesInternal(dateList, summary.endDate, end, false, endInclusive); + businessDatesInternal(dateList, summary.endDate, end, true, endInclusive); } return dateList.toArray(new LocalDate[0]); @@ -1556,7 +1556,7 @@ public LocalDate[] nonBusinessDates(final LocalDate start, final LocalDate end, if(summaryFirst == null){ nonBusinessDatesInternal(dateList, start, end, startInclusive, endInclusive); } else { - nonBusinessDatesInternal(dateList, summary.endDate, end, false, endInclusive); + nonBusinessDatesInternal(dateList, summary.endDate, end, true, endInclusive); } return dateList.toArray(new LocalDate[0]); diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java index d82daf5e991..bf3ac642ccc 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java @@ -32,7 +32,7 @@ public class Calendar { private static class SummaryData { final LocalDate startDate; - final LocalDate endDate; //todo inclusive? + final LocalDate endDate; // exclusive final List dates; SummaryData(LocalDate startDate, LocalDate endDate, List dates) { @@ -54,14 +54,14 @@ private SummaryData summarize(final LocalDate startDate, final LocalDate endDate date = date.plusDays(1); } - return new SummaryData(startDate, endDate, dates); + return new SummaryData(startDate, endDate, dates); // end date is exclusive } private SummaryData computeMonthSummary(final int yearMonth) { final int year = yearMonth / 100; final int month = yearMonth % 100; final LocalDate startDate = LocalDate.of(year, month, 1); - final LocalDate endDate = startDate.plusMonths(1); //todo end date is exclusive --- check code + final LocalDate endDate = startDate.plusMonths(1); // exclusive return summarize(startDate, endDate); } @@ -540,7 +540,7 @@ public LocalDate[] calendarDates(final LocalDate start, final LocalDate end, fin if(summaryFirst == null){ calendarDatesInternal(dateList, start, end, startInclusive, endInclusive); } else { - calendarDatesInternal(dateList, summary.endDate, end, false, endInclusive); + calendarDatesInternal(dateList, summary.endDate, end, true, endInclusive); } return dateList.toArray(new LocalDate[0]); From 3accd32c191fa26ed7c05cd20fdf3534d20090f1 Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Thu, 18 Apr 2024 21:50:12 -0600 Subject: [PATCH 10/54] Spotless. --- .../time/calendar/BusinessCalendar.java | 88 ++++++++++--------- .../io/deephaven/time/calendar/Calendar.java | 25 +++--- .../time/calendar/YearMonthSummaryCache.java | 36 ++++---- .../deephaven/time/calendar/TestCalendar.java | 2 +- .../calendar/TestYearMonthSummaryCache.java | 66 +++++++------- 5 files changed, 115 insertions(+), 102 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java index 1325f6c768c..d6c555e146a 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java @@ -92,7 +92,8 @@ public SummaryData( } private final Map> cachedSchedules = new ConcurrentHashMap<>(); - private final YearMonthSummaryCache summaryCache = new YearMonthSummaryCache<>(this::computeMonthSummary, this::computeYearSummary); + private final YearMonthSummaryCache summaryCache = + new YearMonthSummaryCache<>(this::computeMonthSummary, this::computeYearSummary); private final int yearCacheStart; private final int yearCacheEnd; @@ -123,7 +124,7 @@ private SummaryData summarize(final LocalDate startDate, final LocalDate endDate nonBusinessDays += ibd ? 0 : 1; businessTimeNanos += bs.businessNanos(); - if(ibd) { + if (ibd) { businessDates.add(date); } else { nonBusinessDates.add(date); @@ -148,11 +149,11 @@ private SummaryData computeMonthSummary(final int yearMonth) { final int year = yearMonth / 100; final int month = yearMonth % 100; final LocalDate startDate = LocalDate.of(year, month, 1); - final LocalDate endDate = startDate.plusMonths(1); // exclusive + final LocalDate endDate = startDate.plusMonths(1); // exclusive return summarize(startDate, endDate); } - private SummaryData computeYearSummary(final int year){ + private SummaryData computeYearSummary(final int year) { Instant startInstant = null; LocalDate startDate = null; Instant endInstant = null; @@ -163,9 +164,9 @@ private SummaryData computeYearSummary(final int year){ ArrayList businessDates = new ArrayList<>(); ArrayList nonBusinessDates = new ArrayList<>(); - for(int month=1; month<=12; month++){ + for (int month = 1; month <= 12; month++) { SummaryData ms = summaryCache.getMonthSummary(year, month); - if(month == 1){ + if (month == 1) { startInstant = ms.startInstant; startDate = ms.startDate; } @@ -981,7 +982,7 @@ public double fractionBusinessDayRemaining() { // region Ranges private int numberBusinessDatesInternal(final LocalDate start, final LocalDate end, final boolean startInclusive, - final boolean endInclusive) { + final boolean endInclusive) { int days = 0; for (LocalDate day = start; !day.isAfter(end); day = day.plusDays(1)) { @@ -995,17 +996,17 @@ private int numberBusinessDatesInternal(final LocalDate start, final LocalDate e return days; } - /** - * Returns the number of business dates in a given range. - * - * @param start start of a time range - * @param end end of a time range - * @param startInclusive true to include {@code start} in the result; false to exclude {@code start} - * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} - * @return number of business dates between {@code start} and {@code end}. {@link QueryConstants#NULL_INT} if any - * input is {@code null}. - * @throws InvalidDateException if the dates are not in the valid range - */ + /** + * Returns the number of business dates in a given range. + * + * @param start start of a time range + * @param end end of a time range + * @param startInclusive true to include {@code start} in the result; false to exclude {@code start} + * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} + * @return number of business dates between {@code start} and {@code end}. {@link QueryConstants#NULL_INT} if any + * input is {@code null}. + * @throws InvalidDateException if the dates are not in the valid range + */ public int numberBusinessDates(final LocalDate start, final LocalDate end, final boolean startInclusive, final boolean endInclusive) { if (start == null || end == null) { @@ -1020,17 +1021,18 @@ public int numberBusinessDates(final LocalDate start, final LocalDate end, final SummaryData summary = null; int days = 0; - for (Iterator it = summaryCache.iterator(start, end, startInclusive, endInclusive); it.hasNext(); ) { + for (Iterator it = summaryCache.iterator(start, end, startInclusive, endInclusive); + it.hasNext();) { summary = it.next(); - if(summaryFirst == null) { + if (summaryFirst == null) { summaryFirst = summary; } days += summary.businessDays; } - if(summaryFirst == null){ + if (summaryFirst == null) { return numberBusinessDatesInternal(start, end, startInclusive, endInclusive); } else { days += numberBusinessDatesInternal(start, summaryFirst.startDate, startInclusive, false); @@ -1158,7 +1160,7 @@ public int numberBusinessDates(final Instant start, final Instant end) { } private int numberNonBusinessDatesInternal(final LocalDate start, final LocalDate end, final boolean startInclusive, - final boolean endInclusive) { + final boolean endInclusive) { int days = 0; for (LocalDate day = start; !day.isAfter(end); day = day.plusDays(1)) { @@ -1197,17 +1199,18 @@ public int numberNonBusinessDates(final LocalDate start, final LocalDate end, fi SummaryData summary = null; int days = 0; - for (Iterator it = summaryCache.iterator(start, end, startInclusive, endInclusive); it.hasNext(); ) { + for (Iterator it = summaryCache.iterator(start, end, startInclusive, endInclusive); + it.hasNext();) { summary = it.next(); - if(summaryFirst == null) { + if (summaryFirst == null) { summaryFirst = summary; } days += summary.nonBusinessDays; } - if(summaryFirst == null){ + if (summaryFirst == null) { return numberNonBusinessDatesInternal(start, end, startInclusive, endInclusive); } else { days += numberNonBusinessDatesInternal(start, summaryFirst.startDate, startInclusive, false); @@ -1334,9 +1337,10 @@ public int numberNonBusinessDates(final Instant start, final Instant end) { return numberNonBusinessDates(start, end, true, true); } - private void businessDatesInternal(final ArrayList result, final LocalDate start, final LocalDate end, final boolean startInclusive, - final boolean endInclusive) { - for (LocalDate day = start; !day.isAfter(end); day = day.plusDays(1)) { + private void businessDatesInternal(final ArrayList result, final LocalDate start, final LocalDate end, + final boolean startInclusive, + final boolean endInclusive) { + for (LocalDate day = start; !day.isAfter(end); day = day.plusDays(1)) { final boolean skip = (!startInclusive && day.equals(start)) || (!endInclusive && day.equals(end)); if (!skip && isBusinessDay(day)) { @@ -1370,10 +1374,11 @@ public LocalDate[] businessDates(final LocalDate start, final LocalDate end, fin SummaryData summaryFirst = null; SummaryData summary = null; - for (Iterator it = summaryCache.iterator(start, end, startInclusive, endInclusive); it.hasNext(); ) { + for (Iterator it = summaryCache.iterator(start, end, startInclusive, endInclusive); + it.hasNext();) { summary = it.next(); - if(summaryFirst == null) { + if (summaryFirst == null) { summaryFirst = summary; businessDatesInternal(dateList, start, summaryFirst.startDate, startInclusive, false); } @@ -1381,7 +1386,7 @@ public LocalDate[] businessDates(final LocalDate start, final LocalDate end, fin dateList.addAll(summary.businessDates); } - if(summaryFirst == null){ + if (summaryFirst == null) { businessDatesInternal(dateList, start, end, startInclusive, endInclusive); } else { businessDatesInternal(dateList, summary.endDate, end, true, endInclusive); @@ -1506,8 +1511,9 @@ public LocalDate[] businessDates(final Instant start, final Instant end) { return businessDates(start, end, true, true); } - private void nonBusinessDatesInternal(final ArrayList result, final LocalDate start, final LocalDate end, final boolean startInclusive, - final boolean endInclusive) { + private void nonBusinessDatesInternal(final ArrayList result, final LocalDate start, final LocalDate end, + final boolean startInclusive, + final boolean endInclusive) { for (LocalDate day = start; !day.isAfter(end); day = day.plusDays(1)) { final boolean skip = (!startInclusive && day.equals(start)) || (!endInclusive && day.equals(end)); @@ -1542,10 +1548,11 @@ public LocalDate[] nonBusinessDates(final LocalDate start, final LocalDate end, SummaryData summaryFirst = null; SummaryData summary = null; - for (Iterator it = summaryCache.iterator(start, end, startInclusive, endInclusive); it.hasNext(); ) { + for (Iterator it = summaryCache.iterator(start, end, startInclusive, endInclusive); + it.hasNext();) { summary = it.next(); - if(summaryFirst == null) { + if (summaryFirst == null) { summaryFirst = summary; nonBusinessDatesInternal(dateList, start, summaryFirst.startDate, startInclusive, false); } @@ -1553,7 +1560,7 @@ public LocalDate[] nonBusinessDates(final LocalDate start, final LocalDate end, dateList.addAll(summary.nonBusinessDates); } - if(summaryFirst == null){ + if (summaryFirst == null) { nonBusinessDatesInternal(dateList, start, end, startInclusive, endInclusive); } else { nonBusinessDatesInternal(dateList, summary.endDate, end, true, endInclusive); @@ -1682,7 +1689,8 @@ public LocalDate[] nonBusinessDates(final Instant start, final Instant end) { // region Differences - private long diffBusinessNanosInternal(final Instant start, final LocalDate startDate, final Instant end, final LocalDate endDate) { + private long diffBusinessNanosInternal(final Instant start, final LocalDate startDate, final Instant end, + final LocalDate endDate) { if (startDate.equals(endDate)) { final CalendarDay schedule = this.calendarDay(startDate); @@ -1728,17 +1736,17 @@ public long diffBusinessNanos(final Instant start, final Instant end) { SummaryData summary = null; long nanos = 0; - for (Iterator it = summaryCache.iterator(startDate, endDate, false, false); it.hasNext(); ) { + for (Iterator it = summaryCache.iterator(startDate, endDate, false, false); it.hasNext();) { summary = it.next(); - if(summaryFirst == null) { + if (summaryFirst == null) { summaryFirst = summary; } nanos += summary.businessTimeNanos; } - if(summaryFirst == null){ + if (summaryFirst == null) { return diffBusinessNanosInternal(start, startDate, end, endDate); } else { nanos += diffBusinessNanosInternal(start, startDate, summaryFirst.startInstant, summaryFirst.startDate); diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java index bf3ac642ccc..749d3fb4226 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java @@ -43,7 +43,8 @@ private static class SummaryData { } - private final YearMonthSummaryCache summaryCache = new YearMonthSummaryCache<>(this::computeMonthSummary, this::computeYearSummary); + private final YearMonthSummaryCache summaryCache = + new YearMonthSummaryCache<>(this::computeMonthSummary, this::computeYearSummary); private SummaryData summarize(final LocalDate startDate, final LocalDate endDate) { LocalDate date = startDate; @@ -61,18 +62,18 @@ private SummaryData computeMonthSummary(final int yearMonth) { final int year = yearMonth / 100; final int month = yearMonth % 100; final LocalDate startDate = LocalDate.of(year, month, 1); - final LocalDate endDate = startDate.plusMonths(1); // exclusive + final LocalDate endDate = startDate.plusMonths(1); // exclusive return summarize(startDate, endDate); } - private SummaryData computeYearSummary(final int year){ + private SummaryData computeYearSummary(final int year) { LocalDate startDate = null; LocalDate endDate = null; ArrayList dates = new ArrayList<>(); - for(int month=1; month<=12; month++){ + for (int month = 1; month <= 12; month++) { SummaryData ms = summaryCache.getMonthSummary(year, month); - if(month == 1){ + if (month == 1) { startDate = ms.startDate; } @@ -109,7 +110,7 @@ private SummaryData computeYearSummary(final int year){ // region Cache /** - * Clears the cache. This should not generally be used and is provided for benchmarking. + * Clears the cache. This should not generally be used and is provided for benchmarking. */ public void clearCache() { summaryCache.clear(); @@ -491,8 +492,9 @@ public LocalDate pastDate(final int days) { // region Ranges - private void calendarDatesInternal(final ArrayList result, final LocalDate start, final LocalDate end, final boolean startInclusive, - final boolean endInclusive) { + private void calendarDatesInternal(final ArrayList result, final LocalDate start, final LocalDate end, + final boolean startInclusive, + final boolean endInclusive) { for (LocalDate day = start; !day.isAfter(end); day = day.plusDays(1)) { final boolean skip = (!startInclusive && day.equals(start)) || (!endInclusive && day.equals(end)); @@ -526,10 +528,11 @@ public LocalDate[] calendarDates(final LocalDate start, final LocalDate end, fin SummaryData summaryFirst = null; SummaryData summary = null; - for (Iterator it = summaryCache.iterator(start, end, startInclusive, endInclusive); it.hasNext(); ) { + for (Iterator it = summaryCache.iterator(start, end, startInclusive, endInclusive); + it.hasNext();) { summary = it.next(); - if(summaryFirst == null) { + if (summaryFirst == null) { summaryFirst = summary; calendarDatesInternal(dateList, start, summaryFirst.startDate, startInclusive, false); } @@ -537,7 +540,7 @@ public LocalDate[] calendarDates(final LocalDate start, final LocalDate end, fin dateList.addAll(summary.dates); } - if(summaryFirst == null){ + if (summaryFirst == null) { calendarDatesInternal(dateList, start, end, startInclusive, endInclusive); } else { calendarDatesInternal(dateList, summary.endDate, end, true, endInclusive); diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java index 49b87e7e202..b0612714bd8 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java @@ -23,7 +23,7 @@ class YearMonthSummaryCache { * Creates a new cache. * * @param computeMonthSummary the function to compute a month summary - * @param computeYearSummary the function to compute a year summary + * @param computeYearSummary the function to compute a year summary */ YearMonthSummaryCache(Function computeMonthSummary, Function computeYearSummary) { this.computeMonthSummary = computeMonthSummary; @@ -51,7 +51,7 @@ T getMonthSummary(int yearMonth) { /** * Gets the month summary for the specified year and month. * - * @param year the year + * @param year the year * @param month the month * @return the month summary */ @@ -87,7 +87,7 @@ private class YearMonthSummaryIterator implements Iterator { currentMonth = startMonth; currentYear = startYear; - if(start.getDayOfMonth() != 1) { + if (start.getDayOfMonth() != 1) { incrementCurrentByMonth(); } @@ -99,8 +99,8 @@ private class YearMonthSummaryIterator implements Iterator { finalMonth = endMonth; finalYear = endYear; - if(endPlus1Month == endMonth) { - if(finalMonth == 1) { + if (endPlus1Month == endMonth) { + if (finalMonth == 1) { finalMonth = 12; finalYear = finalYear - 1; } else { @@ -112,7 +112,7 @@ private class YearMonthSummaryIterator implements Iterator { } private void incrementCurrentByMonth() { - if(currentMonth == 12) { + if (currentMonth == 12) { currentMonth = 1; currentYear += 1; } else { @@ -136,7 +136,7 @@ public boolean hasNext() { public T next() { final T val; - if(currentMonth == 1 && (currentYear != finalYear || finalMonth == 12)) { + if (currentMonth == 1 && (currentYear != finalYear || finalMonth == 12)) { val = getYearSummary(currentYear); incrementCurrentByYear(); } else { @@ -149,22 +149,22 @@ public T next() { } /** - * Gets an iterator over the summaries for the specified range. The returned iterator will include the start - * date if {@code startInclusive} is true, and the end date if {@code endInclusive} is true. - * If the start date is after the end date, the iterator will be empty. + * Gets an iterator over the summaries for the specified range. The returned iterator will include the start date if + * {@code startInclusive} is true, and the end date if {@code endInclusive} is true. If the start date is after the + * end date, the iterator will be empty. *

- * The iterator will return summaries in chronological order, - * and these summaries can be a mix of month and year summaries. - * Dates not represented by complete summaries will be skipped (e.g. partial months). + * The iterator will return summaries in chronological order, and these summaries can be a mix of month and year + * summaries. Dates not represented by complete summaries will be skipped (e.g. partial months). * - * @param start the start date - * @param end the end date + * @param start the start date + * @param end the end date * @param startInclusive whether the start date is inclusive - * @param endInclusive whether the end date is inclusive + * @param endInclusive whether the end date is inclusive * @return the iterator */ Iterator iterator(final LocalDate start, final LocalDate end, - final boolean startInclusive, final boolean endInclusive) { - return new YearMonthSummaryIterator(startInclusive ? start : start.plusDays(1), endInclusive ? end : end.minusDays(1)); + final boolean startInclusive, final boolean endInclusive) { + return new YearMonthSummaryIterator(startInclusive ? start : start.plusDays(1), + endInclusive ? end : end.minusDays(1)); } } diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestCalendar.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestCalendar.java index c60fb479618..b0cd72dba1a 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestCalendar.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestCalendar.java @@ -232,7 +232,7 @@ public void testCalendarDates() { final ArrayList targetLong = new ArrayList<>(); for (LocalDate d = startLong; !d.isAfter(endLong); d = d.plusDays(1)) { - targetLong.add(d); + targetLong.add(d); } assertEquals(targetLong.toArray(LocalDate[]::new), calendar.calendarDates(startLong, endLong)); diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java index a6499ed1360..f15d4b21e23 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java @@ -14,8 +14,8 @@ public class TestYearMonthSummaryCache extends BaseArrayTestCase { public void testGetters() { - final int[] monthCount = new int[]{0}; - final int[] yearCount = new int[]{0}; + final int[] monthCount = new int[] {0}; + final int[] yearCount = new int[] {0}; final Function monthSummary = i -> { monthCount[0]++; @@ -73,8 +73,7 @@ public void testGetters() { assertEquals(cache.getMonthSummary(202101), cache.getMonthSummary(2021, 1)); } - private static Stream - iteratorToStream(Iterator iterator) { + private static Stream iteratorToStream(Iterator iterator) { Spliterator spliterator = Spliterators.spliteratorUnknownSize(iterator, 0); return StreamSupport.stream(spliterator, false); } @@ -88,76 +87,78 @@ public void testIteratorInclusive() { LocalDate start = LocalDate.of(2021, 1, 2); LocalDate end = LocalDate.of(2021, 1, 1); String[] target = {}; - String[] actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + String[] actual = + iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); assertEquals(target, actual); // same month start = LocalDate.of(2021, 1, 1); end = LocalDate.of(2021, 1, 11); - target = new String[]{}; + target = new String[] {}; actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); assertEquals(target, actual); // adjacent partial months start = LocalDate.of(2021, 1, 3); end = LocalDate.of(2021, 2, 11); - target = new String[]{}; + target = new String[] {}; actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); assertEquals(target, actual); // full month + partial month start = LocalDate.of(2021, 1, 1); end = LocalDate.of(2021, 2, 11); - target = new String[]{"month202101"}; + target = new String[] {"month202101"}; actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); assertEquals(target, actual); // full month + few days start = LocalDate.of(2020, 12, 12); end = LocalDate.of(2021, 2, 11); - target = new String[]{"month202101"}; + target = new String[] {"month202101"}; actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); assertEquals(target, actual); // multiple months + few days start = LocalDate.of(2020, 11, 12); end = LocalDate.of(2021, 4, 11); - target = new String[]{"month202012", "month202101", "month202102", "month202103"}; + target = new String[] {"month202012", "month202101", "month202102", "month202103"}; actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); assertEquals(target, actual); // partial month + full month start = LocalDate.of(2021, 1, 3); end = LocalDate.of(2021, 2, 28); - target = new String[]{"month202102"}; + target = new String[] {"month202102"}; actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); assertEquals(target, actual); // full year start = LocalDate.of(2021, 1, 1); end = LocalDate.of(2021, 12, 31); - target = new String[]{"year2021"}; + target = new String[] {"year2021"}; actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); assertEquals(target, actual); // full year + few days start = LocalDate.of(2020, 12, 11); end = LocalDate.of(2022, 1, 3); - target = new String[]{"year2021"}; + target = new String[] {"year2021"}; actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); assertEquals(target, actual); // multiple years + few days start = LocalDate.of(2018, 12, 11); end = LocalDate.of(2022, 1, 3); - target = new String[]{"year2019", "year2020", "year2021"}; + target = new String[] {"year2019", "year2020", "year2021"}; actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); assertEquals(target, actual); // mixed start = LocalDate.of(2018, 10, 11); end = LocalDate.of(2022, 3, 3); - target = new String[]{"month201811", "month201812", "year2019", "year2020", "year2021", "month202201", "month202202"}; + target = new String[] {"month201811", "month201812", "year2019", "year2020", "year2021", "month202201", + "month202202"}; actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); assertEquals(target, actual); } @@ -172,25 +173,26 @@ public void testIteratorExclusiveInclusive() { boolean startInclusive = true; boolean endInclusive = true; - String[] target = new String[]{"month202112"}; - String[] actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + String[] target = new String[] {"month202112"}; + String[] actual = + iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); assertEquals(target, actual); startInclusive = false; endInclusive = true; - target = new String[]{}; + target = new String[] {}; actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); assertEquals(target, actual); startInclusive = true; endInclusive = false; - target = new String[]{}; + target = new String[] {}; actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); assertEquals(target, actual); startInclusive = false; endInclusive = false; - target = new String[]{}; + target = new String[] {}; actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); assertEquals(target, actual); @@ -201,25 +203,25 @@ public void testIteratorExclusiveInclusive() { startInclusive = true; endInclusive = true; - target = new String[]{"month202112"}; + target = new String[] {"month202112"}; actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); assertEquals(target, actual); startInclusive = false; endInclusive = true; - target = new String[]{"month202112"}; + target = new String[] {"month202112"}; actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); assertEquals(target, actual); startInclusive = true; endInclusive = false; - target = new String[]{}; + target = new String[] {}; actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); assertEquals(target, actual); startInclusive = false; endInclusive = false; - target = new String[]{}; + target = new String[] {}; actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); assertEquals(target, actual); @@ -230,25 +232,25 @@ public void testIteratorExclusiveInclusive() { startInclusive = true; endInclusive = true; - target = new String[]{"month202112"}; + target = new String[] {"month202112"}; actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); assertEquals(target, actual); startInclusive = false; endInclusive = true; - target = new String[]{}; + target = new String[] {}; actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); assertEquals(target, actual); startInclusive = true; endInclusive = false; - target = new String[]{"month202112"}; + target = new String[] {"month202112"}; actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); assertEquals(target, actual); startInclusive = false; endInclusive = false; - target = new String[]{}; + target = new String[] {}; actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); assertEquals(target, actual); @@ -259,25 +261,25 @@ public void testIteratorExclusiveInclusive() { startInclusive = true; endInclusive = true; - target = new String[]{"month202112"}; + target = new String[] {"month202112"}; actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); assertEquals(target, actual); startInclusive = false; endInclusive = true; - target = new String[]{"month202112"}; + target = new String[] {"month202112"}; actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); assertEquals(target, actual); startInclusive = true; endInclusive = false; - target = new String[]{"month202112"}; + target = new String[] {"month202112"}; actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); assertEquals(target, actual); startInclusive = false; endInclusive = false; - target = new String[]{"month202112"}; + target = new String[] {"month202112"}; actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); assertEquals(target, actual); } From 32f5c57013ad182106d1d2f37b083d3412551130 Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Thu, 18 Apr 2024 21:50:54 -0600 Subject: [PATCH 11/54] Spotless. --- .../time/calendar/BusinessCalendar.java | 16 ++++++++-------- .../io/deephaven/time/calendar/Calendar.java | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java index d6c555e146a..fdfe4a6f8f8 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java @@ -1021,8 +1021,8 @@ public int numberBusinessDates(final LocalDate start, final LocalDate end, final SummaryData summary = null; int days = 0; - for (Iterator it = summaryCache.iterator(start, end, startInclusive, endInclusive); - it.hasNext();) { + for (Iterator it = summaryCache.iterator(start, end, startInclusive, endInclusive); it + .hasNext();) { summary = it.next(); if (summaryFirst == null) { @@ -1199,8 +1199,8 @@ public int numberNonBusinessDates(final LocalDate start, final LocalDate end, fi SummaryData summary = null; int days = 0; - for (Iterator it = summaryCache.iterator(start, end, startInclusive, endInclusive); - it.hasNext();) { + for (Iterator it = summaryCache.iterator(start, end, startInclusive, endInclusive); it + .hasNext();) { summary = it.next(); if (summaryFirst == null) { @@ -1374,8 +1374,8 @@ public LocalDate[] businessDates(final LocalDate start, final LocalDate end, fin SummaryData summaryFirst = null; SummaryData summary = null; - for (Iterator it = summaryCache.iterator(start, end, startInclusive, endInclusive); - it.hasNext();) { + for (Iterator it = summaryCache.iterator(start, end, startInclusive, endInclusive); it + .hasNext();) { summary = it.next(); if (summaryFirst == null) { @@ -1548,8 +1548,8 @@ public LocalDate[] nonBusinessDates(final LocalDate start, final LocalDate end, SummaryData summaryFirst = null; SummaryData summary = null; - for (Iterator it = summaryCache.iterator(start, end, startInclusive, endInclusive); - it.hasNext();) { + for (Iterator it = summaryCache.iterator(start, end, startInclusive, endInclusive); it + .hasNext();) { summary = it.next(); if (summaryFirst == null) { diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java index 749d3fb4226..171ba9b5202 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java @@ -528,8 +528,8 @@ public LocalDate[] calendarDates(final LocalDate start, final LocalDate end, fin SummaryData summaryFirst = null; SummaryData summary = null; - for (Iterator it = summaryCache.iterator(start, end, startInclusive, endInclusive); - it.hasNext();) { + for (Iterator it = summaryCache.iterator(start, end, startInclusive, endInclusive); it + .hasNext();) { summary = it.next(); if (summaryFirst == null) { From d1d96df72f66cebd20851001952ee29624028873 Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Thu, 18 Apr 2024 22:08:02 -0600 Subject: [PATCH 12/54] Copyright header. --- .../java/io/deephaven/time/calendar/YearMonthSummaryCache.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java index b0612714bd8..b74aecbd64a 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java @@ -1,3 +1,6 @@ +/** + * Copyright (c) 2016-2023 Deephaven Data Labs and Patent Pending + */ package io.deephaven.time.calendar; import java.time.LocalDate; From fa690166376f139ac03b923aa7f1448d68c6b575 Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Thu, 18 Apr 2024 22:20:05 -0600 Subject: [PATCH 13/54] Copyright header. --- .../io/deephaven/time/calendar/YearMonthSummaryCache.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java index b74aecbd64a..15df70d7117 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java @@ -1,6 +1,6 @@ -/** - * Copyright (c) 2016-2023 Deephaven Data Labs and Patent Pending - */ +// +// Copyright (c) 2016-2023 Deephaven Data Labs and Patent Pending +// package io.deephaven.time.calendar; import java.time.LocalDate; From f5e1e5fb38bc2c956bb45fc6eaba644c351d1aad Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Thu, 18 Apr 2024 22:29:30 -0600 Subject: [PATCH 14/54] Copyright header. --- .../java/io/deephaven/time/calendar/YearMonthSummaryCache.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java index 15df70d7117..22b4ee53fc9 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java @@ -1,5 +1,5 @@ // -// Copyright (c) 2016-2023 Deephaven Data Labs and Patent Pending +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending // package io.deephaven.time.calendar; From bf54c0f456657e8af6b75a3bf51aabbcae6efd91 Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Thu, 18 Apr 2024 22:36:08 -0600 Subject: [PATCH 15/54] Copyright header. --- .../io/deephaven/time/calendar/TestYearMonthSummaryCache.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java index f15d4b21e23..6794e97fb59 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java @@ -1,3 +1,6 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// package io.deephaven.time.calendar; import io.deephaven.base.testing.BaseArrayTestCase; From 94fa753bcc191d7da7e852185cb865a8b1000d60 Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Tue, 23 Apr 2024 15:35:44 -0600 Subject: [PATCH 16/54] A generalized fast concurrent cache. --- .../time/calendar/FastConcurrentCache.java | 132 ++++++++++++++++++ .../calendar/TestFastConcurrentCache.java | 52 +++++++ 2 files changed, 184 insertions(+) create mode 100644 engine/time/src/main/java/io/deephaven/time/calendar/FastConcurrentCache.java create mode 100644 engine/time/src/test/java/io/deephaven/time/calendar/TestFastConcurrentCache.java diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/FastConcurrentCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/FastConcurrentCache.java new file mode 100644 index 00000000000..07eed467aaa --- /dev/null +++ b/engine/time/src/main/java/io/deephaven/time/calendar/FastConcurrentCache.java @@ -0,0 +1,132 @@ +package io.deephaven.time.calendar; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +/** + * A cache that is designed to be fast when accessed concurrently. When created, the cache uses a ConcurrentHashMap to + * store values. When the fast cache is enabled, the cache is converted to a HashMap. This is done because HashMap is + * faster than {@link ConcurrentHashMap} when accessed concurrently. The fast cache is immutable, so it is safe to + * use the HashMap for optimal concurrent access. + *

+ * The fast cache population happens on a separate thread. This is done to avoid blocking the calling thread. The + * calling thread can wait for the fast cache to be populated by calling {@link #enableFastCache} with the wait parameter + * set to true. This will block the calling thread until the fast cache is populated. + * + * @param the key type + * @param the value type + */ +class FastConcurrentCache { + + private final Function valueComputer; + private Map cache = new ConcurrentHashMap<>(); + private volatile boolean fastCache = false; + private CompletableFuture fastCacheFuture = null; + + /** + * Creates a new cache. + * + * @param valueComputer computes the value for a key. + */ + public FastConcurrentCache(final Function valueComputer) { + this.valueComputer = valueComputer; + } + + /** + * Returns whether the fast cache is enabled. + * + * @return whether the fast cache is enabled + */ + public boolean isFastCache() { + return fastCache; + } + + /** + * Enables the fast cache. + *

+ * To enable the fast cache, a list of keys is provided. The value for each key is computed and added to the + * cache. If the cache is already enabled, an exception is thrown. To regenerate the fast cache, clear the + * cache first. + * + * @param keys the keys to compute and add to the cache + * @param wait whether to wait for the cache to be generated + * @throws IllegalStateException if the fast cache is already enabled + */ + void enableFastCache(final List keys, final boolean wait) { + + synchronized (this) { + if (fastCache) { + throw new IllegalStateException("Fast cache is already enabled. To change the range, clear the cache first."); + } + + fastCacheFuture = CompletableFuture.runAsync(() -> { + final Map c = new HashMap<>(cache); + + for (K key : keys) { + if (!c.containsKey(key)) { + c.put(key, valueComputer.apply(key)); + } + } + + fastCache = true; + cache = c; + synchronized (this) { + fastCacheFuture = null; + } + }); + } + + if (wait) { + fastCacheFuture.join(); + } + } + + /** + * Clears the cache and disables the fast cache. + */ + void clear() { + synchronized (this) { + if (fastCacheFuture != null) { + final boolean canceled = fastCacheFuture.cancel(true); + + if (!canceled) { + throw new IllegalStateException("Failed to cancel fast cache computation"); + } + } + } + + if (fastCache) { + fastCache = false; + cache = new ConcurrentHashMap<>(); + } else { + cache.clear(); + } + } + + /** + * Gets the value for a key. If the fast cache is enabled, the value is retrieved from the cache. If the value is + * not found, an exception is thrown. If the fast cache is not enabled, the value is computed and added to the + * cache. + * + * @param key the key + * @return the value + * @throws IllegalArgumentException if the value is not found + */ + public V get(K key) { + if (fastCache) { + final V v = cache.get(key); + + if (v == null) { + throw new IllegalArgumentException("No value found for " + key + ". Fast cache is enabled so the value can not be computed."); + } + + return v; + } else { + return cache.computeIfAbsent(key, valueComputer); + } + } +} diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestFastConcurrentCache.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestFastConcurrentCache.java new file mode 100644 index 00000000000..89ceef1f0de --- /dev/null +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestFastConcurrentCache.java @@ -0,0 +1,52 @@ +package io.deephaven.time.calendar; + +import io.deephaven.base.testing.BaseArrayTestCase; + +import java.util.List; + +public class TestFastConcurrentCache extends BaseArrayTestCase { + + public void testCache() { + final FastConcurrentCache cache = new FastConcurrentCache<>(String::toUpperCase); + + assertFalse(cache.isFastCache()); + assertEquals("A", cache.get("a")); + assertEquals("A", cache.get("a")); + + cache.enableFastCache(List.of("a", "b", "c"), true); + + assertTrue(cache.isFastCache()); + assertEquals("A", cache.get("a")); + assertEquals("B", cache.get("b")); + assertEquals("C", cache.get("c")); + + try{ + cache.get("d"); + fail("Expected exception"); + } catch (final RuntimeException e) { + //pass + } + + try{ + // cache is already enabled + cache.enableFastCache(List.of("a", "b", "c"), true); + fail("Expected exception"); + } catch (final IllegalStateException e) { + //pass + } + + cache.clear(); + assertFalse(cache.isFastCache()); + assertEquals("A", cache.get("a")); + assertEquals("B", cache.get("b")); + assertEquals("C", cache.get("c")); + assertEquals("D", cache.get("d")); + + cache.clear(); + assertFalse(cache.isFastCache()); + assertEquals("A", cache.get("a")); + assertEquals("B", cache.get("b")); + assertEquals("C", cache.get("c")); + assertEquals("D", cache.get("d")); + } +} From 9a47febee39c18042204a8063b2a25b8a490ea72 Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Tue, 23 Apr 2024 15:41:44 -0600 Subject: [PATCH 17/54] Incorporating generalized cache. --- .../libs/StaticCalendarMethodsGenerator.java | 1 + .../time/calendar/BusinessCalendar.java | 8 +- .../io/deephaven/time/calendar/Calendar.java | 153 ++++++++++-------- .../calendar/TestStaticCalendarMethods.java | 1 + 4 files changed, 88 insertions(+), 75 deletions(-) diff --git a/Generators/src/main/java/io/deephaven/libs/StaticCalendarMethodsGenerator.java b/Generators/src/main/java/io/deephaven/libs/StaticCalendarMethodsGenerator.java index 77f912b4d5a..dc9b015b253 100644 --- a/Generators/src/main/java/io/deephaven/libs/StaticCalendarMethodsGenerator.java +++ b/Generators/src/main/java/io/deephaven/libs/StaticCalendarMethodsGenerator.java @@ -79,6 +79,7 @@ public static void main(String[] args) throws ClassNotFoundException, IOExceptio excludes.add("firstValidDate"); excludes.add("lastValidDate"); excludes.add("clearCache"); + excludes.add("enableFastCache"); StaticCalendarMethodsGenerator gen = new StaticCalendarMethodsGenerator(gradleTask, packageName, className, imports, diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java index fdfe4a6f8f8..60cedb97fb3 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java @@ -10,7 +10,6 @@ import java.time.*; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; import static io.deephaven.util.QueryConstants.*; @@ -91,7 +90,8 @@ public SummaryData( } } - private final Map> cachedSchedules = new ConcurrentHashMap<>(); + private final FastConcurrentCache> schedulesCache = + new FastConcurrentCache<>(this::computeCalendarDay); private final YearMonthSummaryCache summaryCache = new YearMonthSummaryCache<>(this::computeMonthSummary, this::computeYearSummary); private final int yearCacheStart; @@ -100,7 +100,7 @@ public SummaryData( @Override public void clearCache() { super.clearCache(); - cachedSchedules.clear(); + schedulesCache.clear(); summaryCache.clear(); } @@ -346,7 +346,7 @@ public CalendarDay calendarDay(final LocalDate date) { + " lastValidDate=" + lastValidDate); } - return cachedSchedules.computeIfAbsent(date, this::computeCalendarDay); + return schedulesCache.get(date); } /** diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java index 171ba9b5202..7061774224c 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java @@ -87,6 +87,17 @@ private SummaryData computeYearSummary(final int year) { return new SummaryData(startDate, endDate, dates); } + /** + * Enables a fast cache that improves access and computation times. + * + * @param start the start date + * @param end the end date + * @param wait whether to wait for the computation to finish + */ + protected void enableFastCache(final LocalDate start, final LocalDate end, final boolean wait) { + summaryCache.enableFastCache(start, end, wait); + } + // endregion // region Constructors @@ -94,9 +105,9 @@ private SummaryData computeYearSummary(final int year) { /** * Creates a new calendar. * - * @param name calendar name. + * @param name calendar name. * @param description calendar description. - * @param timeZone calendar time zone. + * @param timeZone calendar time zone. * @throws RequirementFailure if {@code name} or {@code timeZone} is {@code null} */ Calendar(final String name, final String description, final ZoneId timeZone) { @@ -244,7 +255,7 @@ public int dayOfWeekValue() { * * @param date date * @return current day of the week, or {@link io.deephaven.util.QueryConstants#NULL_INT} if the input is - * {@code null}. + * {@code null}. */ public int dayOfWeekValue(final LocalDate date) { if (date == null) { @@ -259,7 +270,7 @@ public int dayOfWeekValue(final LocalDate date) { * * @param date date * @return current day of the week, or {@link io.deephaven.util.QueryConstants#NULL_INT} if the input is - * {@code null}. + * {@code null}. */ public int dayOfWeekValue(final String date) { if (date == null) { @@ -274,7 +285,7 @@ public int dayOfWeekValue(final String date) { * * @param time time * @return current day of the week, or {@link io.deephaven.util.QueryConstants#NULL_INT} if the input is - * {@code null}. + * {@code null}. */ public int dayOfWeekValue(final Instant time) { if (time == null) { @@ -289,7 +300,7 @@ public int dayOfWeekValue(final Instant time) { * * @param time time * @return current day of the week, or {@link io.deephaven.util.QueryConstants#NULL_INT} if the input is - * {@code null}. + * {@code null}. */ public int dayOfWeekValue(final ZonedDateTime time) { if (time == null) { @@ -310,7 +321,7 @@ public int dayOfWeekValue(final ZonedDateTime time) { * @param date date * @param days number of days to add * @return {@code days} days after {@code date}, or {@code null} if any input is {@code null} or - * {@link io.deephaven.util.QueryConstants#NULL_INT}. + * {@link io.deephaven.util.QueryConstants#NULL_INT}. */ public LocalDate plusDays(final LocalDate date, final int days) { if (date == null || days == NULL_INT) { @@ -326,7 +337,7 @@ public LocalDate plusDays(final LocalDate date, final int days) { * @param date date * @param days number of days to add * @return {@code days} days after {@code date}, or {@code null} if any input is {@code null} or - * {@link io.deephaven.util.QueryConstants#NULL_INT}. + * {@link io.deephaven.util.QueryConstants#NULL_INT}. * @throws DateTimeUtils.DateTimeParseException if the string cannot be parsed */ public String plusDays(final String date, final int days) { @@ -349,7 +360,7 @@ public String plusDays(final String date, final int days) { * @param time time * @param days number of days to add * @return {@code days} days after {@code time}, or {@code null} if any input is {@code null} or - * {@link io.deephaven.util.QueryConstants#NULL_INT}. + * {@link io.deephaven.util.QueryConstants#NULL_INT}. */ public Instant plusDays(final Instant time, final int days) { if (time == null || days == NULL_INT) { @@ -373,7 +384,7 @@ public Instant plusDays(final Instant time, final int days) { * @param time time * @param days number of days to add * @return {@code days} days after {@code time}, or {@code null} if any input is {@code null} or - * {@link io.deephaven.util.QueryConstants#NULL_INT}. + * {@link io.deephaven.util.QueryConstants#NULL_INT}. */ public ZonedDateTime plusDays(final ZonedDateTime time, final int days) { if (time == null || days == NULL_INT) { @@ -392,7 +403,7 @@ public ZonedDateTime plusDays(final ZonedDateTime time, final int days) { * @param date date * @param days number of days to subtract * @return {@code days} days before {@code date}, or {@code null} if any input is {@code null} or - * {@link io.deephaven.util.QueryConstants#NULL_INT}. + * {@link io.deephaven.util.QueryConstants#NULL_INT}. */ public LocalDate minusDays(final LocalDate date, final int days) { if (date == null || days == NULL_INT) { @@ -408,7 +419,7 @@ public LocalDate minusDays(final LocalDate date, final int days) { * @param date date * @param days number of days to subtract * @return {@code days} days before {@code date}, or {@code null} if any input is {@code null} or - * {@link io.deephaven.util.QueryConstants#NULL_INT}. + * {@link io.deephaven.util.QueryConstants#NULL_INT}. * @throws DateTimeUtils.DateTimeParseException if the string cannot be parsed */ public String minusDays(final String date, final int days) { @@ -431,7 +442,7 @@ public String minusDays(final String date, final int days) { * @param time time * @param days number of days to subtract * @return {@code days} days before {@code time}, or {@code null} if any input is {@code null} or - * {@link io.deephaven.util.QueryConstants#NULL_INT}. + * {@link io.deephaven.util.QueryConstants#NULL_INT}. */ public Instant minusDays(final Instant time, final int days) { if (time == null || days == NULL_INT) { @@ -455,7 +466,7 @@ public Instant minusDays(final Instant time, final int days) { * @param time time * @param days number of days to subtract * @return {@code days} days before {@code time}, or {@code null} if any input is {@code null} or - * {@link io.deephaven.util.QueryConstants#NULL_INT}. + * {@link io.deephaven.util.QueryConstants#NULL_INT}. */ public ZonedDateTime minusDays(final ZonedDateTime time, final int days) { if (time == null || days == NULL_INT) { @@ -470,7 +481,7 @@ public ZonedDateTime minusDays(final ZonedDateTime time, final int days) { * * @param days number of days to add. * @return {@code days} days after the current date, or {@code null} if {@code days} is - * {@link io.deephaven.util.QueryConstants#NULL_INT}. + * {@link io.deephaven.util.QueryConstants#NULL_INT}. */ public LocalDate futureDate(final int days) { return plusDays(calendarDate(), days); @@ -482,7 +493,7 @@ public LocalDate futureDate(final int days) { * * @param days number of days to subtract. * @return {@code days} days before the current date, or {@code null} if {@code days} is - * {@link io.deephaven.util.QueryConstants#NULL_INT}. + * {@link io.deephaven.util.QueryConstants#NULL_INT}. */ public LocalDate pastDate(final int days) { return minusDays(calendarDate(), days); @@ -493,8 +504,8 @@ public LocalDate pastDate(final int days) { // region Ranges private void calendarDatesInternal(final ArrayList result, final LocalDate start, final LocalDate end, - final boolean startInclusive, - final boolean endInclusive) { + final boolean startInclusive, + final boolean endInclusive) { for (LocalDate day = start; !day.isAfter(end); day = day.plusDays(1)) { final boolean skip = (!startInclusive && day.equals(start)) || (!endInclusive && day.equals(end)); @@ -507,14 +518,14 @@ private void calendarDatesInternal(final ArrayList result, final Loca /** * Returns the dates in a given range. * - * @param start start of a time range - * @param end end of a time range + * @param start start of a time range + * @param end end of a time range * @param startInclusive true to include {@code start} in the result; false to exclude {@code start} - * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} + * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} * @return dates between {@code start} and {@code end}, or {@code null} if any input is {@code null}. */ public LocalDate[] calendarDates(final LocalDate start, final LocalDate end, final boolean startInclusive, - final boolean endInclusive) { + final boolean endInclusive) { if (start == null || end == null) { return null; } @@ -529,7 +540,7 @@ public LocalDate[] calendarDates(final LocalDate start, final LocalDate end, fin SummaryData summary = null; for (Iterator it = summaryCache.iterator(start, end, startInclusive, endInclusive); it - .hasNext();) { + .hasNext(); ) { summary = it.next(); if (summaryFirst == null) { @@ -552,15 +563,15 @@ public LocalDate[] calendarDates(final LocalDate start, final LocalDate end, fin /** * Returns the dates in a given range. * - * @param start start of a time range - * @param end end of a time range + * @param start start of a time range + * @param end end of a time range * @param startInclusive true to include {@code start} in the result; false to exclude {@code start} - * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} + * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} * @return dates between {@code start} and {@code end}, or {@code null} if any input is {@code null}. * @throws DateTimeUtils.DateTimeParseException if the string cannot be parsed */ public String[] calendarDates(final String start, final String end, final boolean startInclusive, - final boolean endInclusive) { + final boolean endInclusive) { if (start == null || end == null) { return null; } @@ -574,15 +585,15 @@ public String[] calendarDates(final String start, final String end, final boolea /** * Returns the dates in a given range. * - * @param start start of a time range - * @param end end of a time range + * @param start start of a time range + * @param end end of a time range * @param startInclusive true to include {@code start} in the result; false to exclude {@code start} - * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} + * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} * @return dates between {@code start} and {@code end}, or {@code null} if any input is {@code null}. * @throws DateTimeUtils.DateTimeParseException if the string cannot be parsed */ public LocalDate[] calendarDates(final ZonedDateTime start, final ZonedDateTime end, final boolean startInclusive, - final boolean endInclusive) { + final boolean endInclusive) { if (start == null || end == null) { return null; } @@ -594,15 +605,15 @@ public LocalDate[] calendarDates(final ZonedDateTime start, final ZonedDateTime /** * Returns the dates in a given range. * - * @param start start of a time range - * @param end end of a time range + * @param start start of a time range + * @param end end of a time range * @param startInclusive true to include {@code start} in the result; false to exclude {@code start} - * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} + * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} * @return dates between {@code start} and {@code end}, or {@code null} if any input is {@code null}. * @throws DateTimeUtils.DateTimeParseException if the string cannot be parsed */ public LocalDate[] calendarDates(final Instant start, final Instant end, final boolean startInclusive, - final boolean endInclusive) { + final boolean endInclusive) { if (start == null || end == null) { return null; } @@ -615,9 +626,9 @@ public LocalDate[] calendarDates(final Instant start, final Instant end, final b * Returns the dates in a given range. * * @param start start of a time range - * @param end end of a time range + * @param end end of a time range * @return dates between {@code start} and {@code end}, including {@code start} and {@code end}, or {@code null} if - * any input is {@code null}. + * any input is {@code null}. */ public LocalDate[] calendarDates(final LocalDate start, final LocalDate end) { return calendarDates(start, end, true, true); @@ -627,9 +638,9 @@ public LocalDate[] calendarDates(final LocalDate start, final LocalDate end) { * Returns the dates in a given range. * * @param start start of a time range - * @param end end of a time range + * @param end end of a time range * @return dates between {@code start} and {@code end}, including {@code start} and {@code end}, or {@code null} if - * any input is {@code null}. + * any input is {@code null}. * @throws DateTimeUtils.DateTimeParseException if the string cannot be parsed */ public String[] calendarDates(final String start, final String end) { @@ -640,9 +651,9 @@ public String[] calendarDates(final String start, final String end) { * Returns the dates in a given range. * * @param start start of a time range - * @param end end of a time range + * @param end end of a time range * @return dates between {@code start} and {@code end}, including {@code start} and {@code end}, or {@code null} if - * any input is {@code null}. + * any input is {@code null}. */ public LocalDate[] calendarDates(final ZonedDateTime start, final ZonedDateTime end) { return calendarDates(start, end, true, true); @@ -652,9 +663,9 @@ public LocalDate[] calendarDates(final ZonedDateTime start, final ZonedDateTime * Returns the dates in a given range. * * @param start start of a time range - * @param end end of a time range + * @param end end of a time range * @return dates between {@code start} and {@code end}, including {@code start} and {@code end}, or {@code null} if - * any input is {@code null}. + * any input is {@code null}. */ public LocalDate[] calendarDates(final Instant start, final Instant end) { return calendarDates(start, end, true, true); @@ -663,15 +674,15 @@ public LocalDate[] calendarDates(final Instant start, final Instant end) { /** * Returns the number of dates in a given range. * - * @param start start of a time range - * @param end end of a time range + * @param start start of a time range + * @param end end of a time range * @param startInclusive true to include {@code start} in the result; false to exclude {@code start} - * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} + * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} * @return number of dates between {@code start} and {@code end}, or - * {@link io.deephaven.util.QueryConstants#NULL_INT} if any input is {@code null}. + * {@link io.deephaven.util.QueryConstants#NULL_INT} if any input is {@code null}. */ public int numberCalendarDates(final LocalDate start, final LocalDate end, final boolean startInclusive, - final boolean endInclusive) { + final boolean endInclusive) { if (start == null || end == null) { return NULL_INT; } @@ -692,16 +703,16 @@ public int numberCalendarDates(final LocalDate start, final LocalDate end, final /** * Returns the number of dates in a given range. * - * @param start start of a time range - * @param end end of a time range + * @param start start of a time range + * @param end end of a time range * @param startInclusive true to include {@code start} in the result; false to exclude {@code start} - * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} + * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} * @return number of dates between {@code start} and {@code end}, or - * {@link io.deephaven.util.QueryConstants#NULL_INT} if any input is {@code null}. + * {@link io.deephaven.util.QueryConstants#NULL_INT} if any input is {@code null}. * @throws DateTimeUtils.DateTimeParseException if the string cannot be parsed */ public int numberCalendarDates(final String start, final String end, final boolean startInclusive, - final boolean endInclusive) { + final boolean endInclusive) { if (start == null || end == null) { return NULL_INT; } @@ -713,16 +724,16 @@ public int numberCalendarDates(final String start, final String end, final boole /** * Returns the number of dates in a given range. * - * @param start start of a time range - * @param end end of a time range + * @param start start of a time range + * @param end end of a time range * @param startInclusive true to include {@code start} in the result; false to exclude {@code start} - * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} + * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} * @return number of dates between {@code start} and {@code end}, or - * {@link io.deephaven.util.QueryConstants#NULL_INT} if any input is {@code null}. + * {@link io.deephaven.util.QueryConstants#NULL_INT} if any input is {@code null}. * @throws DateTimeUtils.DateTimeParseException if the string cannot be parsed */ public int numberCalendarDates(final ZonedDateTime start, final ZonedDateTime end, final boolean startInclusive, - final boolean endInclusive) { + final boolean endInclusive) { if (start == null || end == null) { return NULL_INT; } @@ -734,16 +745,16 @@ public int numberCalendarDates(final ZonedDateTime start, final ZonedDateTime en /** * Returns the number of dates in a given range. * - * @param start start of a time range - * @param end end of a time range + * @param start start of a time range + * @param end end of a time range * @param startInclusive true to include {@code start} in the result; false to exclude {@code start} - * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} + * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} * @return number of dates between {@code start} and {@code end}, or - * {@link io.deephaven.util.QueryConstants#NULL_INT} if any input is {@code null}. + * {@link io.deephaven.util.QueryConstants#NULL_INT} if any input is {@code null}. * @throws DateTimeUtils.DateTimeParseException if the string cannot be parsed */ public int numberCalendarDates(final Instant start, final Instant end, final boolean startInclusive, - final boolean endInclusive) { + final boolean endInclusive) { if (start == null || end == null) { return NULL_INT; } @@ -756,9 +767,9 @@ public int numberCalendarDates(final Instant start, final Instant end, final boo * Returns the number of dates in a given range. * * @param start start of a time range - * @param end end of a time range + * @param end end of a time range * @return number of dates between {@code start} and {@code end}, including {@code start} and {@code end}, or - * {@link io.deephaven.util.QueryConstants#NULL_INT} if any input is {@code null}. + * {@link io.deephaven.util.QueryConstants#NULL_INT} if any input is {@code null}. */ public int numberCalendarDates(final LocalDate start, final LocalDate end) { return numberCalendarDates(start, end, true, true); @@ -768,9 +779,9 @@ public int numberCalendarDates(final LocalDate start, final LocalDate end) { * Returns the number of dates in a given range. * * @param start start of a time range - * @param end end of a time range + * @param end end of a time range * @return number of dates between {@code start} and {@code end}, including {@code start} and {@code end}, or - * {@link io.deephaven.util.QueryConstants#NULL_INT} if any input is {@code null}. + * {@link io.deephaven.util.QueryConstants#NULL_INT} if any input is {@code null}. * @throws DateTimeUtils.DateTimeParseException if the string cannot be parsed */ public int numberCalendarDates(final String start, final String end) { @@ -781,9 +792,9 @@ public int numberCalendarDates(final String start, final String end) { * Returns the number of dates in a given range. * * @param start start of a time range - * @param end end of a time range + * @param end end of a time range * @return number of dates between {@code start} and {@code end}, including {@code start} and {@code end}, or - * {@link io.deephaven.util.QueryConstants#NULL_INT} if any input is {@code null}. + * {@link io.deephaven.util.QueryConstants#NULL_INT} if any input is {@code null}. */ public int numberCalendarDates(final ZonedDateTime start, final ZonedDateTime end) { return numberCalendarDates(start, end, true, true); @@ -793,9 +804,9 @@ public int numberCalendarDates(final ZonedDateTime start, final ZonedDateTime en * Returns the number of dates in a given range. * * @param start start of a time range - * @param end end of a time range + * @param end end of a time range * @return number of dates between {@code start} and {@code end}, including {@code start} and {@code end}, or - * {@link io.deephaven.util.QueryConstants#NULL_INT} if any input is {@code null}. + * {@link io.deephaven.util.QueryConstants#NULL_INT} if any input is {@code null}. */ public int numberCalendarDates(final Instant start, final Instant end) { return numberCalendarDates(start, end, true, true); diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestStaticCalendarMethods.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestStaticCalendarMethods.java index 6ebb243edf7..e593571ab7b 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestStaticCalendarMethods.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestStaticCalendarMethods.java @@ -107,6 +107,7 @@ public void testAll() { excludes.add("firstValidDate"); excludes.add("lastValidDate"); excludes.add("clearCache"); + excludes.add("enableFastCache"); for (Method m1 : BusinessCalendar.class.getMethods()) { if (m1.getDeclaringClass() == Object.class || From d50afa44e3781dc46f7b872c1a4014a46323131f Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Wed, 24 Apr 2024 14:33:00 -0600 Subject: [PATCH 18/54] Incorporating generalized cache. --- .../time/calendar/BusinessCalendar.java | 25 +++- .../time/calendar/YearMonthSummaryCache.java | 119 +++++++++++++++--- .../calendar/TestYearMonthSummaryCache.java | 101 ++++++++------- 3 files changed, 182 insertions(+), 63 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java index 60cedb97fb3..2d27f6966eb 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java @@ -98,7 +98,7 @@ public SummaryData( private final int yearCacheEnd; @Override - public void clearCache() { + public synchronized void clearCache() { super.clearCache(); schedulesCache.clear(); summaryCache.clear(); @@ -217,6 +217,29 @@ private CalendarDay computeCalendarDay(final LocalDate date) { } } + /** + /** + * Enables a fast cache that improves access and computation times. + * + * @param wait whether to wait for the computation to finish + */ + public synchronized void enableFastCache(final boolean wait) { + if(schedulesCache.isFastCache() || summaryCache.isFastCache()){ + return; + } + + super.enableFastCache(firstValidDate, lastValidDate, wait); + summaryCache.enableFastCache(firstValidDate, lastValidDate, wait); + + final ArrayList dates = new ArrayList<>(); + + for (LocalDate date=firstValidDate; !date.isAfter(lastValidDate); date=date.plusDays(1)) { + dates.add(date); + } + + schedulesCache.enableFastCache(dates, wait); + } + // endregion // region Constructors diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java index 22b4ee53fc9..3bd69b1153b 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java @@ -4,9 +4,8 @@ package io.deephaven.time.calendar; import java.time.LocalDate; +import java.util.ArrayList; import java.util.Iterator; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; /** @@ -16,29 +15,111 @@ */ class YearMonthSummaryCache { - private final Map yearCache = new ConcurrentHashMap<>(); - private final Map monthCache = new ConcurrentHashMap<>(); - - private final Function computeMonthSummary; - private final Function computeYearSummary; + private final FastConcurrentCache monthCache; + private final FastConcurrentCache yearCache; + private volatile boolean fastCache = false; /** * Creates a new cache. * * @param computeMonthSummary the function to compute a month summary - * @param computeYearSummary the function to compute a year summary + * @param computeYearSummary the function to compute a year summary */ YearMonthSummaryCache(Function computeMonthSummary, Function computeYearSummary) { - this.computeMonthSummary = computeMonthSummary; - this.computeYearSummary = computeYearSummary; + monthCache = new FastConcurrentCache<>(computeMonthSummary); + yearCache = new FastConcurrentCache<>(computeYearSummary); + } + + /** + * Returns whether the fast cache is enabled. + * + * @return whether the fast cache is enabled + */ + public boolean isFastCache() { + return fastCache; + } + + /** + * Computes the summaries for the specified range and caches them. + * The map is changed from a ConcurrentHashMap to a HashMap for faster access. + * This results in faster cache access, but it limits the range of dates in the cache. + *

+ * To enable the fast cache for a different range, clear the cache first. + * + * @param startYear the start year (inclusive) + * @param startMonth the start month (inclusive) + * @param endYear the end year (inclusive) + * @param endMonth the end month (inclusive) + * @param wait whether to wait for the computation to finish + * before returning + * @throws IllegalStateException if the fast cache is already enabled + */ + synchronized void enableFastCache(final int startYear, final int startMonth, final int endYear, final int endMonth, final boolean wait) { + + if (fastCache) { + throw new IllegalStateException("Fast cache is already enabled. To change the range, clear the cache first."); + } + + fastCache = true; + + final ArrayList yearMonths = new ArrayList<>(); + + for (int year = startYear; year <= endYear; year++) { + for (int month = (year == startYear ? startMonth : 1); month <= (year == endYear ? endMonth : 12); month++) { + yearMonths.add(year * 100 + month); + } + } + + monthCache.enableFastCache(yearMonths, wait); + + final ArrayList years = new ArrayList<>(); + + for (int year = (startMonth == 1 ? startYear : startYear + 1); year <= (endMonth == 12 ? endYear : endYear - 1); year++) { + years.add(year); + } + + yearCache.enableFastCache(years, wait); + } + + /** + * Computes the summaries for the specified range and caches them. + * The map is changed from a ConcurrentHashMap to a HashMap for faster access. + * This results in faster cache access, but it limits the range of dates in the cache. + *

+ * To enable the fast cache for a different range, clear the cache first. + * + * @param start the start date (inclusive) + * @param end the end date (inclusive) + * @param wait whether to wait for the computation to finish + * before returning + * @throws IllegalStateException if the fast cache is already enabled + */ + void enableFastCache(LocalDate start, LocalDate end, final boolean wait) { + // Ensure only full months are computed + + // Skip the first month if the start date is not the first day of the month + if (start.getDayOfMonth() != 1) { + start = start.withDayOfMonth(1).plusMonths(1); + } + + // Skip the last month if the end date is not the last day of the month + + final LocalDate endPlus1 = end.plusDays(1); + + if (end.getMonth() == endPlus1.getMonth()) { + end = end.withDayOfMonth(1).minusMonths(1); + } + + enableFastCache(start.getYear(), start.getMonthValue(), end.getYear(), end.getMonthValue(), wait); } /** * Clears the cache. */ - void clear() { - yearCache.clear(); + synchronized void clear() { + fastCache = false; monthCache.clear(); + yearCache.clear(); } /** @@ -48,13 +129,13 @@ void clear() { * @return the month summary */ T getMonthSummary(int yearMonth) { - return monthCache.computeIfAbsent(yearMonth, computeMonthSummary); + return monthCache.get(yearMonth); } /** * Gets the month summary for the specified year and month. * - * @param year the year + * @param year the year * @param month the month * @return the month summary */ @@ -69,7 +150,7 @@ T getMonthSummary(int year, int month) { * @return the year summary */ T getYearSummary(int year) { - return yearCache.computeIfAbsent(year, computeYearSummary); + return yearCache.get(year); } private class YearMonthSummaryIterator implements Iterator { @@ -159,14 +240,14 @@ public T next() { * The iterator will return summaries in chronological order, and these summaries can be a mix of month and year * summaries. Dates not represented by complete summaries will be skipped (e.g. partial months). * - * @param start the start date - * @param end the end date + * @param start the start date + * @param end the end date * @param startInclusive whether the start date is inclusive - * @param endInclusive whether the end date is inclusive + * @param endInclusive whether the end date is inclusive * @return the iterator */ Iterator iterator(final LocalDate start, final LocalDate end, - final boolean startInclusive, final boolean endInclusive) { + final boolean startInclusive, final boolean endInclusive) { return new YearMonthSummaryIterator(startInclusive ? start : start.plusDays(1), endInclusive ? end : end.minusDays(1)); } diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java index 6794e97fb59..0fa9cab5fd7 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java @@ -31,49 +31,64 @@ public void testGetters() { }; final YearMonthSummaryCache cache = new YearMonthSummaryCache<>(monthSummary, yearSummary); - assertEquals("month202101", cache.getMonthSummary(202101)); - assertEquals(1, monthCount[0]); - assertEquals(0, yearCount[0]); - assertEquals("year2021", cache.getYearSummary(2021)); - assertEquals(1, monthCount[0]); - assertEquals(1, yearCount[0]); - assertEquals("month202101", cache.getMonthSummary(202101)); - assertEquals(1, monthCount[0]); - assertEquals(1, yearCount[0]); - assertEquals("year2021", cache.getYearSummary(2021)); - assertEquals(1, monthCount[0]); - assertEquals(1, yearCount[0]); - - assertEquals("month202102", cache.getMonthSummary(202102)); - assertEquals(2, monthCount[0]); - assertEquals(1, yearCount[0]); - assertEquals("year2022", cache.getYearSummary(2022)); - assertEquals(2, monthCount[0]); - assertEquals(2, yearCount[0]); - - cache.clear(); - - assertEquals("month202101", cache.getMonthSummary(202101)); - assertEquals(3, monthCount[0]); - assertEquals(2, yearCount[0]); - assertEquals("year2021", cache.getYearSummary(2021)); - assertEquals(3, monthCount[0]); - assertEquals(3, yearCount[0]); - assertEquals("month202101", cache.getMonthSummary(202101)); - assertEquals(3, monthCount[0]); - assertEquals(3, yearCount[0]); - assertEquals("year2021", cache.getYearSummary(2021)); - assertEquals(3, monthCount[0]); - assertEquals(3, yearCount[0]); - - assertEquals("month202102", cache.getMonthSummary(202102)); - assertEquals(4, monthCount[0]); - assertEquals(3, yearCount[0]); - assertEquals("year2022", cache.getYearSummary(2022)); - assertEquals(4, monthCount[0]); - assertEquals(4, yearCount[0]); - - assertEquals(cache.getMonthSummary(202101), cache.getMonthSummary(2021, 1)); + + for(boolean fastCache : new boolean[] {false, true}) { + cache.clear(); + monthCount[0] = 0; + yearCount[0] = 0; + + if(fastCache) { + final LocalDate start = LocalDate.of(2020, 12, 12); + final LocalDate end = LocalDate.of(2023, 1, 7); + cache.enableFastCache(start, end, true); + } + + assertEquals(fastCache, cache.isFastCache()); + + assertEquals("month202101", cache.getMonthSummary(202101)); + assertEquals(fastCache ? 24 : 1, monthCount[0]); + assertEquals(fastCache ? 2 : 0, yearCount[0]); + assertEquals("year2021", cache.getYearSummary(2021)); + assertEquals(fastCache ? 24 : 1, monthCount[0]); + assertEquals(fastCache ? 2 :1, yearCount[0]); + assertEquals("month202101", cache.getMonthSummary(202101)); + assertEquals(fastCache ? 24 : 1, monthCount[0]); + assertEquals(fastCache ? 2 :1, yearCount[0]); + assertEquals("year2021", cache.getYearSummary(2021)); + assertEquals(fastCache ? 24 : 1, monthCount[0]); + assertEquals(fastCache ? 2 :1, yearCount[0]); + + assertEquals("month202102", cache.getMonthSummary(202102)); + assertEquals(fastCache ? 24 : 2, monthCount[0]); + assertEquals(fastCache ? 2 :1, yearCount[0]); + assertEquals("year2022", cache.getYearSummary(2022)); + assertEquals(fastCache ? 24 : 2, monthCount[0]); + assertEquals(fastCache ? 2 :2, yearCount[0]); + + cache.clear(); + + assertEquals("month202101", cache.getMonthSummary(202101)); + assertEquals(fastCache ? 25 : 3, monthCount[0]); + assertEquals(fastCache ? 2: 2, yearCount[0]); + assertEquals("year2021", cache.getYearSummary(2021)); + assertEquals(fastCache ? 25: 3, monthCount[0]); + assertEquals(fastCache ? 3: 3, yearCount[0]); + assertEquals("month202101", cache.getMonthSummary(202101)); + assertEquals(fastCache ? 25: 3, monthCount[0]); + assertEquals(3, yearCount[0]); + assertEquals("year2021", cache.getYearSummary(2021)); + assertEquals(fastCache ? 25: 3, monthCount[0]); + assertEquals(3, yearCount[0]); + + assertEquals("month202102", cache.getMonthSummary(202102)); + assertEquals(fastCache ? 26: 4, monthCount[0]); + assertEquals(3, yearCount[0]); + assertEquals("year2022", cache.getYearSummary(2022)); + assertEquals(fastCache ? 26: 4, monthCount[0]); + assertEquals(4, yearCount[0]); + + assertEquals(cache.getMonthSummary(202101), cache.getMonthSummary(2021, 1)); + } } private static Stream iteratorToStream(Iterator iterator) { From 5b0fe5434e3cc89410d377f5fa7687661ca298fb Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Wed, 24 Apr 2024 14:50:30 -0600 Subject: [PATCH 19/54] Incorporating generalized cache. --- .../time/calendar/YearMonthSummaryCache.java | 4 ++-- .../time/calendar/TestYearMonthSummaryCache.java | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java index 3bd69b1153b..0893f2e0b12 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java @@ -17,7 +17,7 @@ class YearMonthSummaryCache { private final FastConcurrentCache monthCache; private final FastConcurrentCache yearCache; - private volatile boolean fastCache = false; + private volatile boolean fastCache = false; // synchronized /** * Creates a new cache. @@ -35,7 +35,7 @@ class YearMonthSummaryCache { * * @return whether the fast cache is enabled */ - public boolean isFastCache() { + public synchronized boolean isFastCache() { return fastCache; } diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java index 0fa9cab5fd7..a019b942037 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java @@ -65,6 +65,18 @@ public void testGetters() { assertEquals(fastCache ? 24 : 2, monthCount[0]); assertEquals(fastCache ? 2 :2, yearCount[0]); + if(fastCache) { + try { + // check enabling the cache 2x + final LocalDate start = LocalDate.of(2020, 12, 12); + final LocalDate end = LocalDate.of(2023, 1, 7); + cache.enableFastCache(start, end, true); + fail("Expected IllegalStateException because the cache is already enabled"); + } catch (IllegalStateException e) { + // expected + } + } + cache.clear(); assertEquals("month202101", cache.getMonthSummary(202101)); From d0cde5a728bed691cde4da84196a50a11a89e4be Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Wed, 24 Apr 2024 15:03:00 -0600 Subject: [PATCH 20/54] Incorporating generalized cache. --- .../libs/StaticCalendarMethodsGenerator.java | 1 + .../io/deephaven/time/calendar/Calendar.java | 16 ++++++++++++-- .../time/calendar/TestBusinessCalendar.java | 22 +++++++++++++++++++ .../deephaven/time/calendar/TestCalendar.java | 15 +++++++++++++ .../calendar/TestStaticCalendarMethods.java | 1 + 5 files changed, 53 insertions(+), 2 deletions(-) diff --git a/Generators/src/main/java/io/deephaven/libs/StaticCalendarMethodsGenerator.java b/Generators/src/main/java/io/deephaven/libs/StaticCalendarMethodsGenerator.java index dc9b015b253..320b1ac1d7e 100644 --- a/Generators/src/main/java/io/deephaven/libs/StaticCalendarMethodsGenerator.java +++ b/Generators/src/main/java/io/deephaven/libs/StaticCalendarMethodsGenerator.java @@ -80,6 +80,7 @@ public static void main(String[] args) throws ClassNotFoundException, IOExceptio excludes.add("lastValidDate"); excludes.add("clearCache"); excludes.add("enableFastCache"); + excludes.add("isFastCache"); StaticCalendarMethodsGenerator gen = new StaticCalendarMethodsGenerator(gradleTask, packageName, className, imports, diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java index 7061774224c..8d310fd58fb 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java @@ -27,6 +27,7 @@ public class Calendar { private final String name; private final String description; private final ZoneId timeZone; + private boolean fastCache = false; // synchronized // region Cache @@ -94,7 +95,8 @@ private SummaryData computeYearSummary(final int year) { * @param end the end date * @param wait whether to wait for the computation to finish */ - protected void enableFastCache(final LocalDate start, final LocalDate end, final boolean wait) { + protected synchronized void enableFastCache(final LocalDate start, final LocalDate end, final boolean wait) { + fastCache = true; summaryCache.enableFastCache(start, end, wait); } @@ -123,10 +125,20 @@ protected void enableFastCache(final LocalDate start, final LocalDate end, final /** * Clears the cache. This should not generally be used and is provided for benchmarking. */ - public void clearCache() { + public synchronized void clearCache() { + fastCache = false; summaryCache.clear(); } + /** + * Returns whether the fast cache is enabled. + * + * @return whether the fast cache is enabled + */ + public synchronized boolean isFastCache() { + return fastCache; + } + // endregion // region Getters diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestBusinessCalendar.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestBusinessCalendar.java index bec390fcc9c..a40587879b5 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestBusinessCalendar.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestBusinessCalendar.java @@ -1612,4 +1612,26 @@ public void testClearCache() { final int i4 = bCalendar.numberBusinessDates(start, end); assertEquals(i3, i4); } + + public void testFastCache() { + final LocalDate start = LocalDate.of(2018, 2, 3); + final LocalDate end = LocalDate.of(2023, 2, 5); + + bCalendar.clearCache(); + assertFalse(bCalendar.isFastCache()); + final LocalDate[] calDates = bCalendar.calendarDates(start, end); + final LocalDate[] busDates = bCalendar.businessDates(start, end); + final LocalDate[] nonBusDates = bCalendar.nonBusinessDates(start, end); + + bCalendar.enableFastCache(true); + assertTrue(bCalendar.isFastCache()); + final LocalDate[] calDates2 = bCalendar.calendarDates(start, end); + final LocalDate[] busDates2 = bCalendar.businessDates(start, end); + final LocalDate[] nonBusDates2 = bCalendar.nonBusinessDates(start, end); + + assertEquals(calDates, calDates2); + assertEquals(busDates, busDates2); + assertEquals(nonBusDates, nonBusDates2); + } + } diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestCalendar.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestCalendar.java index b0cd72dba1a..01cd509dda6 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestCalendar.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestCalendar.java @@ -282,4 +282,19 @@ public void testNumberCalendarDates() { assertEquals(NULL_INT, calendar.numberCalendarDates(null, end.atTime(3, 15).atZone(timeZone), false, false)); assertEquals(NULL_INT, calendar.numberCalendarDates(start.atTime(3, 15).atZone(timeZone), null, false, false)); } + + public void testFastCache() { + final LocalDate start = LocalDate.of(2018, 2, 3); + final LocalDate end = LocalDate.of(2023, 2, 5); + + calendar.clearCache(); + assertFalse(calendar.isFastCache()); + final LocalDate[] dates = calendar.calendarDates(start, end); + + calendar.enableFastCache(start, end, true); + assertTrue(calendar.isFastCache()); + final LocalDate[] dates2 = calendar.calendarDates(start, end); + + assertEquals(dates, dates2); + } } diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestStaticCalendarMethods.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestStaticCalendarMethods.java index e593571ab7b..31f738d8dbf 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestStaticCalendarMethods.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestStaticCalendarMethods.java @@ -108,6 +108,7 @@ public void testAll() { excludes.add("lastValidDate"); excludes.add("clearCache"); excludes.add("enableFastCache"); + excludes.add("isFastCache"); for (Method m1 : BusinessCalendar.class.getMethods()) { if (m1.getDeclaringClass() == Object.class || From b12453d69088ef34498bf919310f2922f87a390b Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Wed, 24 Apr 2024 15:46:08 -0600 Subject: [PATCH 21/54] Configs to control fast cache population. --- .../java/io/deephaven/time/calendar/BusinessCalendar.java | 5 +++-- .../src/main/java/io/deephaven/time/calendar/Calendar.java | 6 ++++++ .../main/java/io/deephaven/time/calendar/Calendars.java | 7 +++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java index 2d27f6966eb..651d44cc825 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java @@ -218,13 +218,14 @@ private CalendarDay computeCalendarDay(final LocalDate date) { } /** - /** * Enables a fast cache that improves access and computation times. + *

+ * If the cache is already enabled, this method does nothing. * * @param wait whether to wait for the computation to finish */ public synchronized void enableFastCache(final boolean wait) { - if(schedulesCache.isFastCache() || summaryCache.isFastCache()){ + if(isFastCache()){ return; } diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java index 8d310fd58fb..69a525d4343 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java @@ -90,12 +90,18 @@ private SummaryData computeYearSummary(final int year) { /** * Enables a fast cache that improves access and computation times. + *

+ * If the cache is already enabled, this method does nothing. * * @param start the start date * @param end the end date * @param wait whether to wait for the computation to finish */ protected synchronized void enableFastCache(final LocalDate start, final LocalDate end, final boolean wait) { + if(fastCache) { + return; + } + fastCache = true; summaryCache.enableFastCache(start, end, wait); } diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/Calendars.java b/engine/time/src/main/java/io/deephaven/time/calendar/Calendars.java index c0743d01a35..c079c2e5575 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/Calendars.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/Calendars.java @@ -27,6 +27,8 @@ public class Calendars { private static final String BUSINESS_CALENDAR_PROP_INTERNAL = "Calendar.importPath"; private static final String BUSINESS_CALENDAR_PROP_USER = "Calendar.userImportPath"; private static String defaultName = Configuration.getInstance().getProperty("Calendar.default"); + private static final boolean USE_FAST_CACHE = Configuration.getInstance().getBooleanWithDefault("Calendar.useFastCache", true); + private static final boolean WAIT_ON_FAST_CACHE = Configuration.getInstance().getBooleanWithDefault("Calendar.waitOnFastCache", false); private static final Map calMap = new TreeMap<>(); /** @@ -107,6 +109,11 @@ public synchronized static void addCalendar(final BusinessCalendar cal) { throw new IllegalArgumentException("Multiple calendars have the same name: name='" + name + "'"); } + if(USE_FAST_CACHE) { + logger.info("Enabling fast cache for calendar: name=" + name + " waitOnFastCache=" + WAIT_ON_FAST_CACHE); + cal.enableFastCache(WAIT_ON_FAST_CACHE); + } + map.put(name, cal); } From 9a2892871f6f0aa33f686645c7f15a96ca44e0ce Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Wed, 24 Apr 2024 15:47:33 -0600 Subject: [PATCH 22/54] Spotless --- .../time/calendar/BusinessCalendar.java | 6 +- .../io/deephaven/time/calendar/Calendar.java | 148 +++++++++--------- .../io/deephaven/time/calendar/Calendars.java | 10 +- .../time/calendar/FastConcurrentCache.java | 29 ++-- .../time/calendar/YearMonthSummaryCache.java | 50 +++--- .../calendar/TestFastConcurrentCache.java | 8 +- .../calendar/TestYearMonthSummaryCache.java | 30 ++-- 7 files changed, 142 insertions(+), 139 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java index 651d44cc825..1d8084dc254 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java @@ -222,10 +222,10 @@ private CalendarDay computeCalendarDay(final LocalDate date) { *

* If the cache is already enabled, this method does nothing. * - * @param wait whether to wait for the computation to finish + * @param wait whether to wait for the computation to finish */ public synchronized void enableFastCache(final boolean wait) { - if(isFastCache()){ + if (isFastCache()) { return; } @@ -234,7 +234,7 @@ public synchronized void enableFastCache(final boolean wait) { final ArrayList dates = new ArrayList<>(); - for (LocalDate date=firstValidDate; !date.isAfter(lastValidDate); date=date.plusDays(1)) { + for (LocalDate date = firstValidDate; !date.isAfter(lastValidDate); date = date.plusDays(1)) { dates.add(date); } diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java index 69a525d4343..9c8795b5b55 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java @@ -94,11 +94,11 @@ private SummaryData computeYearSummary(final int year) { * If the cache is already enabled, this method does nothing. * * @param start the start date - * @param end the end date - * @param wait whether to wait for the computation to finish + * @param end the end date + * @param wait whether to wait for the computation to finish */ protected synchronized void enableFastCache(final LocalDate start, final LocalDate end, final boolean wait) { - if(fastCache) { + if (fastCache) { return; } @@ -113,9 +113,9 @@ protected synchronized void enableFastCache(final LocalDate start, final LocalDa /** * Creates a new calendar. * - * @param name calendar name. + * @param name calendar name. * @param description calendar description. - * @param timeZone calendar time zone. + * @param timeZone calendar time zone. * @throws RequirementFailure if {@code name} or {@code timeZone} is {@code null} */ Calendar(final String name, final String description, final ZoneId timeZone) { @@ -273,7 +273,7 @@ public int dayOfWeekValue() { * * @param date date * @return current day of the week, or {@link io.deephaven.util.QueryConstants#NULL_INT} if the input is - * {@code null}. + * {@code null}. */ public int dayOfWeekValue(final LocalDate date) { if (date == null) { @@ -288,7 +288,7 @@ public int dayOfWeekValue(final LocalDate date) { * * @param date date * @return current day of the week, or {@link io.deephaven.util.QueryConstants#NULL_INT} if the input is - * {@code null}. + * {@code null}. */ public int dayOfWeekValue(final String date) { if (date == null) { @@ -303,7 +303,7 @@ public int dayOfWeekValue(final String date) { * * @param time time * @return current day of the week, or {@link io.deephaven.util.QueryConstants#NULL_INT} if the input is - * {@code null}. + * {@code null}. */ public int dayOfWeekValue(final Instant time) { if (time == null) { @@ -318,7 +318,7 @@ public int dayOfWeekValue(final Instant time) { * * @param time time * @return current day of the week, or {@link io.deephaven.util.QueryConstants#NULL_INT} if the input is - * {@code null}. + * {@code null}. */ public int dayOfWeekValue(final ZonedDateTime time) { if (time == null) { @@ -339,7 +339,7 @@ public int dayOfWeekValue(final ZonedDateTime time) { * @param date date * @param days number of days to add * @return {@code days} days after {@code date}, or {@code null} if any input is {@code null} or - * {@link io.deephaven.util.QueryConstants#NULL_INT}. + * {@link io.deephaven.util.QueryConstants#NULL_INT}. */ public LocalDate plusDays(final LocalDate date, final int days) { if (date == null || days == NULL_INT) { @@ -355,7 +355,7 @@ public LocalDate plusDays(final LocalDate date, final int days) { * @param date date * @param days number of days to add * @return {@code days} days after {@code date}, or {@code null} if any input is {@code null} or - * {@link io.deephaven.util.QueryConstants#NULL_INT}. + * {@link io.deephaven.util.QueryConstants#NULL_INT}. * @throws DateTimeUtils.DateTimeParseException if the string cannot be parsed */ public String plusDays(final String date, final int days) { @@ -378,7 +378,7 @@ public String plusDays(final String date, final int days) { * @param time time * @param days number of days to add * @return {@code days} days after {@code time}, or {@code null} if any input is {@code null} or - * {@link io.deephaven.util.QueryConstants#NULL_INT}. + * {@link io.deephaven.util.QueryConstants#NULL_INT}. */ public Instant plusDays(final Instant time, final int days) { if (time == null || days == NULL_INT) { @@ -402,7 +402,7 @@ public Instant plusDays(final Instant time, final int days) { * @param time time * @param days number of days to add * @return {@code days} days after {@code time}, or {@code null} if any input is {@code null} or - * {@link io.deephaven.util.QueryConstants#NULL_INT}. + * {@link io.deephaven.util.QueryConstants#NULL_INT}. */ public ZonedDateTime plusDays(final ZonedDateTime time, final int days) { if (time == null || days == NULL_INT) { @@ -421,7 +421,7 @@ public ZonedDateTime plusDays(final ZonedDateTime time, final int days) { * @param date date * @param days number of days to subtract * @return {@code days} days before {@code date}, or {@code null} if any input is {@code null} or - * {@link io.deephaven.util.QueryConstants#NULL_INT}. + * {@link io.deephaven.util.QueryConstants#NULL_INT}. */ public LocalDate minusDays(final LocalDate date, final int days) { if (date == null || days == NULL_INT) { @@ -437,7 +437,7 @@ public LocalDate minusDays(final LocalDate date, final int days) { * @param date date * @param days number of days to subtract * @return {@code days} days before {@code date}, or {@code null} if any input is {@code null} or - * {@link io.deephaven.util.QueryConstants#NULL_INT}. + * {@link io.deephaven.util.QueryConstants#NULL_INT}. * @throws DateTimeUtils.DateTimeParseException if the string cannot be parsed */ public String minusDays(final String date, final int days) { @@ -460,7 +460,7 @@ public String minusDays(final String date, final int days) { * @param time time * @param days number of days to subtract * @return {@code days} days before {@code time}, or {@code null} if any input is {@code null} or - * {@link io.deephaven.util.QueryConstants#NULL_INT}. + * {@link io.deephaven.util.QueryConstants#NULL_INT}. */ public Instant minusDays(final Instant time, final int days) { if (time == null || days == NULL_INT) { @@ -484,7 +484,7 @@ public Instant minusDays(final Instant time, final int days) { * @param time time * @param days number of days to subtract * @return {@code days} days before {@code time}, or {@code null} if any input is {@code null} or - * {@link io.deephaven.util.QueryConstants#NULL_INT}. + * {@link io.deephaven.util.QueryConstants#NULL_INT}. */ public ZonedDateTime minusDays(final ZonedDateTime time, final int days) { if (time == null || days == NULL_INT) { @@ -499,7 +499,7 @@ public ZonedDateTime minusDays(final ZonedDateTime time, final int days) { * * @param days number of days to add. * @return {@code days} days after the current date, or {@code null} if {@code days} is - * {@link io.deephaven.util.QueryConstants#NULL_INT}. + * {@link io.deephaven.util.QueryConstants#NULL_INT}. */ public LocalDate futureDate(final int days) { return plusDays(calendarDate(), days); @@ -511,7 +511,7 @@ public LocalDate futureDate(final int days) { * * @param days number of days to subtract. * @return {@code days} days before the current date, or {@code null} if {@code days} is - * {@link io.deephaven.util.QueryConstants#NULL_INT}. + * {@link io.deephaven.util.QueryConstants#NULL_INT}. */ public LocalDate pastDate(final int days) { return minusDays(calendarDate(), days); @@ -522,8 +522,8 @@ public LocalDate pastDate(final int days) { // region Ranges private void calendarDatesInternal(final ArrayList result, final LocalDate start, final LocalDate end, - final boolean startInclusive, - final boolean endInclusive) { + final boolean startInclusive, + final boolean endInclusive) { for (LocalDate day = start; !day.isAfter(end); day = day.plusDays(1)) { final boolean skip = (!startInclusive && day.equals(start)) || (!endInclusive && day.equals(end)); @@ -536,14 +536,14 @@ private void calendarDatesInternal(final ArrayList result, final Loca /** * Returns the dates in a given range. * - * @param start start of a time range - * @param end end of a time range + * @param start start of a time range + * @param end end of a time range * @param startInclusive true to include {@code start} in the result; false to exclude {@code start} - * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} + * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} * @return dates between {@code start} and {@code end}, or {@code null} if any input is {@code null}. */ public LocalDate[] calendarDates(final LocalDate start, final LocalDate end, final boolean startInclusive, - final boolean endInclusive) { + final boolean endInclusive) { if (start == null || end == null) { return null; } @@ -558,7 +558,7 @@ public LocalDate[] calendarDates(final LocalDate start, final LocalDate end, fin SummaryData summary = null; for (Iterator it = summaryCache.iterator(start, end, startInclusive, endInclusive); it - .hasNext(); ) { + .hasNext();) { summary = it.next(); if (summaryFirst == null) { @@ -581,15 +581,15 @@ public LocalDate[] calendarDates(final LocalDate start, final LocalDate end, fin /** * Returns the dates in a given range. * - * @param start start of a time range - * @param end end of a time range + * @param start start of a time range + * @param end end of a time range * @param startInclusive true to include {@code start} in the result; false to exclude {@code start} - * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} + * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} * @return dates between {@code start} and {@code end}, or {@code null} if any input is {@code null}. * @throws DateTimeUtils.DateTimeParseException if the string cannot be parsed */ public String[] calendarDates(final String start, final String end, final boolean startInclusive, - final boolean endInclusive) { + final boolean endInclusive) { if (start == null || end == null) { return null; } @@ -603,15 +603,15 @@ public String[] calendarDates(final String start, final String end, final boolea /** * Returns the dates in a given range. * - * @param start start of a time range - * @param end end of a time range + * @param start start of a time range + * @param end end of a time range * @param startInclusive true to include {@code start} in the result; false to exclude {@code start} - * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} + * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} * @return dates between {@code start} and {@code end}, or {@code null} if any input is {@code null}. * @throws DateTimeUtils.DateTimeParseException if the string cannot be parsed */ public LocalDate[] calendarDates(final ZonedDateTime start, final ZonedDateTime end, final boolean startInclusive, - final boolean endInclusive) { + final boolean endInclusive) { if (start == null || end == null) { return null; } @@ -623,15 +623,15 @@ public LocalDate[] calendarDates(final ZonedDateTime start, final ZonedDateTime /** * Returns the dates in a given range. * - * @param start start of a time range - * @param end end of a time range + * @param start start of a time range + * @param end end of a time range * @param startInclusive true to include {@code start} in the result; false to exclude {@code start} - * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} + * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} * @return dates between {@code start} and {@code end}, or {@code null} if any input is {@code null}. * @throws DateTimeUtils.DateTimeParseException if the string cannot be parsed */ public LocalDate[] calendarDates(final Instant start, final Instant end, final boolean startInclusive, - final boolean endInclusive) { + final boolean endInclusive) { if (start == null || end == null) { return null; } @@ -644,9 +644,9 @@ public LocalDate[] calendarDates(final Instant start, final Instant end, final b * Returns the dates in a given range. * * @param start start of a time range - * @param end end of a time range + * @param end end of a time range * @return dates between {@code start} and {@code end}, including {@code start} and {@code end}, or {@code null} if - * any input is {@code null}. + * any input is {@code null}. */ public LocalDate[] calendarDates(final LocalDate start, final LocalDate end) { return calendarDates(start, end, true, true); @@ -656,9 +656,9 @@ public LocalDate[] calendarDates(final LocalDate start, final LocalDate end) { * Returns the dates in a given range. * * @param start start of a time range - * @param end end of a time range + * @param end end of a time range * @return dates between {@code start} and {@code end}, including {@code start} and {@code end}, or {@code null} if - * any input is {@code null}. + * any input is {@code null}. * @throws DateTimeUtils.DateTimeParseException if the string cannot be parsed */ public String[] calendarDates(final String start, final String end) { @@ -669,9 +669,9 @@ public String[] calendarDates(final String start, final String end) { * Returns the dates in a given range. * * @param start start of a time range - * @param end end of a time range + * @param end end of a time range * @return dates between {@code start} and {@code end}, including {@code start} and {@code end}, or {@code null} if - * any input is {@code null}. + * any input is {@code null}. */ public LocalDate[] calendarDates(final ZonedDateTime start, final ZonedDateTime end) { return calendarDates(start, end, true, true); @@ -681,9 +681,9 @@ public LocalDate[] calendarDates(final ZonedDateTime start, final ZonedDateTime * Returns the dates in a given range. * * @param start start of a time range - * @param end end of a time range + * @param end end of a time range * @return dates between {@code start} and {@code end}, including {@code start} and {@code end}, or {@code null} if - * any input is {@code null}. + * any input is {@code null}. */ public LocalDate[] calendarDates(final Instant start, final Instant end) { return calendarDates(start, end, true, true); @@ -692,15 +692,15 @@ public LocalDate[] calendarDates(final Instant start, final Instant end) { /** * Returns the number of dates in a given range. * - * @param start start of a time range - * @param end end of a time range + * @param start start of a time range + * @param end end of a time range * @param startInclusive true to include {@code start} in the result; false to exclude {@code start} - * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} + * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} * @return number of dates between {@code start} and {@code end}, or - * {@link io.deephaven.util.QueryConstants#NULL_INT} if any input is {@code null}. + * {@link io.deephaven.util.QueryConstants#NULL_INT} if any input is {@code null}. */ public int numberCalendarDates(final LocalDate start, final LocalDate end, final boolean startInclusive, - final boolean endInclusive) { + final boolean endInclusive) { if (start == null || end == null) { return NULL_INT; } @@ -721,16 +721,16 @@ public int numberCalendarDates(final LocalDate start, final LocalDate end, final /** * Returns the number of dates in a given range. * - * @param start start of a time range - * @param end end of a time range + * @param start start of a time range + * @param end end of a time range * @param startInclusive true to include {@code start} in the result; false to exclude {@code start} - * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} + * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} * @return number of dates between {@code start} and {@code end}, or - * {@link io.deephaven.util.QueryConstants#NULL_INT} if any input is {@code null}. + * {@link io.deephaven.util.QueryConstants#NULL_INT} if any input is {@code null}. * @throws DateTimeUtils.DateTimeParseException if the string cannot be parsed */ public int numberCalendarDates(final String start, final String end, final boolean startInclusive, - final boolean endInclusive) { + final boolean endInclusive) { if (start == null || end == null) { return NULL_INT; } @@ -742,16 +742,16 @@ public int numberCalendarDates(final String start, final String end, final boole /** * Returns the number of dates in a given range. * - * @param start start of a time range - * @param end end of a time range + * @param start start of a time range + * @param end end of a time range * @param startInclusive true to include {@code start} in the result; false to exclude {@code start} - * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} + * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} * @return number of dates between {@code start} and {@code end}, or - * {@link io.deephaven.util.QueryConstants#NULL_INT} if any input is {@code null}. + * {@link io.deephaven.util.QueryConstants#NULL_INT} if any input is {@code null}. * @throws DateTimeUtils.DateTimeParseException if the string cannot be parsed */ public int numberCalendarDates(final ZonedDateTime start, final ZonedDateTime end, final boolean startInclusive, - final boolean endInclusive) { + final boolean endInclusive) { if (start == null || end == null) { return NULL_INT; } @@ -763,16 +763,16 @@ public int numberCalendarDates(final ZonedDateTime start, final ZonedDateTime en /** * Returns the number of dates in a given range. * - * @param start start of a time range - * @param end end of a time range + * @param start start of a time range + * @param end end of a time range * @param startInclusive true to include {@code start} in the result; false to exclude {@code start} - * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} + * @param endInclusive true to include {@code end} in the result; false to exclude {@code end} * @return number of dates between {@code start} and {@code end}, or - * {@link io.deephaven.util.QueryConstants#NULL_INT} if any input is {@code null}. + * {@link io.deephaven.util.QueryConstants#NULL_INT} if any input is {@code null}. * @throws DateTimeUtils.DateTimeParseException if the string cannot be parsed */ public int numberCalendarDates(final Instant start, final Instant end, final boolean startInclusive, - final boolean endInclusive) { + final boolean endInclusive) { if (start == null || end == null) { return NULL_INT; } @@ -785,9 +785,9 @@ public int numberCalendarDates(final Instant start, final Instant end, final boo * Returns the number of dates in a given range. * * @param start start of a time range - * @param end end of a time range + * @param end end of a time range * @return number of dates between {@code start} and {@code end}, including {@code start} and {@code end}, or - * {@link io.deephaven.util.QueryConstants#NULL_INT} if any input is {@code null}. + * {@link io.deephaven.util.QueryConstants#NULL_INT} if any input is {@code null}. */ public int numberCalendarDates(final LocalDate start, final LocalDate end) { return numberCalendarDates(start, end, true, true); @@ -797,9 +797,9 @@ public int numberCalendarDates(final LocalDate start, final LocalDate end) { * Returns the number of dates in a given range. * * @param start start of a time range - * @param end end of a time range + * @param end end of a time range * @return number of dates between {@code start} and {@code end}, including {@code start} and {@code end}, or - * {@link io.deephaven.util.QueryConstants#NULL_INT} if any input is {@code null}. + * {@link io.deephaven.util.QueryConstants#NULL_INT} if any input is {@code null}. * @throws DateTimeUtils.DateTimeParseException if the string cannot be parsed */ public int numberCalendarDates(final String start, final String end) { @@ -810,9 +810,9 @@ public int numberCalendarDates(final String start, final String end) { * Returns the number of dates in a given range. * * @param start start of a time range - * @param end end of a time range + * @param end end of a time range * @return number of dates between {@code start} and {@code end}, including {@code start} and {@code end}, or - * {@link io.deephaven.util.QueryConstants#NULL_INT} if any input is {@code null}. + * {@link io.deephaven.util.QueryConstants#NULL_INT} if any input is {@code null}. */ public int numberCalendarDates(final ZonedDateTime start, final ZonedDateTime end) { return numberCalendarDates(start, end, true, true); @@ -822,9 +822,9 @@ public int numberCalendarDates(final ZonedDateTime start, final ZonedDateTime en * Returns the number of dates in a given range. * * @param start start of a time range - * @param end end of a time range + * @param end end of a time range * @return number of dates between {@code start} and {@code end}, including {@code start} and {@code end}, or - * {@link io.deephaven.util.QueryConstants#NULL_INT} if any input is {@code null}. + * {@link io.deephaven.util.QueryConstants#NULL_INT} if any input is {@code null}. */ public int numberCalendarDates(final Instant start, final Instant end) { return numberCalendarDates(start, end, true, true); diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/Calendars.java b/engine/time/src/main/java/io/deephaven/time/calendar/Calendars.java index c079c2e5575..ee7f7a3f92a 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/Calendars.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/Calendars.java @@ -27,8 +27,10 @@ public class Calendars { private static final String BUSINESS_CALENDAR_PROP_INTERNAL = "Calendar.importPath"; private static final String BUSINESS_CALENDAR_PROP_USER = "Calendar.userImportPath"; private static String defaultName = Configuration.getInstance().getProperty("Calendar.default"); - private static final boolean USE_FAST_CACHE = Configuration.getInstance().getBooleanWithDefault("Calendar.useFastCache", true); - private static final boolean WAIT_ON_FAST_CACHE = Configuration.getInstance().getBooleanWithDefault("Calendar.waitOnFastCache", false); + private static final boolean USE_FAST_CACHE = + Configuration.getInstance().getBooleanWithDefault("Calendar.useFastCache", true); + private static final boolean WAIT_ON_FAST_CACHE = + Configuration.getInstance().getBooleanWithDefault("Calendar.waitOnFastCache", false); private static final Map calMap = new TreeMap<>(); /** @@ -109,9 +111,9 @@ public synchronized static void addCalendar(final BusinessCalendar cal) { throw new IllegalArgumentException("Multiple calendars have the same name: name='" + name + "'"); } - if(USE_FAST_CACHE) { + if (USE_FAST_CACHE) { logger.info("Enabling fast cache for calendar: name=" + name + " waitOnFastCache=" + WAIT_ON_FAST_CACHE); - cal.enableFastCache(WAIT_ON_FAST_CACHE); + cal.enableFastCache(WAIT_ON_FAST_CACHE); } map.put(name, cal); diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/FastConcurrentCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/FastConcurrentCache.java index 07eed467aaa..836ba8e10b8 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/FastConcurrentCache.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/FastConcurrentCache.java @@ -8,14 +8,14 @@ import java.util.function.Function; /** - * A cache that is designed to be fast when accessed concurrently. When created, the cache uses a ConcurrentHashMap to - * store values. When the fast cache is enabled, the cache is converted to a HashMap. This is done because HashMap is - * faster than {@link ConcurrentHashMap} when accessed concurrently. The fast cache is immutable, so it is safe to - * use the HashMap for optimal concurrent access. + * A cache that is designed to be fast when accessed concurrently. When created, the cache uses a ConcurrentHashMap to + * store values. When the fast cache is enabled, the cache is converted to a HashMap. This is done because HashMap is + * faster than {@link ConcurrentHashMap} when accessed concurrently. The fast cache is immutable, so it is safe to use + * the HashMap for optimal concurrent access. *

- * The fast cache population happens on a separate thread. This is done to avoid blocking the calling thread. The - * calling thread can wait for the fast cache to be populated by calling {@link #enableFastCache} with the wait parameter - * set to true. This will block the calling thread until the fast cache is populated. + * The fast cache population happens on a separate thread. This is done to avoid blocking the calling thread. The + * calling thread can wait for the fast cache to be populated by calling {@link #enableFastCache} with the wait + * parameter set to true. This will block the calling thread until the fast cache is populated. * * @param the key type * @param the value type @@ -48,9 +48,8 @@ public boolean isFastCache() { /** * Enables the fast cache. *

- * To enable the fast cache, a list of keys is provided. The value for each key is computed and added to the - * cache. If the cache is already enabled, an exception is thrown. To regenerate the fast cache, clear the - * cache first. + * To enable the fast cache, a list of keys is provided. The value for each key is computed and added to the cache. + * If the cache is already enabled, an exception is thrown. To regenerate the fast cache, clear the cache first. * * @param keys the keys to compute and add to the cache * @param wait whether to wait for the cache to be generated @@ -60,7 +59,8 @@ void enableFastCache(final List keys, final boolean wait) { synchronized (this) { if (fastCache) { - throw new IllegalStateException("Fast cache is already enabled. To change the range, clear the cache first."); + throw new IllegalStateException( + "Fast cache is already enabled. To change the range, clear the cache first."); } fastCacheFuture = CompletableFuture.runAsync(() -> { @@ -108,8 +108,8 @@ void clear() { } /** - * Gets the value for a key. If the fast cache is enabled, the value is retrieved from the cache. If the value is - * not found, an exception is thrown. If the fast cache is not enabled, the value is computed and added to the + * Gets the value for a key. If the fast cache is enabled, the value is retrieved from the cache. If the value is + * not found, an exception is thrown. If the fast cache is not enabled, the value is computed and added to the * cache. * * @param key the key @@ -121,7 +121,8 @@ public V get(K key) { final V v = cache.get(key); if (v == null) { - throw new IllegalArgumentException("No value found for " + key + ". Fast cache is enabled so the value can not be computed."); + throw new IllegalArgumentException( + "No value found for " + key + ". Fast cache is enabled so the value can not be computed."); } return v; diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java index 0893f2e0b12..c6d8d19bcc4 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java @@ -23,7 +23,7 @@ class YearMonthSummaryCache { * Creates a new cache. * * @param computeMonthSummary the function to compute a month summary - * @param computeYearSummary the function to compute a year summary + * @param computeYearSummary the function to compute a year summary */ YearMonthSummaryCache(Function computeMonthSummary, Function computeYearSummary) { monthCache = new FastConcurrentCache<>(computeMonthSummary); @@ -40,24 +40,24 @@ public synchronized boolean isFastCache() { } /** - * Computes the summaries for the specified range and caches them. - * The map is changed from a ConcurrentHashMap to a HashMap for faster access. - * This results in faster cache access, but it limits the range of dates in the cache. + * Computes the summaries for the specified range and caches them. The map is changed from a ConcurrentHashMap to a + * HashMap for faster access. This results in faster cache access, but it limits the range of dates in the cache. *

* To enable the fast cache for a different range, clear the cache first. * - * @param startYear the start year (inclusive) + * @param startYear the start year (inclusive) * @param startMonth the start month (inclusive) - * @param endYear the end year (inclusive) - * @param endMonth the end month (inclusive) - * @param wait whether to wait for the computation to finish - * before returning + * @param endYear the end year (inclusive) + * @param endMonth the end month (inclusive) + * @param wait whether to wait for the computation to finish before returning * @throws IllegalStateException if the fast cache is already enabled */ - synchronized void enableFastCache(final int startYear, final int startMonth, final int endYear, final int endMonth, final boolean wait) { + synchronized void enableFastCache(final int startYear, final int startMonth, final int endYear, final int endMonth, + final boolean wait) { if (fastCache) { - throw new IllegalStateException("Fast cache is already enabled. To change the range, clear the cache first."); + throw new IllegalStateException( + "Fast cache is already enabled. To change the range, clear the cache first."); } fastCache = true; @@ -65,8 +65,9 @@ synchronized void enableFastCache(final int startYear, final int startMonth, fin final ArrayList yearMonths = new ArrayList<>(); for (int year = startYear; year <= endYear; year++) { - for (int month = (year == startYear ? startMonth : 1); month <= (year == endYear ? endMonth : 12); month++) { - yearMonths.add(year * 100 + month); + for (int month = + (year == startYear ? startMonth : 1); month <= (year == endYear ? endMonth : 12); month++) { + yearMonths.add(year * 100 + month); } } @@ -74,7 +75,8 @@ synchronized void enableFastCache(final int startYear, final int startMonth, fin final ArrayList years = new ArrayList<>(); - for (int year = (startMonth == 1 ? startYear : startYear + 1); year <= (endMonth == 12 ? endYear : endYear - 1); year++) { + for (int year = (startMonth == 1 ? startYear : startYear + 1); year <= (endMonth == 12 ? endYear + : endYear - 1); year++) { years.add(year); } @@ -82,16 +84,14 @@ synchronized void enableFastCache(final int startYear, final int startMonth, fin } /** - * Computes the summaries for the specified range and caches them. - * The map is changed from a ConcurrentHashMap to a HashMap for faster access. - * This results in faster cache access, but it limits the range of dates in the cache. + * Computes the summaries for the specified range and caches them. The map is changed from a ConcurrentHashMap to a + * HashMap for faster access. This results in faster cache access, but it limits the range of dates in the cache. *

* To enable the fast cache for a different range, clear the cache first. * * @param start the start date (inclusive) - * @param end the end date (inclusive) - * @param wait whether to wait for the computation to finish - * before returning + * @param end the end date (inclusive) + * @param wait whether to wait for the computation to finish before returning * @throws IllegalStateException if the fast cache is already enabled */ void enableFastCache(LocalDate start, LocalDate end, final boolean wait) { @@ -135,7 +135,7 @@ T getMonthSummary(int yearMonth) { /** * Gets the month summary for the specified year and month. * - * @param year the year + * @param year the year * @param month the month * @return the month summary */ @@ -240,14 +240,14 @@ public T next() { * The iterator will return summaries in chronological order, and these summaries can be a mix of month and year * summaries. Dates not represented by complete summaries will be skipped (e.g. partial months). * - * @param start the start date - * @param end the end date + * @param start the start date + * @param end the end date * @param startInclusive whether the start date is inclusive - * @param endInclusive whether the end date is inclusive + * @param endInclusive whether the end date is inclusive * @return the iterator */ Iterator iterator(final LocalDate start, final LocalDate end, - final boolean startInclusive, final boolean endInclusive) { + final boolean startInclusive, final boolean endInclusive) { return new YearMonthSummaryIterator(startInclusive ? start : start.plusDays(1), endInclusive ? end : end.minusDays(1)); } diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestFastConcurrentCache.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestFastConcurrentCache.java index 89ceef1f0de..1d3f8faa99d 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestFastConcurrentCache.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestFastConcurrentCache.java @@ -20,19 +20,19 @@ public void testCache() { assertEquals("B", cache.get("b")); assertEquals("C", cache.get("c")); - try{ + try { cache.get("d"); fail("Expected exception"); } catch (final RuntimeException e) { - //pass + // pass } - try{ + try { // cache is already enabled cache.enableFastCache(List.of("a", "b", "c"), true); fail("Expected exception"); } catch (final IllegalStateException e) { - //pass + // pass } cache.clear(); diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java index a019b942037..ebd08fcdcdb 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java @@ -32,12 +32,12 @@ public void testGetters() { final YearMonthSummaryCache cache = new YearMonthSummaryCache<>(monthSummary, yearSummary); - for(boolean fastCache : new boolean[] {false, true}) { + for (boolean fastCache : new boolean[] {false, true}) { cache.clear(); monthCount[0] = 0; yearCount[0] = 0; - if(fastCache) { + if (fastCache) { final LocalDate start = LocalDate.of(2020, 12, 12); final LocalDate end = LocalDate.of(2023, 1, 7); cache.enableFastCache(start, end, true); @@ -50,22 +50,22 @@ public void testGetters() { assertEquals(fastCache ? 2 : 0, yearCount[0]); assertEquals("year2021", cache.getYearSummary(2021)); assertEquals(fastCache ? 24 : 1, monthCount[0]); - assertEquals(fastCache ? 2 :1, yearCount[0]); + assertEquals(fastCache ? 2 : 1, yearCount[0]); assertEquals("month202101", cache.getMonthSummary(202101)); assertEquals(fastCache ? 24 : 1, monthCount[0]); - assertEquals(fastCache ? 2 :1, yearCount[0]); + assertEquals(fastCache ? 2 : 1, yearCount[0]); assertEquals("year2021", cache.getYearSummary(2021)); assertEquals(fastCache ? 24 : 1, monthCount[0]); - assertEquals(fastCache ? 2 :1, yearCount[0]); + assertEquals(fastCache ? 2 : 1, yearCount[0]); assertEquals("month202102", cache.getMonthSummary(202102)); assertEquals(fastCache ? 24 : 2, monthCount[0]); - assertEquals(fastCache ? 2 :1, yearCount[0]); + assertEquals(fastCache ? 2 : 1, yearCount[0]); assertEquals("year2022", cache.getYearSummary(2022)); assertEquals(fastCache ? 24 : 2, monthCount[0]); - assertEquals(fastCache ? 2 :2, yearCount[0]); + assertEquals(fastCache ? 2 : 2, yearCount[0]); - if(fastCache) { + if (fastCache) { try { // check enabling the cache 2x final LocalDate start = LocalDate.of(2020, 12, 12); @@ -81,22 +81,22 @@ public void testGetters() { assertEquals("month202101", cache.getMonthSummary(202101)); assertEquals(fastCache ? 25 : 3, monthCount[0]); - assertEquals(fastCache ? 2: 2, yearCount[0]); + assertEquals(fastCache ? 2 : 2, yearCount[0]); assertEquals("year2021", cache.getYearSummary(2021)); - assertEquals(fastCache ? 25: 3, monthCount[0]); - assertEquals(fastCache ? 3: 3, yearCount[0]); + assertEquals(fastCache ? 25 : 3, monthCount[0]); + assertEquals(fastCache ? 3 : 3, yearCount[0]); assertEquals("month202101", cache.getMonthSummary(202101)); - assertEquals(fastCache ? 25: 3, monthCount[0]); + assertEquals(fastCache ? 25 : 3, monthCount[0]); assertEquals(3, yearCount[0]); assertEquals("year2021", cache.getYearSummary(2021)); - assertEquals(fastCache ? 25: 3, monthCount[0]); + assertEquals(fastCache ? 25 : 3, monthCount[0]); assertEquals(3, yearCount[0]); assertEquals("month202102", cache.getMonthSummary(202102)); - assertEquals(fastCache ? 26: 4, monthCount[0]); + assertEquals(fastCache ? 26 : 4, monthCount[0]); assertEquals(3, yearCount[0]); assertEquals("year2022", cache.getYearSummary(2022)); - assertEquals(fastCache ? 26: 4, monthCount[0]); + assertEquals(fastCache ? 26 : 4, monthCount[0]); assertEquals(4, yearCount[0]); assertEquals(cache.getMonthSummary(202101), cache.getMonthSummary(2021, 1)); From 51138c75a5497f9e0c2863bd68df757b5071ddeb Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Thu, 25 Apr 2024 08:42:10 -0600 Subject: [PATCH 23/54] Add a missing volatile. --- .../java/io/deephaven/time/calendar/FastConcurrentCache.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/FastConcurrentCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/FastConcurrentCache.java index 836ba8e10b8..f59d0a36ad5 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/FastConcurrentCache.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/FastConcurrentCache.java @@ -23,7 +23,7 @@ class FastConcurrentCache { private final Function valueComputer; - private Map cache = new ConcurrentHashMap<>(); + private volatile Map cache = new ConcurrentHashMap<>(); private volatile boolean fastCache = false; private CompletableFuture fastCacheFuture = null; From 2399510d3f875bc37f126def482ac5b46e645d52 Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Thu, 25 Apr 2024 11:29:13 -0600 Subject: [PATCH 24/54] More perf tuning. --- .../deephaven/time/calendar/CalendarDay.java | 27 +++++++---- .../time/calendar/FastConcurrentCache.java | 46 +++++++++---------- .../time/calendar/TestCalendarDay.java | 4 +- 3 files changed, 41 insertions(+), 36 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/CalendarDay.java b/engine/time/src/main/java/io/deephaven/time/calendar/CalendarDay.java index ba22de7bcef..d1f60b68d79 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/CalendarDay.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/CalendarDay.java @@ -30,7 +30,9 @@ public class CalendarDay & Temporal> { */ public static final CalendarDay HOLIDAY = new CalendarDay<>(); - private final List> businessTimeRanges; + private final TimeRange[] businessTimeRanges; + private final List> businessTimeRangesList; + private volatile long businessNanos = -1; /** * Creates a CalendarDay instance. @@ -63,7 +65,8 @@ public class CalendarDay & Temporal> { } } - this.businessTimeRanges = List.of(ranges); + this.businessTimeRanges = ranges; + this.businessTimeRangesList = List.of(ranges); } /** @@ -80,7 +83,7 @@ public class CalendarDay & Temporal> { * @return business time ranges for the day */ public List> businessTimeRanges() { - return businessTimeRanges; + return businessTimeRangesList; } /** @@ -89,7 +92,7 @@ public List> businessTimeRanges() { * @return start of the business day, or {@code null} for a holiday schedule */ public T businessStart() { - return !businessTimeRanges.isEmpty() ? businessTimeRanges.get(0).start() : null; + return businessTimeRanges.length > 0 ? businessTimeRanges[0].start() : null; } /** @@ -98,7 +101,7 @@ public T businessStart() { * @return end of the business day, or {@code null} for a holiday schedule */ public T businessEnd() { - return !businessTimeRanges.isEmpty() ? businessTimeRanges.get(businessTimeRanges.size() - 1).end() : null; + return businessTimeRanges.length > 0 ? businessTimeRanges[businessTimeRanges.length - 1].end() : null; } /** @@ -107,7 +110,7 @@ public T businessEnd() { * @return is the end of the business day inclusive? */ public boolean isInclusiveEnd() { - return businessTimeRanges.isEmpty() || businessTimeRanges.get(businessTimeRanges.size() - 1).isInclusiveEnd(); + return businessTimeRanges.length == 0 || businessTimeRanges[businessTimeRanges.length - 1].isInclusiveEnd(); } /** @@ -117,7 +120,11 @@ public boolean isInclusiveEnd() { * @return length of the day in nanoseconds */ public long businessNanos() { - return businessTimeRanges.stream().map(TimeRange::nanos).reduce(0L, Long::sum); + if (businessNanos < 0) { + businessNanos = Arrays.stream(businessTimeRanges).map(TimeRange::nanos).reduce(0L, Long::sum); + } + + return businessNanos; } /** @@ -239,18 +246,18 @@ public boolean equals(Object o) { if (!(o instanceof CalendarDay)) return false; CalendarDay that = (CalendarDay) o; - return Objects.equals(businessTimeRanges, that.businessTimeRanges); + return businessNanos == that.businessNanos && Arrays.equals(businessTimeRanges, that.businessTimeRanges); } @Override public int hashCode() { - return Objects.hash(businessTimeRanges); + return Arrays.hashCode(businessTimeRanges); } @Override public String toString() { return "CalendarDay{" + - "businessTimeRanges=" + Arrays.toString(businessTimeRanges.toArray()) + + "businessTimeRanges=" + Arrays.toString(businessTimeRanges) + '}'; } diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/FastConcurrentCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/FastConcurrentCache.java index f59d0a36ad5..4b03b1f0c12 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/FastConcurrentCache.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/FastConcurrentCache.java @@ -23,8 +23,9 @@ class FastConcurrentCache { private final Function valueComputer; - private volatile Map cache = new ConcurrentHashMap<>(); - private volatile boolean fastCache = false; + private final Map concurrentCache = new ConcurrentHashMap<>(); + private final Map fastCache = new HashMap<>(); + private volatile boolean isFastCache = false; private CompletableFuture fastCacheFuture = null; /** @@ -42,7 +43,7 @@ public FastConcurrentCache(final Function valueComputer) { * @return whether the fast cache is enabled */ public boolean isFastCache() { - return fastCache; + return isFastCache; } /** @@ -58,22 +59,21 @@ public boolean isFastCache() { void enableFastCache(final List keys, final boolean wait) { synchronized (this) { - if (fastCache) { + if (isFastCache || fastCacheFuture != null) { throw new IllegalStateException( "Fast cache is already enabled. To change the range, clear the cache first."); } fastCacheFuture = CompletableFuture.runAsync(() -> { - final Map c = new HashMap<>(cache); + fastCache.putAll(concurrentCache); for (K key : keys) { - if (!c.containsKey(key)) { - c.put(key, valueComputer.apply(key)); + if (!fastCache.containsKey(key)) { + fastCache.put(key, valueComputer.apply(key)); } } - fastCache = true; - cache = c; + isFastCache = true; synchronized (this) { fastCacheFuture = null; } @@ -88,23 +88,21 @@ void enableFastCache(final List keys, final boolean wait) { /** * Clears the cache and disables the fast cache. */ - void clear() { - synchronized (this) { - if (fastCacheFuture != null) { - final boolean canceled = fastCacheFuture.cancel(true); + synchronized void clear() { + if (fastCacheFuture != null) { + final boolean canceled = fastCacheFuture.cancel(true); - if (!canceled) { - throw new IllegalStateException("Failed to cancel fast cache computation"); - } + if (!canceled) { + throw new IllegalStateException("Failed to cancel fast cache computation"); } } - if (fastCache) { - fastCache = false; - cache = new ConcurrentHashMap<>(); - } else { - cache.clear(); + if (isFastCache) { + isFastCache = false; } + + fastCache.clear(); + concurrentCache.clear(); } /** @@ -117,8 +115,8 @@ void clear() { * @throws IllegalArgumentException if the value is not found */ public V get(K key) { - if (fastCache) { - final V v = cache.get(key); + if (isFastCache) { + final V v = fastCache.get(key); if (v == null) { throw new IllegalArgumentException( @@ -127,7 +125,7 @@ public V get(K key) { return v; } else { - return cache.computeIfAbsent(key, valueComputer); + return concurrentCache.computeIfAbsent(key, valueComputer); } } } diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestCalendarDay.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestCalendarDay.java index ced49a701ac..6ca1576fe1c 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestCalendarDay.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestCalendarDay.java @@ -7,8 +7,8 @@ import io.deephaven.time.DateTimeUtils; import java.time.*; +import java.util.Arrays; import java.util.List; -import java.util.Objects; import static io.deephaven.util.QueryConstants.NULL_LONG; import static org.junit.Assert.assertNotEquals; @@ -216,7 +216,7 @@ public void testEqualsHash() { final CalendarDay multi = new CalendarDay<>(new TimeRange[] {period1, period2}); assertEquals(List.of(period1, period2), multi.businessTimeRanges()); - int hashTarget = Objects.hash(multi.businessTimeRanges()); + int hashTarget = Arrays.hashCode(multi.businessTimeRanges().toArray()); assertEquals(hashTarget, multi.hashCode()); final CalendarDay multi2 = new CalendarDay<>(new TimeRange[] {period1, period2}); From c8e9fff82fd3ddc0a3fa8e195fad63e7c0a5d0a9 Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Thu, 25 Apr 2024 11:58:26 -0600 Subject: [PATCH 25/54] Fixed some locking problems. --- .../time/calendar/FastConcurrentCache.java | 52 +++++++++---------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/FastConcurrentCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/FastConcurrentCache.java index 4b03b1f0c12..cd85bf5ee67 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/FastConcurrentCache.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/FastConcurrentCache.java @@ -25,8 +25,9 @@ class FastConcurrentCache { private final Function valueComputer; private final Map concurrentCache = new ConcurrentHashMap<>(); private final Map fastCache = new HashMap<>(); - private volatile boolean isFastCache = false; - private CompletableFuture fastCacheFuture = null; + private boolean isFastCacheEnabled = false; // synchronized + private volatile boolean isFastCacheActive = false; + private CompletableFuture fastCacheFuture = null; // synchronized /** * Creates a new cache. @@ -42,8 +43,8 @@ public FastConcurrentCache(final Function valueComputer) { * * @return whether the fast cache is enabled */ - public boolean isFastCache() { - return isFastCache; + public synchronized boolean isFastCache() { + return isFastCacheEnabled; } /** @@ -56,29 +57,25 @@ public boolean isFastCache() { * @param wait whether to wait for the cache to be generated * @throws IllegalStateException if the fast cache is already enabled */ - void enableFastCache(final List keys, final boolean wait) { + synchronized void enableFastCache(final List keys, final boolean wait) { + if (isFastCacheEnabled) { + throw new IllegalStateException( + "Fast cache is already enabled. To change the range, clear the cache first."); + } - synchronized (this) { - if (isFastCache || fastCacheFuture != null) { - throw new IllegalStateException( - "Fast cache is already enabled. To change the range, clear the cache first."); - } + isFastCacheEnabled = true; - fastCacheFuture = CompletableFuture.runAsync(() -> { - fastCache.putAll(concurrentCache); + fastCacheFuture = CompletableFuture.runAsync(() -> { + fastCache.putAll(concurrentCache); - for (K key : keys) { - if (!fastCache.containsKey(key)) { - fastCache.put(key, valueComputer.apply(key)); - } + for (K key : keys) { + if (!fastCache.containsKey(key)) { + fastCache.put(key, valueComputer.apply(key)); } + } - isFastCache = true; - synchronized (this) { - fastCacheFuture = null; - } - }); - } + isFastCacheActive = true; + }); if (wait) { fastCacheFuture.join(); @@ -90,17 +87,16 @@ void enableFastCache(final List keys, final boolean wait) { */ synchronized void clear() { if (fastCacheFuture != null) { - final boolean canceled = fastCacheFuture.cancel(true); - - if (!canceled) { + if (!fastCacheFuture.isDone() && !fastCacheFuture.cancel(true)) { throw new IllegalStateException("Failed to cancel fast cache computation"); } } - if (isFastCache) { - isFastCache = false; + if (isFastCacheActive) { + isFastCacheActive = false; } + isFastCacheEnabled = false; fastCache.clear(); concurrentCache.clear(); } @@ -115,7 +111,7 @@ synchronized void clear() { * @throws IllegalArgumentException if the value is not found */ public V get(K key) { - if (isFastCache) { + if (isFastCacheActive) { final V v = fastCache.get(key); if (v == null) { From 3d5e5003fd3ba9527846a76745bb6fc1540b958c Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Thu, 25 Apr 2024 21:46:15 -0600 Subject: [PATCH 26/54] Spotless --- .../java/io/deephaven/time/calendar/FastConcurrentCache.java | 3 +++ .../io/deephaven/time/calendar/TestFastConcurrentCache.java | 3 +++ 2 files changed, 6 insertions(+) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/FastConcurrentCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/FastConcurrentCache.java index cd85bf5ee67..3c41707e7ee 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/FastConcurrentCache.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/FastConcurrentCache.java @@ -1,3 +1,6 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// package io.deephaven.time.calendar; import java.util.HashMap; diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestFastConcurrentCache.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestFastConcurrentCache.java index 1d3f8faa99d..1ade281776a 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestFastConcurrentCache.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestFastConcurrentCache.java @@ -1,3 +1,6 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// package io.deephaven.time.calendar; import io.deephaven.base.testing.BaseArrayTestCase; From 11725d74e2b1241f4fdc8336d25d7ea51e07642c Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Fri, 17 May 2024 10:31:39 -0600 Subject: [PATCH 27/54] Fast cache implementation based on a stamped lock. --- .../time/calendar/FastConcurrentCache.java | 58 +++++++++++++++---- 1 file changed, 46 insertions(+), 12 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/FastConcurrentCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/FastConcurrentCache.java index 3c41707e7ee..1d5f3100440 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/FastConcurrentCache.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/FastConcurrentCache.java @@ -8,8 +8,11 @@ import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.StampedLock; import java.util.function.Function; +//TODO: update docs + /** * A cache that is designed to be fast when accessed concurrently. When created, the cache uses a ConcurrentHashMap to * store values. When the fast cache is enabled, the cache is converted to a HashMap. This is done because HashMap is @@ -26,8 +29,9 @@ class FastConcurrentCache { private final Function valueComputer; - private final Map concurrentCache = new ConcurrentHashMap<>(); - private final Map fastCache = new HashMap<>(); + private final StampedLock lock = new StampedLock(); + private final Map cache = new HashMap<>(); + private boolean isFastCacheEnabled = false; // synchronized private volatile boolean isFastCacheActive = false; private CompletableFuture fastCacheFuture = null; // synchronized @@ -69,12 +73,8 @@ synchronized void enableFastCache(final List keys, final boolean wait) { isFastCacheEnabled = true; fastCacheFuture = CompletableFuture.runAsync(() -> { - fastCache.putAll(concurrentCache); - for (K key : keys) { - if (!fastCache.containsKey(key)) { - fastCache.put(key, valueComputer.apply(key)); - } + get(key); } isFastCacheActive = true; @@ -100,8 +100,13 @@ synchronized void clear() { } isFastCacheEnabled = false; - fastCache.clear(); - concurrentCache.clear(); + + long stamp = lock.writeLock(); + try { + cache.clear(); + } finally { + lock.unlock(stamp); + } } /** @@ -115,7 +120,7 @@ synchronized void clear() { */ public V get(K key) { if (isFastCacheActive) { - final V v = fastCache.get(key); + final V v = cache.get(key); if (v == null) { throw new IllegalArgumentException( @@ -123,8 +128,37 @@ public V get(K key) { } return v; - } else { - return concurrentCache.computeIfAbsent(key, valueComputer); + } + + long stamp = lock.tryOptimisticRead(); + V existing = cache.get(key); + + if (!lock.validate(stamp)) { + stamp = lock.readLock(); + try { + existing = cache.get(key); + } finally { + lock.unlockRead(stamp); + } + } + + if (existing != null) { + return existing; + } + + stamp = lock.writeLock(); + + try { + final V newValue = valueComputer.apply(key); + + if (newValue == null) { + throw new IllegalArgumentException("Computed a null value for: key=" + key); + } + + existing = cache.putIfAbsent(key, newValue); + return existing == null ? newValue : existing; + } finally { + lock.unlockWrite(stamp); } } } From cc8e4dcc3238e551dbd147e0d9f26e372832b27b Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Fri, 17 May 2024 11:37:19 -0600 Subject: [PATCH 28/54] Removed cache population and clean up code. --- .../time/calendar/BusinessCalendar.java | 28 +-- .../io/deephaven/time/calendar/Calendar.java | 29 ---- .../io/deephaven/time/calendar/Calendars.java | 9 - .../time/calendar/FastConcurrentCache.java | 164 ------------------ .../ImmutableReadHeavyConcurrentCache.java | 85 +++++++++ .../time/calendar/YearMonthSummaryCache.java | 94 +--------- .../time/calendar/TestBusinessCalendar.java | 27 +-- .../deephaven/time/calendar/TestCalendar.java | 15 -- .../calendar/TestFastConcurrentCache.java | 55 ------ ...TestImmutableReadHeavyConcurrentCache.java | 49 ++++++ .../calendar/TestYearMonthSummaryCache.java | 62 +++---- 11 files changed, 166 insertions(+), 451 deletions(-) delete mode 100644 engine/time/src/main/java/io/deephaven/time/calendar/FastConcurrentCache.java create mode 100644 engine/time/src/main/java/io/deephaven/time/calendar/ImmutableReadHeavyConcurrentCache.java delete mode 100644 engine/time/src/test/java/io/deephaven/time/calendar/TestFastConcurrentCache.java create mode 100644 engine/time/src/test/java/io/deephaven/time/calendar/TestImmutableReadHeavyConcurrentCache.java diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java index 1d8084dc254..149696c5a8a 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java @@ -90,8 +90,8 @@ public SummaryData( } } - private final FastConcurrentCache> schedulesCache = - new FastConcurrentCache<>(this::computeCalendarDay); + private final ImmutableReadHeavyConcurrentCache> schedulesCache = + new ImmutableReadHeavyConcurrentCache<>(this::computeCalendarDay); private final YearMonthSummaryCache summaryCache = new YearMonthSummaryCache<>(this::computeMonthSummary, this::computeYearSummary); private final int yearCacheStart; @@ -217,30 +217,6 @@ private CalendarDay computeCalendarDay(final LocalDate date) { } } - /** - * Enables a fast cache that improves access and computation times. - *

- * If the cache is already enabled, this method does nothing. - * - * @param wait whether to wait for the computation to finish - */ - public synchronized void enableFastCache(final boolean wait) { - if (isFastCache()) { - return; - } - - super.enableFastCache(firstValidDate, lastValidDate, wait); - summaryCache.enableFastCache(firstValidDate, lastValidDate, wait); - - final ArrayList dates = new ArrayList<>(); - - for (LocalDate date = firstValidDate; !date.isAfter(lastValidDate); date = date.plusDays(1)) { - dates.add(date); - } - - schedulesCache.enableFastCache(dates, wait); - } - // endregion // region Constructors diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java index 9c8795b5b55..1da271d4005 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java @@ -27,7 +27,6 @@ public class Calendar { private final String name; private final String description; private final ZoneId timeZone; - private boolean fastCache = false; // synchronized // region Cache @@ -88,24 +87,6 @@ private SummaryData computeYearSummary(final int year) { return new SummaryData(startDate, endDate, dates); } - /** - * Enables a fast cache that improves access and computation times. - *

- * If the cache is already enabled, this method does nothing. - * - * @param start the start date - * @param end the end date - * @param wait whether to wait for the computation to finish - */ - protected synchronized void enableFastCache(final LocalDate start, final LocalDate end, final boolean wait) { - if (fastCache) { - return; - } - - fastCache = true; - summaryCache.enableFastCache(start, end, wait); - } - // endregion // region Constructors @@ -132,19 +113,9 @@ protected synchronized void enableFastCache(final LocalDate start, final LocalDa * Clears the cache. This should not generally be used and is provided for benchmarking. */ public synchronized void clearCache() { - fastCache = false; summaryCache.clear(); } - /** - * Returns whether the fast cache is enabled. - * - * @return whether the fast cache is enabled - */ - public synchronized boolean isFastCache() { - return fastCache; - } - // endregion // region Getters diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/Calendars.java b/engine/time/src/main/java/io/deephaven/time/calendar/Calendars.java index ee7f7a3f92a..c0743d01a35 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/Calendars.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/Calendars.java @@ -27,10 +27,6 @@ public class Calendars { private static final String BUSINESS_CALENDAR_PROP_INTERNAL = "Calendar.importPath"; private static final String BUSINESS_CALENDAR_PROP_USER = "Calendar.userImportPath"; private static String defaultName = Configuration.getInstance().getProperty("Calendar.default"); - private static final boolean USE_FAST_CACHE = - Configuration.getInstance().getBooleanWithDefault("Calendar.useFastCache", true); - private static final boolean WAIT_ON_FAST_CACHE = - Configuration.getInstance().getBooleanWithDefault("Calendar.waitOnFastCache", false); private static final Map calMap = new TreeMap<>(); /** @@ -111,11 +107,6 @@ public synchronized static void addCalendar(final BusinessCalendar cal) { throw new IllegalArgumentException("Multiple calendars have the same name: name='" + name + "'"); } - if (USE_FAST_CACHE) { - logger.info("Enabling fast cache for calendar: name=" + name + " waitOnFastCache=" + WAIT_ON_FAST_CACHE); - cal.enableFastCache(WAIT_ON_FAST_CACHE); - } - map.put(name, cal); } diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/FastConcurrentCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/FastConcurrentCache.java deleted file mode 100644 index 1d5f3100440..00000000000 --- a/engine/time/src/main/java/io/deephaven/time/calendar/FastConcurrentCache.java +++ /dev/null @@ -1,164 +0,0 @@ -// -// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending -// -package io.deephaven.time.calendar; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.locks.StampedLock; -import java.util.function.Function; - -//TODO: update docs - -/** - * A cache that is designed to be fast when accessed concurrently. When created, the cache uses a ConcurrentHashMap to - * store values. When the fast cache is enabled, the cache is converted to a HashMap. This is done because HashMap is - * faster than {@link ConcurrentHashMap} when accessed concurrently. The fast cache is immutable, so it is safe to use - * the HashMap for optimal concurrent access. - *

- * The fast cache population happens on a separate thread. This is done to avoid blocking the calling thread. The - * calling thread can wait for the fast cache to be populated by calling {@link #enableFastCache} with the wait - * parameter set to true. This will block the calling thread until the fast cache is populated. - * - * @param the key type - * @param the value type - */ -class FastConcurrentCache { - - private final Function valueComputer; - private final StampedLock lock = new StampedLock(); - private final Map cache = new HashMap<>(); - - private boolean isFastCacheEnabled = false; // synchronized - private volatile boolean isFastCacheActive = false; - private CompletableFuture fastCacheFuture = null; // synchronized - - /** - * Creates a new cache. - * - * @param valueComputer computes the value for a key. - */ - public FastConcurrentCache(final Function valueComputer) { - this.valueComputer = valueComputer; - } - - /** - * Returns whether the fast cache is enabled. - * - * @return whether the fast cache is enabled - */ - public synchronized boolean isFastCache() { - return isFastCacheEnabled; - } - - /** - * Enables the fast cache. - *

- * To enable the fast cache, a list of keys is provided. The value for each key is computed and added to the cache. - * If the cache is already enabled, an exception is thrown. To regenerate the fast cache, clear the cache first. - * - * @param keys the keys to compute and add to the cache - * @param wait whether to wait for the cache to be generated - * @throws IllegalStateException if the fast cache is already enabled - */ - synchronized void enableFastCache(final List keys, final boolean wait) { - if (isFastCacheEnabled) { - throw new IllegalStateException( - "Fast cache is already enabled. To change the range, clear the cache first."); - } - - isFastCacheEnabled = true; - - fastCacheFuture = CompletableFuture.runAsync(() -> { - for (K key : keys) { - get(key); - } - - isFastCacheActive = true; - }); - - if (wait) { - fastCacheFuture.join(); - } - } - - /** - * Clears the cache and disables the fast cache. - */ - synchronized void clear() { - if (fastCacheFuture != null) { - if (!fastCacheFuture.isDone() && !fastCacheFuture.cancel(true)) { - throw new IllegalStateException("Failed to cancel fast cache computation"); - } - } - - if (isFastCacheActive) { - isFastCacheActive = false; - } - - isFastCacheEnabled = false; - - long stamp = lock.writeLock(); - try { - cache.clear(); - } finally { - lock.unlock(stamp); - } - } - - /** - * Gets the value for a key. If the fast cache is enabled, the value is retrieved from the cache. If the value is - * not found, an exception is thrown. If the fast cache is not enabled, the value is computed and added to the - * cache. - * - * @param key the key - * @return the value - * @throws IllegalArgumentException if the value is not found - */ - public V get(K key) { - if (isFastCacheActive) { - final V v = cache.get(key); - - if (v == null) { - throw new IllegalArgumentException( - "No value found for " + key + ". Fast cache is enabled so the value can not be computed."); - } - - return v; - } - - long stamp = lock.tryOptimisticRead(); - V existing = cache.get(key); - - if (!lock.validate(stamp)) { - stamp = lock.readLock(); - try { - existing = cache.get(key); - } finally { - lock.unlockRead(stamp); - } - } - - if (existing != null) { - return existing; - } - - stamp = lock.writeLock(); - - try { - final V newValue = valueComputer.apply(key); - - if (newValue == null) { - throw new IllegalArgumentException("Computed a null value for: key=" + key); - } - - existing = cache.putIfAbsent(key, newValue); - return existing == null ? newValue : existing; - } finally { - lock.unlockWrite(stamp); - } - } -} diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/ImmutableReadHeavyConcurrentCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/ImmutableReadHeavyConcurrentCache.java new file mode 100644 index 00000000000..ddcc99ce659 --- /dev/null +++ b/engine/time/src/main/java/io/deephaven/time/calendar/ImmutableReadHeavyConcurrentCache.java @@ -0,0 +1,85 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.time.calendar; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.locks.StampedLock; +import java.util.function.Function; + +/** + * An immutable cache that is designed to be fast when accessed concurrently with read-heavy workloads. + * Values are populated from a function when they are not found in the cache. + * All values must be non-null. + * + * @param the key type + * @param the value type + */ +class ImmutableReadHeavyConcurrentCache { + + private final Function valueComputer; + private final StampedLock lock = new StampedLock(); + private final Map cache = new HashMap<>(); + + /** + * Creates a new cache. + * + * @param valueComputer computes the value for a key. + */ + public ImmutableReadHeavyConcurrentCache(final Function valueComputer) { + this.valueComputer = valueComputer; + } + + /** + * Clears the cache. + */ + synchronized void clear() { + long stamp = lock.writeLock(); + try { + cache.clear(); + } finally { + lock.unlock(stamp); + } + } + + /** + * Gets the value for a key. If the value is not found, it is computed and added to the cache. + * + * @param key the key + * @return the value + * @throws IllegalArgumentException if the value is not found + */ + public V get(K key) { + long stamp = lock.tryOptimisticRead(); + V existing = cache.get(key); + + if (!lock.validate(stamp)) { + stamp = lock.readLock(); + try { + existing = cache.get(key); + } finally { + lock.unlockRead(stamp); + } + } + + if (existing != null) { + return existing; + } + + stamp = lock.writeLock(); + + try { + final V newValue = valueComputer.apply(key); + + if (newValue == null) { + throw new IllegalArgumentException("Computed a null value: key=" + key); + } + + existing = cache.putIfAbsent(key, newValue); + return existing == null ? newValue : existing; + } finally { + lock.unlockWrite(stamp); + } + } +} diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java index c6d8d19bcc4..4b96a389868 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java @@ -4,7 +4,6 @@ package io.deephaven.time.calendar; import java.time.LocalDate; -import java.util.ArrayList; import java.util.Iterator; import java.util.function.Function; @@ -15,9 +14,8 @@ */ class YearMonthSummaryCache { - private final FastConcurrentCache monthCache; - private final FastConcurrentCache yearCache; - private volatile boolean fastCache = false; // synchronized + private final ImmutableReadHeavyConcurrentCache monthCache; + private final ImmutableReadHeavyConcurrentCache yearCache; /** * Creates a new cache. @@ -26,98 +24,14 @@ class YearMonthSummaryCache { * @param computeYearSummary the function to compute a year summary */ YearMonthSummaryCache(Function computeMonthSummary, Function computeYearSummary) { - monthCache = new FastConcurrentCache<>(computeMonthSummary); - yearCache = new FastConcurrentCache<>(computeYearSummary); - } - - /** - * Returns whether the fast cache is enabled. - * - * @return whether the fast cache is enabled - */ - public synchronized boolean isFastCache() { - return fastCache; - } - - /** - * Computes the summaries for the specified range and caches them. The map is changed from a ConcurrentHashMap to a - * HashMap for faster access. This results in faster cache access, but it limits the range of dates in the cache. - *

- * To enable the fast cache for a different range, clear the cache first. - * - * @param startYear the start year (inclusive) - * @param startMonth the start month (inclusive) - * @param endYear the end year (inclusive) - * @param endMonth the end month (inclusive) - * @param wait whether to wait for the computation to finish before returning - * @throws IllegalStateException if the fast cache is already enabled - */ - synchronized void enableFastCache(final int startYear, final int startMonth, final int endYear, final int endMonth, - final boolean wait) { - - if (fastCache) { - throw new IllegalStateException( - "Fast cache is already enabled. To change the range, clear the cache first."); - } - - fastCache = true; - - final ArrayList yearMonths = new ArrayList<>(); - - for (int year = startYear; year <= endYear; year++) { - for (int month = - (year == startYear ? startMonth : 1); month <= (year == endYear ? endMonth : 12); month++) { - yearMonths.add(year * 100 + month); - } - } - - monthCache.enableFastCache(yearMonths, wait); - - final ArrayList years = new ArrayList<>(); - - for (int year = (startMonth == 1 ? startYear : startYear + 1); year <= (endMonth == 12 ? endYear - : endYear - 1); year++) { - years.add(year); - } - - yearCache.enableFastCache(years, wait); - } - - /** - * Computes the summaries for the specified range and caches them. The map is changed from a ConcurrentHashMap to a - * HashMap for faster access. This results in faster cache access, but it limits the range of dates in the cache. - *

- * To enable the fast cache for a different range, clear the cache first. - * - * @param start the start date (inclusive) - * @param end the end date (inclusive) - * @param wait whether to wait for the computation to finish before returning - * @throws IllegalStateException if the fast cache is already enabled - */ - void enableFastCache(LocalDate start, LocalDate end, final boolean wait) { - // Ensure only full months are computed - - // Skip the first month if the start date is not the first day of the month - if (start.getDayOfMonth() != 1) { - start = start.withDayOfMonth(1).plusMonths(1); - } - - // Skip the last month if the end date is not the last day of the month - - final LocalDate endPlus1 = end.plusDays(1); - - if (end.getMonth() == endPlus1.getMonth()) { - end = end.withDayOfMonth(1).minusMonths(1); - } - - enableFastCache(start.getYear(), start.getMonthValue(), end.getYear(), end.getMonthValue(), wait); + monthCache = new ImmutableReadHeavyConcurrentCache<>(computeMonthSummary); + yearCache = new ImmutableReadHeavyConcurrentCache<>(computeYearSummary); } /** * Clears the cache. */ synchronized void clear() { - fastCache = false; monthCache.clear(); yearCache.clear(); } diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestBusinessCalendar.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestBusinessCalendar.java index a40587879b5..f3cb5128629 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestBusinessCalendar.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestBusinessCalendar.java @@ -912,6 +912,7 @@ public void testNumberNonBusinessDates() { // LocalDate.of(2023,7,15), // }; + assertEquals(0, bCalendar.numberNonBusinessDates(end, start)); assertEquals(nonBus.length, bCalendar.numberNonBusinessDates(start, end)); assertEquals(nonBus.length, bCalendar.numberNonBusinessDates(start.toString(), end.toString())); assertEquals(nonBus.length, bCalendar.numberNonBusinessDates(start.atTime(1, 24).atZone(timeZone), @@ -959,6 +960,11 @@ public void testNumberNonBusinessDates() { bCalendar.numberNonBusinessDates(start.atTime(1, 2).atZone(timeZone).toInstant(), null, true, true)); assertEquals(NULL_INT, bCalendar.numberNonBusinessDates(null, end.atTime(1, 2).atZone(timeZone), true, true)); assertEquals(NULL_INT, bCalendar.numberNonBusinessDates(start.atTime(1, 2).atZone(timeZone), null, true, true)); + + final LocalDate startLong = LocalDate.of(2023, 7, 3); + final LocalDate endLong = LocalDate.of(2025, 7, 15); + final LocalDate[] nonBusLong = bCalendar.nonBusinessDates(startLong, endLong); + assertEquals(nonBusLong.length, bCalendar.numberNonBusinessDates(startLong, endLong)); } public void testDiffBusinessNanos() { @@ -1613,25 +1619,4 @@ public void testClearCache() { assertEquals(i3, i4); } - public void testFastCache() { - final LocalDate start = LocalDate.of(2018, 2, 3); - final LocalDate end = LocalDate.of(2023, 2, 5); - - bCalendar.clearCache(); - assertFalse(bCalendar.isFastCache()); - final LocalDate[] calDates = bCalendar.calendarDates(start, end); - final LocalDate[] busDates = bCalendar.businessDates(start, end); - final LocalDate[] nonBusDates = bCalendar.nonBusinessDates(start, end); - - bCalendar.enableFastCache(true); - assertTrue(bCalendar.isFastCache()); - final LocalDate[] calDates2 = bCalendar.calendarDates(start, end); - final LocalDate[] busDates2 = bCalendar.businessDates(start, end); - final LocalDate[] nonBusDates2 = bCalendar.nonBusinessDates(start, end); - - assertEquals(calDates, calDates2); - assertEquals(busDates, busDates2); - assertEquals(nonBusDates, nonBusDates2); - } - } diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestCalendar.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestCalendar.java index 01cd509dda6..b0cd72dba1a 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestCalendar.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestCalendar.java @@ -282,19 +282,4 @@ public void testNumberCalendarDates() { assertEquals(NULL_INT, calendar.numberCalendarDates(null, end.atTime(3, 15).atZone(timeZone), false, false)); assertEquals(NULL_INT, calendar.numberCalendarDates(start.atTime(3, 15).atZone(timeZone), null, false, false)); } - - public void testFastCache() { - final LocalDate start = LocalDate.of(2018, 2, 3); - final LocalDate end = LocalDate.of(2023, 2, 5); - - calendar.clearCache(); - assertFalse(calendar.isFastCache()); - final LocalDate[] dates = calendar.calendarDates(start, end); - - calendar.enableFastCache(start, end, true); - assertTrue(calendar.isFastCache()); - final LocalDate[] dates2 = calendar.calendarDates(start, end); - - assertEquals(dates, dates2); - } } diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestFastConcurrentCache.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestFastConcurrentCache.java deleted file mode 100644 index 1ade281776a..00000000000 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestFastConcurrentCache.java +++ /dev/null @@ -1,55 +0,0 @@ -// -// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending -// -package io.deephaven.time.calendar; - -import io.deephaven.base.testing.BaseArrayTestCase; - -import java.util.List; - -public class TestFastConcurrentCache extends BaseArrayTestCase { - - public void testCache() { - final FastConcurrentCache cache = new FastConcurrentCache<>(String::toUpperCase); - - assertFalse(cache.isFastCache()); - assertEquals("A", cache.get("a")); - assertEquals("A", cache.get("a")); - - cache.enableFastCache(List.of("a", "b", "c"), true); - - assertTrue(cache.isFastCache()); - assertEquals("A", cache.get("a")); - assertEquals("B", cache.get("b")); - assertEquals("C", cache.get("c")); - - try { - cache.get("d"); - fail("Expected exception"); - } catch (final RuntimeException e) { - // pass - } - - try { - // cache is already enabled - cache.enableFastCache(List.of("a", "b", "c"), true); - fail("Expected exception"); - } catch (final IllegalStateException e) { - // pass - } - - cache.clear(); - assertFalse(cache.isFastCache()); - assertEquals("A", cache.get("a")); - assertEquals("B", cache.get("b")); - assertEquals("C", cache.get("c")); - assertEquals("D", cache.get("d")); - - cache.clear(); - assertFalse(cache.isFastCache()); - assertEquals("A", cache.get("a")); - assertEquals("B", cache.get("b")); - assertEquals("C", cache.get("c")); - assertEquals("D", cache.get("d")); - } -} diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestImmutableReadHeavyConcurrentCache.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestImmutableReadHeavyConcurrentCache.java new file mode 100644 index 00000000000..824d0243dc3 --- /dev/null +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestImmutableReadHeavyConcurrentCache.java @@ -0,0 +1,49 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.time.calendar; + +import io.deephaven.base.testing.BaseArrayTestCase; + +import java.util.List; + +public class TestImmutableReadHeavyConcurrentCache extends BaseArrayTestCase { + + private static String makeVal(String s) { + if(s.equals("d")) { + return null; + } + + return s.toUpperCase(); + } + + public void testCache() { + final ImmutableReadHeavyConcurrentCache cache = new ImmutableReadHeavyConcurrentCache<>(TestImmutableReadHeavyConcurrentCache::makeVal); + + assertEquals("A", cache.get("a")); + assertEquals("A", cache.get("a")); + + assertEquals("A", cache.get("a")); + assertEquals("B", cache.get("b")); + assertEquals("C", cache.get("c")); + + try { + cache.get("d"); + fail("Expected exception"); + } catch (final IllegalArgumentException e) { + // pass + } + + cache.clear(); + assertEquals("A", cache.get("a")); + assertEquals("B", cache.get("b")); + assertEquals("C", cache.get("c")); + + try { + cache.get("d"); + fail("Expected exception"); + } catch (final IllegalArgumentException e) { + // pass + } + } +} diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java index ebd08fcdcdb..dd113954896 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java @@ -32,75 +32,53 @@ public void testGetters() { final YearMonthSummaryCache cache = new YearMonthSummaryCache<>(monthSummary, yearSummary); - for (boolean fastCache : new boolean[] {false, true}) { cache.clear(); monthCount[0] = 0; yearCount[0] = 0; - if (fastCache) { - final LocalDate start = LocalDate.of(2020, 12, 12); - final LocalDate end = LocalDate.of(2023, 1, 7); - cache.enableFastCache(start, end, true); - } - - assertEquals(fastCache, cache.isFastCache()); - assertEquals("month202101", cache.getMonthSummary(202101)); - assertEquals(fastCache ? 24 : 1, monthCount[0]); - assertEquals(fastCache ? 2 : 0, yearCount[0]); + assertEquals(1, monthCount[0]); + assertEquals(0, yearCount[0]); assertEquals("year2021", cache.getYearSummary(2021)); - assertEquals(fastCache ? 24 : 1, monthCount[0]); - assertEquals(fastCache ? 2 : 1, yearCount[0]); + assertEquals(1, monthCount[0]); + assertEquals(1, yearCount[0]); assertEquals("month202101", cache.getMonthSummary(202101)); - assertEquals(fastCache ? 24 : 1, monthCount[0]); - assertEquals(fastCache ? 2 : 1, yearCount[0]); + assertEquals(1, monthCount[0]); + assertEquals(1, yearCount[0]); assertEquals("year2021", cache.getYearSummary(2021)); - assertEquals(fastCache ? 24 : 1, monthCount[0]); - assertEquals(fastCache ? 2 : 1, yearCount[0]); + assertEquals(1, monthCount[0]); + assertEquals(1, yearCount[0]); assertEquals("month202102", cache.getMonthSummary(202102)); - assertEquals(fastCache ? 24 : 2, monthCount[0]); - assertEquals(fastCache ? 2 : 1, yearCount[0]); + assertEquals(2, monthCount[0]); + assertEquals(1, yearCount[0]); assertEquals("year2022", cache.getYearSummary(2022)); - assertEquals(fastCache ? 24 : 2, monthCount[0]); - assertEquals(fastCache ? 2 : 2, yearCount[0]); - - if (fastCache) { - try { - // check enabling the cache 2x - final LocalDate start = LocalDate.of(2020, 12, 12); - final LocalDate end = LocalDate.of(2023, 1, 7); - cache.enableFastCache(start, end, true); - fail("Expected IllegalStateException because the cache is already enabled"); - } catch (IllegalStateException e) { - // expected - } - } + assertEquals(2, monthCount[0]); + assertEquals(2, yearCount[0]); cache.clear(); assertEquals("month202101", cache.getMonthSummary(202101)); - assertEquals(fastCache ? 25 : 3, monthCount[0]); - assertEquals(fastCache ? 2 : 2, yearCount[0]); + assertEquals(3, monthCount[0]); + assertEquals( 2, yearCount[0]); assertEquals("year2021", cache.getYearSummary(2021)); - assertEquals(fastCache ? 25 : 3, monthCount[0]); - assertEquals(fastCache ? 3 : 3, yearCount[0]); + assertEquals(3, monthCount[0]); + assertEquals(3, yearCount[0]); assertEquals("month202101", cache.getMonthSummary(202101)); - assertEquals(fastCache ? 25 : 3, monthCount[0]); + assertEquals(3, monthCount[0]); assertEquals(3, yearCount[0]); assertEquals("year2021", cache.getYearSummary(2021)); - assertEquals(fastCache ? 25 : 3, monthCount[0]); + assertEquals(3, monthCount[0]); assertEquals(3, yearCount[0]); assertEquals("month202102", cache.getMonthSummary(202102)); - assertEquals(fastCache ? 26 : 4, monthCount[0]); + assertEquals( 4, monthCount[0]); assertEquals(3, yearCount[0]); assertEquals("year2022", cache.getYearSummary(2022)); - assertEquals(fastCache ? 26 : 4, monthCount[0]); + assertEquals(4, monthCount[0]); assertEquals(4, yearCount[0]); assertEquals(cache.getMonthSummary(202101), cache.getMonthSummary(2021, 1)); - } } private static Stream iteratorToStream(Iterator iterator) { From 75283e75c22d3ebabdf17e9830c6861c5335074f Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Fri, 17 May 2024 11:38:10 -0600 Subject: [PATCH 29/54] Code self review. --- .../java/io/deephaven/libs/StaticCalendarMethodsGenerator.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/Generators/src/main/java/io/deephaven/libs/StaticCalendarMethodsGenerator.java b/Generators/src/main/java/io/deephaven/libs/StaticCalendarMethodsGenerator.java index 320b1ac1d7e..77f912b4d5a 100644 --- a/Generators/src/main/java/io/deephaven/libs/StaticCalendarMethodsGenerator.java +++ b/Generators/src/main/java/io/deephaven/libs/StaticCalendarMethodsGenerator.java @@ -79,8 +79,6 @@ public static void main(String[] args) throws ClassNotFoundException, IOExceptio excludes.add("firstValidDate"); excludes.add("lastValidDate"); excludes.add("clearCache"); - excludes.add("enableFastCache"); - excludes.add("isFastCache"); StaticCalendarMethodsGenerator gen = new StaticCalendarMethodsGenerator(gradleTask, packageName, className, imports, From 359dc05821c243a2b5513bc2d477f1e5855fdad8 Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Fri, 17 May 2024 11:42:13 -0600 Subject: [PATCH 30/54] Spotless --- .../ImmutableReadHeavyConcurrentCache.java | 5 +- ...TestImmutableReadHeavyConcurrentCache.java | 5 +- .../calendar/TestYearMonthSummaryCache.java | 94 +++++++++---------- 3 files changed, 52 insertions(+), 52 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/ImmutableReadHeavyConcurrentCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/ImmutableReadHeavyConcurrentCache.java index ddcc99ce659..c59a01cd839 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/ImmutableReadHeavyConcurrentCache.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/ImmutableReadHeavyConcurrentCache.java @@ -9,9 +9,8 @@ import java.util.function.Function; /** - * An immutable cache that is designed to be fast when accessed concurrently with read-heavy workloads. - * Values are populated from a function when they are not found in the cache. - * All values must be non-null. + * An immutable cache that is designed to be fast when accessed concurrently with read-heavy workloads. Values are + * populated from a function when they are not found in the cache. All values must be non-null. * * @param the key type * @param the value type diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestImmutableReadHeavyConcurrentCache.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestImmutableReadHeavyConcurrentCache.java index 824d0243dc3..0de34298ee7 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestImmutableReadHeavyConcurrentCache.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestImmutableReadHeavyConcurrentCache.java @@ -10,7 +10,7 @@ public class TestImmutableReadHeavyConcurrentCache extends BaseArrayTestCase { private static String makeVal(String s) { - if(s.equals("d")) { + if (s.equals("d")) { return null; } @@ -18,7 +18,8 @@ private static String makeVal(String s) { } public void testCache() { - final ImmutableReadHeavyConcurrentCache cache = new ImmutableReadHeavyConcurrentCache<>(TestImmutableReadHeavyConcurrentCache::makeVal); + final ImmutableReadHeavyConcurrentCache cache = + new ImmutableReadHeavyConcurrentCache<>(TestImmutableReadHeavyConcurrentCache::makeVal); assertEquals("A", cache.get("a")); assertEquals("A", cache.get("a")); diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java index dd113954896..cf303c82a0f 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java @@ -32,53 +32,53 @@ public void testGetters() { final YearMonthSummaryCache cache = new YearMonthSummaryCache<>(monthSummary, yearSummary); - cache.clear(); - monthCount[0] = 0; - yearCount[0] = 0; - - assertEquals("month202101", cache.getMonthSummary(202101)); - assertEquals(1, monthCount[0]); - assertEquals(0, yearCount[0]); - assertEquals("year2021", cache.getYearSummary(2021)); - assertEquals(1, monthCount[0]); - assertEquals(1, yearCount[0]); - assertEquals("month202101", cache.getMonthSummary(202101)); - assertEquals(1, monthCount[0]); - assertEquals(1, yearCount[0]); - assertEquals("year2021", cache.getYearSummary(2021)); - assertEquals(1, monthCount[0]); - assertEquals(1, yearCount[0]); - - assertEquals("month202102", cache.getMonthSummary(202102)); - assertEquals(2, monthCount[0]); - assertEquals(1, yearCount[0]); - assertEquals("year2022", cache.getYearSummary(2022)); - assertEquals(2, monthCount[0]); - assertEquals(2, yearCount[0]); - - cache.clear(); - - assertEquals("month202101", cache.getMonthSummary(202101)); - assertEquals(3, monthCount[0]); - assertEquals( 2, yearCount[0]); - assertEquals("year2021", cache.getYearSummary(2021)); - assertEquals(3, monthCount[0]); - assertEquals(3, yearCount[0]); - assertEquals("month202101", cache.getMonthSummary(202101)); - assertEquals(3, monthCount[0]); - assertEquals(3, yearCount[0]); - assertEquals("year2021", cache.getYearSummary(2021)); - assertEquals(3, monthCount[0]); - assertEquals(3, yearCount[0]); - - assertEquals("month202102", cache.getMonthSummary(202102)); - assertEquals( 4, monthCount[0]); - assertEquals(3, yearCount[0]); - assertEquals("year2022", cache.getYearSummary(2022)); - assertEquals(4, monthCount[0]); - assertEquals(4, yearCount[0]); - - assertEquals(cache.getMonthSummary(202101), cache.getMonthSummary(2021, 1)); + cache.clear(); + monthCount[0] = 0; + yearCount[0] = 0; + + assertEquals("month202101", cache.getMonthSummary(202101)); + assertEquals(1, monthCount[0]); + assertEquals(0, yearCount[0]); + assertEquals("year2021", cache.getYearSummary(2021)); + assertEquals(1, monthCount[0]); + assertEquals(1, yearCount[0]); + assertEquals("month202101", cache.getMonthSummary(202101)); + assertEquals(1, monthCount[0]); + assertEquals(1, yearCount[0]); + assertEquals("year2021", cache.getYearSummary(2021)); + assertEquals(1, monthCount[0]); + assertEquals(1, yearCount[0]); + + assertEquals("month202102", cache.getMonthSummary(202102)); + assertEquals(2, monthCount[0]); + assertEquals(1, yearCount[0]); + assertEquals("year2022", cache.getYearSummary(2022)); + assertEquals(2, monthCount[0]); + assertEquals(2, yearCount[0]); + + cache.clear(); + + assertEquals("month202101", cache.getMonthSummary(202101)); + assertEquals(3, monthCount[0]); + assertEquals(2, yearCount[0]); + assertEquals("year2021", cache.getYearSummary(2021)); + assertEquals(3, monthCount[0]); + assertEquals(3, yearCount[0]); + assertEquals("month202101", cache.getMonthSummary(202101)); + assertEquals(3, monthCount[0]); + assertEquals(3, yearCount[0]); + assertEquals("year2021", cache.getYearSummary(2021)); + assertEquals(3, monthCount[0]); + assertEquals(3, yearCount[0]); + + assertEquals("month202102", cache.getMonthSummary(202102)); + assertEquals(4, monthCount[0]); + assertEquals(3, yearCount[0]); + assertEquals("year2022", cache.getYearSummary(2022)); + assertEquals(4, monthCount[0]); + assertEquals(4, yearCount[0]); + + assertEquals(cache.getMonthSummary(202101), cache.getMonthSummary(2021, 1)); } private static Stream iteratorToStream(Iterator iterator) { From 4da4729552128b6ba03eaf72e60d85a260530e5c Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Fri, 17 May 2024 14:10:40 -0600 Subject: [PATCH 31/54] Moved to a KeyedIntObjectHash. --- .../time/calendar/BusinessCalendar.java | 33 +++-- .../io/deephaven/time/calendar/Calendar.java | 13 +- .../calendar/ImmutableConcurrentCache.java | 135 ++++++++++++++++++ .../ImmutableReadHeavyConcurrentCache.java | 84 ----------- .../time/calendar/YearMonthSummaryCache.java | 10 +- .../TestImmutableConcurrentCache.java | 56 ++++++++ ...TestImmutableReadHeavyConcurrentCache.java | 50 ------- .../calendar/TestYearMonthSummaryCache.java | 94 ++++++------ 8 files changed, 276 insertions(+), 199 deletions(-) create mode 100644 engine/time/src/main/java/io/deephaven/time/calendar/ImmutableConcurrentCache.java delete mode 100644 engine/time/src/main/java/io/deephaven/time/calendar/ImmutableReadHeavyConcurrentCache.java create mode 100644 engine/time/src/test/java/io/deephaven/time/calendar/TestImmutableConcurrentCache.java delete mode 100644 engine/time/src/test/java/io/deephaven/time/calendar/TestImmutableReadHeavyConcurrentCache.java diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java index 149696c5a8a..192c2e441f7 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java @@ -57,7 +57,7 @@ public InvalidDateException(final String message, final Throwable cause) { // region Cache - private static class SummaryData { + private static class SummaryData extends ImmutableConcurrentCache.IntKeyedValue { private final Instant startInstant; private final LocalDate startDate; private final Instant endInstant; @@ -69,6 +69,7 @@ private static class SummaryData { private final ArrayList nonBusinessDates; public SummaryData( + final int key, final Instant startInstant, final LocalDate startDate, final Instant endInstant, @@ -78,6 +79,7 @@ public SummaryData( final int nonBusinessDays, final ArrayList businessDates, final ArrayList nonBusinessDates) { + super(key); this.startInstant = startInstant; this.startDate = startDate; this.endInstant = endInstant; @@ -90,8 +92,8 @@ public SummaryData( } } - private final ImmutableReadHeavyConcurrentCache> schedulesCache = - new ImmutableReadHeavyConcurrentCache<>(this::computeCalendarDay); + private final ImmutableConcurrentCache>> schedulesCache = + new ImmutableConcurrentCache<>(10000, this::computeCalendarDay); private final YearMonthSummaryCache summaryCache = new YearMonthSummaryCache<>(this::computeMonthSummary, this::computeYearSummary); private final int yearCacheStart; @@ -104,7 +106,7 @@ public synchronized void clearCache() { summaryCache.clear(); } - private SummaryData summarize(final LocalDate startDate, final LocalDate endDate) { + private SummaryData summarize(final int key, final LocalDate startDate, final LocalDate endDate) { final ZonedDateTime start = startDate.atTime(0, 0).atZone(timeZone()); final ZonedDateTime end = endDate.atTime(0, 0).atZone(timeZone()); @@ -134,6 +136,7 @@ private SummaryData summarize(final LocalDate startDate, final LocalDate endDate } return new SummaryData( + key, start.toInstant(), start.toLocalDate(), end.toInstant(), @@ -150,7 +153,7 @@ private SummaryData computeMonthSummary(final int yearMonth) { final int month = yearMonth % 100; final LocalDate startDate = LocalDate.of(year, month, 1); final LocalDate endDate = startDate.plusMonths(1); // exclusive - return summarize(startDate, endDate); + return summarize(yearMonth, startDate, endDate); } private SummaryData computeYearSummary(final int year) { @@ -184,6 +187,7 @@ private SummaryData computeYearSummary(final int year) { } return new SummaryData( + year, startInstant, startDate, endInstant, @@ -205,16 +209,24 @@ private SummaryData getYearSummary(final int year) { } - private CalendarDay computeCalendarDay(final LocalDate date) { + private ImmutableConcurrentCache.Pair> computeCalendarDay(final int yearMonthDay) { + final int year = yearMonthDay / 10000; + final int monthDay = yearMonthDay % 10000; + final int month = monthDay / 100; + final int day = monthDay % 100; + final LocalDate date = LocalDate.of(year, month, day); final CalendarDay h = holidays.get(date); + final CalendarDay v; if (h != null) { - return h; + v = h; } else if (weekendDays.contains(date.getDayOfWeek())) { - return CalendarDay.toInstant(CalendarDay.HOLIDAY, date, timeZone()); + v = CalendarDay.toInstant(CalendarDay.HOLIDAY, date, timeZone()); } else { - return CalendarDay.toInstant(standardBusinessDay, date, timeZone()); + v = CalendarDay.toInstant(standardBusinessDay, date, timeZone()); } + + return new ImmutableConcurrentCache.Pair<>(yearMonthDay, v); } // endregion @@ -346,7 +358,8 @@ public CalendarDay calendarDay(final LocalDate date) { + " lastValidDate=" + lastValidDate); } - return schedulesCache.get(date); + final int yearMonthDay = date.getYear() * 10000 + date.getMonthValue() * 100 + date.getDayOfMonth(); + return schedulesCache.get(yearMonthDay).getValue(); } /** diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java index 1da271d4005..ea06d203c78 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java @@ -30,12 +30,13 @@ public class Calendar { // region Cache - private static class SummaryData { + private static class SummaryData extends ImmutableConcurrentCache.IntKeyedValue { final LocalDate startDate; final LocalDate endDate; // exclusive final List dates; - SummaryData(LocalDate startDate, LocalDate endDate, List dates) { + SummaryData(int key, LocalDate startDate, LocalDate endDate, List dates) { + super(key); this.startDate = startDate; this.endDate = endDate; this.dates = dates; @@ -46,7 +47,7 @@ private static class SummaryData { private final YearMonthSummaryCache summaryCache = new YearMonthSummaryCache<>(this::computeMonthSummary, this::computeYearSummary); - private SummaryData summarize(final LocalDate startDate, final LocalDate endDate) { + private SummaryData summarize(final int key, final LocalDate startDate, final LocalDate endDate) { LocalDate date = startDate; final ArrayList dates = new ArrayList<>(); @@ -55,7 +56,7 @@ private SummaryData summarize(final LocalDate startDate, final LocalDate endDate date = date.plusDays(1); } - return new SummaryData(startDate, endDate, dates); // end date is exclusive + return new SummaryData(key, startDate, endDate, dates); // end date is exclusive } private SummaryData computeMonthSummary(final int yearMonth) { @@ -63,7 +64,7 @@ private SummaryData computeMonthSummary(final int yearMonth) { final int month = yearMonth % 100; final LocalDate startDate = LocalDate.of(year, month, 1); final LocalDate endDate = startDate.plusMonths(1); // exclusive - return summarize(startDate, endDate); + return summarize(yearMonth, startDate, endDate); } private SummaryData computeYearSummary(final int year) { @@ -84,7 +85,7 @@ private SummaryData computeYearSummary(final int year) { dates.addAll(ms.dates); } - return new SummaryData(startDate, endDate, dates); + return new SummaryData(year, startDate, endDate, dates); } // endregion diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/ImmutableConcurrentCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/ImmutableConcurrentCache.java new file mode 100644 index 00000000000..d172aa76236 --- /dev/null +++ b/engine/time/src/main/java/io/deephaven/time/calendar/ImmutableConcurrentCache.java @@ -0,0 +1,135 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.time.calendar; + +import io.deephaven.hash.KeyedIntObjectHash; +import io.deephaven.hash.KeyedIntObjectKey; + +import java.util.function.Function; + +/** + * An immutable cache that is designed to be fast when accessed concurrently with read-heavy workloads. Values are + * populated from a function when they are not found in the cache. All values must be non-null. + * + * @param the value type + */ +class ImmutableConcurrentCache { + + /** + * A value that has an included integer key. + */ + public static abstract class IntKeyedValue { + private final int key; + + /** + * Creates a new value. + * + * @param key the key + */ + public IntKeyedValue(int key) { + this.key = key; + } + + /** + * Gets the key. + * + * @return the key + */ + public int getKey() { + return key; + } + } + + /** + * A pair of a key and a value. + * + * @param the value type + */ + public static class Pair extends IntKeyedValue { + private final T value; + + /** + * Creates a new pair. + * + * @param key the key + * @param value the value + */ + public Pair(int key, T value) { + super(key); + this.value = value; + } + + /** + * Gets the value. + * + * @return the value + */ + public T getValue() { + return value; + } + } + + private static class KeyDef extends KeyedIntObjectKey.Strict { + + @Override + public int getIntKey(V v) { + return v.getKey(); + } + + @Override + public int hashIntKey(int i) { + return i; + } + + @Override + public boolean equalIntKey(int i, V v) { + return i == v.getKey(); + } + } + + private final Function valueComputer; + private final KeyedIntObjectHash cache; + + /** + * Creates a new cache. + * + * @param initialCapacity the initial capacity + * @param valueComputer computes the value for a key. + */ + public ImmutableConcurrentCache(final int initialCapacity, final Function valueComputer) { + this.valueComputer = valueComputer; + this.cache = new KeyedIntObjectHash<>(initialCapacity, new KeyDef()); + } + + /** + * Clears the cache. + */ + synchronized void clear() { + cache.clear(); + } + + /** + * Gets the value for a key. If the value is not found, it is computed and added to the cache. + * + * @param key the key + * @return the value + * @throws IllegalArgumentException if the value is not found + */ + public V get(int key) { + V existing = cache.get(key); + + if (existing != null) { + return existing; + } + + final V newValue = valueComputer.apply(key); + + if (newValue == null) { + throw new IllegalArgumentException("Computed a null value: key=" + key); + } + + existing = cache.putIfAbsent(key, newValue); + return existing == null ? newValue : existing; + } +} diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/ImmutableReadHeavyConcurrentCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/ImmutableReadHeavyConcurrentCache.java deleted file mode 100644 index c59a01cd839..00000000000 --- a/engine/time/src/main/java/io/deephaven/time/calendar/ImmutableReadHeavyConcurrentCache.java +++ /dev/null @@ -1,84 +0,0 @@ -// -// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending -// -package io.deephaven.time.calendar; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.locks.StampedLock; -import java.util.function.Function; - -/** - * An immutable cache that is designed to be fast when accessed concurrently with read-heavy workloads. Values are - * populated from a function when they are not found in the cache. All values must be non-null. - * - * @param the key type - * @param the value type - */ -class ImmutableReadHeavyConcurrentCache { - - private final Function valueComputer; - private final StampedLock lock = new StampedLock(); - private final Map cache = new HashMap<>(); - - /** - * Creates a new cache. - * - * @param valueComputer computes the value for a key. - */ - public ImmutableReadHeavyConcurrentCache(final Function valueComputer) { - this.valueComputer = valueComputer; - } - - /** - * Clears the cache. - */ - synchronized void clear() { - long stamp = lock.writeLock(); - try { - cache.clear(); - } finally { - lock.unlock(stamp); - } - } - - /** - * Gets the value for a key. If the value is not found, it is computed and added to the cache. - * - * @param key the key - * @return the value - * @throws IllegalArgumentException if the value is not found - */ - public V get(K key) { - long stamp = lock.tryOptimisticRead(); - V existing = cache.get(key); - - if (!lock.validate(stamp)) { - stamp = lock.readLock(); - try { - existing = cache.get(key); - } finally { - lock.unlockRead(stamp); - } - } - - if (existing != null) { - return existing; - } - - stamp = lock.writeLock(); - - try { - final V newValue = valueComputer.apply(key); - - if (newValue == null) { - throw new IllegalArgumentException("Computed a null value: key=" + key); - } - - existing = cache.putIfAbsent(key, newValue); - return existing == null ? newValue : existing; - } finally { - lock.unlockWrite(stamp); - } - } -} diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java index 4b96a389868..55196b2f9fc 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java @@ -12,10 +12,10 @@ * * @param the type of the summary */ -class YearMonthSummaryCache { +class YearMonthSummaryCache { - private final ImmutableReadHeavyConcurrentCache monthCache; - private final ImmutableReadHeavyConcurrentCache yearCache; + private final ImmutableConcurrentCache monthCache; + private final ImmutableConcurrentCache yearCache; /** * Creates a new cache. @@ -24,8 +24,8 @@ class YearMonthSummaryCache { * @param computeYearSummary the function to compute a year summary */ YearMonthSummaryCache(Function computeMonthSummary, Function computeYearSummary) { - monthCache = new ImmutableReadHeavyConcurrentCache<>(computeMonthSummary); - yearCache = new ImmutableReadHeavyConcurrentCache<>(computeYearSummary); + monthCache = new ImmutableConcurrentCache<>(12*50, computeMonthSummary); + yearCache = new ImmutableConcurrentCache<>(50, computeYearSummary); } /** diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestImmutableConcurrentCache.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestImmutableConcurrentCache.java new file mode 100644 index 00000000000..f922f2b5e7a --- /dev/null +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestImmutableConcurrentCache.java @@ -0,0 +1,56 @@ +// +// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending +// +package io.deephaven.time.calendar; + +import io.deephaven.base.testing.BaseArrayTestCase; + +public class TestImmutableConcurrentCache extends BaseArrayTestCase { + + private static class Value extends ImmutableConcurrentCache.Pair { + Value(int key, String value) { + super(key, value); + } + } + + private static Value makeVal(Integer key) { + final Character c = (char) (key + 'a'); + + if (c.equals('d')) { + return null; + } + + return new Value(key, c.toString().toUpperCase()); + } + + public void testCache() { + final ImmutableConcurrentCache cache = + new ImmutableConcurrentCache<>(10, TestImmutableConcurrentCache::makeVal); + + assertEquals("A", cache.get(0).getValue()); + assertEquals("A", cache.get(0).getValue()); + + assertEquals("A", cache.get(0).getValue()); + assertEquals("B", cache.get(1).getValue()); + assertEquals("C", cache.get(2).getValue()); + + try { + cache.get(3); + fail("Expected exception"); + } catch (final IllegalArgumentException e) { + // pass + } + + cache.clear(); + assertEquals("A", cache.get(0).getValue()); + assertEquals("B", cache.get(1).getValue()); + assertEquals("C", cache.get(2).getValue()); + + try { + cache.get(3); + fail("Expected exception"); + } catch (final IllegalArgumentException e) { + // pass + } + } +} diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestImmutableReadHeavyConcurrentCache.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestImmutableReadHeavyConcurrentCache.java deleted file mode 100644 index 0de34298ee7..00000000000 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestImmutableReadHeavyConcurrentCache.java +++ /dev/null @@ -1,50 +0,0 @@ -// -// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending -// -package io.deephaven.time.calendar; - -import io.deephaven.base.testing.BaseArrayTestCase; - -import java.util.List; - -public class TestImmutableReadHeavyConcurrentCache extends BaseArrayTestCase { - - private static String makeVal(String s) { - if (s.equals("d")) { - return null; - } - - return s.toUpperCase(); - } - - public void testCache() { - final ImmutableReadHeavyConcurrentCache cache = - new ImmutableReadHeavyConcurrentCache<>(TestImmutableReadHeavyConcurrentCache::makeVal); - - assertEquals("A", cache.get("a")); - assertEquals("A", cache.get("a")); - - assertEquals("A", cache.get("a")); - assertEquals("B", cache.get("b")); - assertEquals("C", cache.get("c")); - - try { - cache.get("d"); - fail("Expected exception"); - } catch (final IllegalArgumentException e) { - // pass - } - - cache.clear(); - assertEquals("A", cache.get("a")); - assertEquals("B", cache.get("b")); - assertEquals("C", cache.get("c")); - - try { - cache.get("d"); - fail("Expected exception"); - } catch (final IllegalArgumentException e) { - // pass - } - } -} diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java index cf303c82a0f..6c3fb9b15b1 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java @@ -16,69 +16,75 @@ @SuppressWarnings({"DataFlowIssue", "ConstantValue"}) public class TestYearMonthSummaryCache extends BaseArrayTestCase { + private static class Value extends ImmutableConcurrentCache.Pair { + Value(int key, String value) { + super(key, value); + } + } + public void testGetters() { final int[] monthCount = new int[] {0}; final int[] yearCount = new int[] {0}; - final Function monthSummary = i -> { + final Function monthSummary = i -> { monthCount[0]++; - return "month" + i; + return new Value(i, "month" + i); }; - final Function yearSummary = i -> { + final Function yearSummary = i -> { yearCount[0]++; - return "year" + i; + return new Value(i, "year" + i); }; - final YearMonthSummaryCache cache = new YearMonthSummaryCache<>(monthSummary, yearSummary); + final YearMonthSummaryCache cache = new YearMonthSummaryCache<>(monthSummary, yearSummary); cache.clear(); monthCount[0] = 0; yearCount[0] = 0; - assertEquals("month202101", cache.getMonthSummary(202101)); + assertEquals("month202101", cache.getMonthSummary(202101).getValue()); assertEquals(1, monthCount[0]); assertEquals(0, yearCount[0]); - assertEquals("year2021", cache.getYearSummary(2021)); + assertEquals("year2021", cache.getYearSummary(2021).getValue()); assertEquals(1, monthCount[0]); assertEquals(1, yearCount[0]); - assertEquals("month202101", cache.getMonthSummary(202101)); + assertEquals("month202101", cache.getMonthSummary(202101).getValue()); assertEquals(1, monthCount[0]); assertEquals(1, yearCount[0]); - assertEquals("year2021", cache.getYearSummary(2021)); + assertEquals("year2021", cache.getYearSummary(2021).getValue()); assertEquals(1, monthCount[0]); assertEquals(1, yearCount[0]); - assertEquals("month202102", cache.getMonthSummary(202102)); + assertEquals("month202102", cache.getMonthSummary(202102).getValue()); assertEquals(2, monthCount[0]); assertEquals(1, yearCount[0]); - assertEquals("year2022", cache.getYearSummary(2022)); + assertEquals("year2022", cache.getYearSummary(2022).getValue()); assertEquals(2, monthCount[0]); assertEquals(2, yearCount[0]); cache.clear(); - assertEquals("month202101", cache.getMonthSummary(202101)); + assertEquals("month202101", cache.getMonthSummary(202101).getValue()); assertEquals(3, monthCount[0]); assertEquals(2, yearCount[0]); - assertEquals("year2021", cache.getYearSummary(2021)); + assertEquals("year2021", cache.getYearSummary(2021).getValue()); assertEquals(3, monthCount[0]); assertEquals(3, yearCount[0]); - assertEquals("month202101", cache.getMonthSummary(202101)); + assertEquals("month202101", cache.getMonthSummary(202101).getValue()); assertEquals(3, monthCount[0]); assertEquals(3, yearCount[0]); - assertEquals("year2021", cache.getYearSummary(2021)); + assertEquals("year2021", cache.getYearSummary(2021).getValue()); assertEquals(3, monthCount[0]); assertEquals(3, yearCount[0]); - assertEquals("month202102", cache.getMonthSummary(202102)); + assertEquals("month202102", cache.getMonthSummary(202102).getValue()); assertEquals(4, monthCount[0]); assertEquals(3, yearCount[0]); - assertEquals("year2022", cache.getYearSummary(2022)); + assertEquals("year2022", cache.getYearSummary(2022).getValue()); assertEquals(4, monthCount[0]); assertEquals(4, yearCount[0]); - assertEquals(cache.getMonthSummary(202101), cache.getMonthSummary(2021, 1)); + assertEquals(cache.getMonthSummary(202101).getValue(), cache.getMonthSummary(2021, 1).getValue()); } private static Stream iteratorToStream(Iterator iterator) { @@ -87,7 +93,7 @@ private static Stream iteratorToStream(Iterator iterator) { } public void testIteratorInclusive() { - final YearMonthSummaryCache cache = new YearMonthSummaryCache<>(i -> "month" + i, i -> "year" + i); + final YearMonthSummaryCache cache = new YearMonthSummaryCache<>(i -> new Value(i, "month" + i), i -> new Value(i, "year" + i)); final boolean startInclusive = true; final boolean endInclusive = true; @@ -117,49 +123,49 @@ public void testIteratorInclusive() { start = LocalDate.of(2021, 1, 1); end = LocalDate.of(2021, 2, 11); target = new String[] {"month202101"}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); assertEquals(target, actual); // full month + few days start = LocalDate.of(2020, 12, 12); end = LocalDate.of(2021, 2, 11); target = new String[] {"month202101"}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); assertEquals(target, actual); // multiple months + few days start = LocalDate.of(2020, 11, 12); end = LocalDate.of(2021, 4, 11); target = new String[] {"month202012", "month202101", "month202102", "month202103"}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); assertEquals(target, actual); // partial month + full month start = LocalDate.of(2021, 1, 3); end = LocalDate.of(2021, 2, 28); target = new String[] {"month202102"}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); assertEquals(target, actual); // full year start = LocalDate.of(2021, 1, 1); end = LocalDate.of(2021, 12, 31); target = new String[] {"year2021"}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); assertEquals(target, actual); // full year + few days start = LocalDate.of(2020, 12, 11); end = LocalDate.of(2022, 1, 3); target = new String[] {"year2021"}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); assertEquals(target, actual); // multiple years + few days start = LocalDate.of(2018, 12, 11); end = LocalDate.of(2022, 1, 3); target = new String[] {"year2019", "year2020", "year2021"}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); assertEquals(target, actual); // mixed @@ -167,12 +173,12 @@ public void testIteratorInclusive() { end = LocalDate.of(2022, 3, 3); target = new String[] {"month201811", "month201812", "year2019", "year2020", "year2021", "month202201", "month202202"}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); assertEquals(target, actual); } public void testIteratorExclusiveInclusive() { - final YearMonthSummaryCache cache = new YearMonthSummaryCache<>(i -> "month" + i, i -> "year" + i); + final YearMonthSummaryCache cache = new YearMonthSummaryCache<>(i -> new Value(i, "month" + i), i -> new Value(i, "year" + i)); // start and end of month @@ -183,25 +189,25 @@ public void testIteratorExclusiveInclusive() { boolean endInclusive = true; String[] target = new String[] {"month202112"}; String[] actual = - iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); assertEquals(target, actual); startInclusive = false; endInclusive = true; target = new String[] {}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); assertEquals(target, actual); startInclusive = true; endInclusive = false; target = new String[] {}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); assertEquals(target, actual); startInclusive = false; endInclusive = false; target = new String[] {}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); assertEquals(target, actual); // day before start of month @@ -212,25 +218,25 @@ public void testIteratorExclusiveInclusive() { startInclusive = true; endInclusive = true; target = new String[] {"month202112"}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); assertEquals(target, actual); startInclusive = false; endInclusive = true; target = new String[] {"month202112"}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); assertEquals(target, actual); startInclusive = true; endInclusive = false; target = new String[] {}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); assertEquals(target, actual); startInclusive = false; endInclusive = false; target = new String[] {}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); assertEquals(target, actual); // day after end of month @@ -241,25 +247,25 @@ public void testIteratorExclusiveInclusive() { startInclusive = true; endInclusive = true; target = new String[] {"month202112"}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); assertEquals(target, actual); startInclusive = false; endInclusive = true; target = new String[] {}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); assertEquals(target, actual); startInclusive = true; endInclusive = false; target = new String[] {"month202112"}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); assertEquals(target, actual); startInclusive = false; endInclusive = false; target = new String[] {}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); assertEquals(target, actual); // day before and after end of month @@ -270,25 +276,25 @@ public void testIteratorExclusiveInclusive() { startInclusive = true; endInclusive = true; target = new String[] {"month202112"}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); assertEquals(target, actual); startInclusive = false; endInclusive = true; target = new String[] {"month202112"}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); assertEquals(target, actual); startInclusive = true; endInclusive = false; target = new String[] {"month202112"}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); assertEquals(target, actual); startInclusive = false; endInclusive = false; target = new String[] {"month202112"}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); assertEquals(target, actual); } } From 8e92d1efbc445d6c5dac8e80c7ff4f2f045b3a49 Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Fri, 17 May 2024 14:13:06 -0600 Subject: [PATCH 32/54] Moved to a IntFunction. --- .../deephaven/time/calendar/ImmutableConcurrentCache.java | 8 ++++---- .../io/deephaven/time/calendar/YearMonthSummaryCache.java | 4 ++-- .../time/calendar/TestYearMonthSummaryCache.java | 5 +++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/ImmutableConcurrentCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/ImmutableConcurrentCache.java index d172aa76236..2d7f43531d4 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/ImmutableConcurrentCache.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/ImmutableConcurrentCache.java @@ -6,7 +6,7 @@ import io.deephaven.hash.KeyedIntObjectHash; import io.deephaven.hash.KeyedIntObjectKey; -import java.util.function.Function; +import java.util.function.IntFunction; /** * An immutable cache that is designed to be fast when accessed concurrently with read-heavy workloads. Values are @@ -88,7 +88,7 @@ public boolean equalIntKey(int i, V v) { } } - private final Function valueComputer; + private final IntFunction valueComputer; private final KeyedIntObjectHash cache; /** @@ -97,9 +97,9 @@ public boolean equalIntKey(int i, V v) { * @param initialCapacity the initial capacity * @param valueComputer computes the value for a key. */ - public ImmutableConcurrentCache(final int initialCapacity, final Function valueComputer) { + public ImmutableConcurrentCache(final int initialCapacity, final IntFunction valueComputer) { this.valueComputer = valueComputer; - this.cache = new KeyedIntObjectHash<>(initialCapacity, new KeyDef()); + this.cache = new KeyedIntObjectHash<>(initialCapacity, new KeyDef<>()); } /** diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java index 55196b2f9fc..50c1691237b 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java @@ -5,7 +5,7 @@ import java.time.LocalDate; import java.util.Iterator; -import java.util.function.Function; +import java.util.function.IntFunction; /** * A thread-safe lazily initialized cache for year and month summaries. @@ -23,7 +23,7 @@ class YearMonthSummaryCache { * @param computeMonthSummary the function to compute a month summary * @param computeYearSummary the function to compute a year summary */ - YearMonthSummaryCache(Function computeMonthSummary, Function computeYearSummary) { + YearMonthSummaryCache(IntFunction computeMonthSummary, IntFunction computeYearSummary) { monthCache = new ImmutableConcurrentCache<>(12*50, computeMonthSummary); yearCache = new ImmutableConcurrentCache<>(50, computeYearSummary); } diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java index 6c3fb9b15b1..0ea1ed92d47 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java @@ -10,6 +10,7 @@ import java.util.Spliterator; import java.util.Spliterators; import java.util.function.Function; +import java.util.function.IntFunction; import java.util.stream.Stream; import java.util.stream.StreamSupport; @@ -26,12 +27,12 @@ public void testGetters() { final int[] monthCount = new int[] {0}; final int[] yearCount = new int[] {0}; - final Function monthSummary = i -> { + final IntFunction monthSummary = i -> { monthCount[0]++; return new Value(i, "month" + i); }; - final Function yearSummary = i -> { + final IntFunction yearSummary = i -> { yearCount[0]++; return new Value(i, "year" + i); }; From 050defa5256be0a8a7d61b479b4278f281989587 Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Fri, 17 May 2024 14:13:57 -0600 Subject: [PATCH 33/54] Spotless --- .../time/calendar/YearMonthSummaryCache.java | 2 +- .../calendar/TestYearMonthSummaryCache.java | 78 ++++++++++++------- 2 files changed, 53 insertions(+), 27 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java index 50c1691237b..a467abbc2bd 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java @@ -24,7 +24,7 @@ class YearMonthSummaryCache { * @param computeYearSummary the function to compute a year summary */ YearMonthSummaryCache(IntFunction computeMonthSummary, IntFunction computeYearSummary) { - monthCache = new ImmutableConcurrentCache<>(12*50, computeMonthSummary); + monthCache = new ImmutableConcurrentCache<>(12 * 50, computeMonthSummary); yearCache = new ImmutableConcurrentCache<>(50, computeYearSummary); } diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java index 0ea1ed92d47..b4eb7ffca84 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java @@ -94,7 +94,8 @@ private static Stream iteratorToStream(Iterator iterator) { } public void testIteratorInclusive() { - final YearMonthSummaryCache cache = new YearMonthSummaryCache<>(i -> new Value(i, "month" + i), i -> new Value(i, "year" + i)); + final YearMonthSummaryCache cache = + new YearMonthSummaryCache<>(i -> new Value(i, "month" + i), i -> new Value(i, "year" + i)); final boolean startInclusive = true; final boolean endInclusive = true; @@ -124,49 +125,56 @@ public void testIteratorInclusive() { start = LocalDate.of(2021, 1, 1); end = LocalDate.of(2021, 2, 11); target = new String[] {"month202101"}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x -> x.getValue()) + .toArray(String[]::new); assertEquals(target, actual); // full month + few days start = LocalDate.of(2020, 12, 12); end = LocalDate.of(2021, 2, 11); target = new String[] {"month202101"}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x -> x.getValue()) + .toArray(String[]::new); assertEquals(target, actual); // multiple months + few days start = LocalDate.of(2020, 11, 12); end = LocalDate.of(2021, 4, 11); target = new String[] {"month202012", "month202101", "month202102", "month202103"}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x -> x.getValue()) + .toArray(String[]::new); assertEquals(target, actual); // partial month + full month start = LocalDate.of(2021, 1, 3); end = LocalDate.of(2021, 2, 28); target = new String[] {"month202102"}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x -> x.getValue()) + .toArray(String[]::new); assertEquals(target, actual); // full year start = LocalDate.of(2021, 1, 1); end = LocalDate.of(2021, 12, 31); target = new String[] {"year2021"}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x -> x.getValue()) + .toArray(String[]::new); assertEquals(target, actual); // full year + few days start = LocalDate.of(2020, 12, 11); end = LocalDate.of(2022, 1, 3); target = new String[] {"year2021"}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x -> x.getValue()) + .toArray(String[]::new); assertEquals(target, actual); // multiple years + few days start = LocalDate.of(2018, 12, 11); end = LocalDate.of(2022, 1, 3); target = new String[] {"year2019", "year2020", "year2021"}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x -> x.getValue()) + .toArray(String[]::new); assertEquals(target, actual); // mixed @@ -174,12 +182,14 @@ public void testIteratorInclusive() { end = LocalDate.of(2022, 3, 3); target = new String[] {"month201811", "month201812", "year2019", "year2020", "year2021", "month202201", "month202202"}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x -> x.getValue()) + .toArray(String[]::new); assertEquals(target, actual); } public void testIteratorExclusiveInclusive() { - final YearMonthSummaryCache cache = new YearMonthSummaryCache<>(i -> new Value(i, "month" + i), i -> new Value(i, "year" + i)); + final YearMonthSummaryCache cache = + new YearMonthSummaryCache<>(i -> new Value(i, "month" + i), i -> new Value(i, "year" + i)); // start and end of month @@ -190,25 +200,29 @@ public void testIteratorExclusiveInclusive() { boolean endInclusive = true; String[] target = new String[] {"month202112"}; String[] actual = - iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); + iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x -> x.getValue()) + .toArray(String[]::new); assertEquals(target, actual); startInclusive = false; endInclusive = true; target = new String[] {}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x -> x.getValue()) + .toArray(String[]::new); assertEquals(target, actual); startInclusive = true; endInclusive = false; target = new String[] {}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x -> x.getValue()) + .toArray(String[]::new); assertEquals(target, actual); startInclusive = false; endInclusive = false; target = new String[] {}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x -> x.getValue()) + .toArray(String[]::new); assertEquals(target, actual); // day before start of month @@ -219,25 +233,29 @@ public void testIteratorExclusiveInclusive() { startInclusive = true; endInclusive = true; target = new String[] {"month202112"}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x -> x.getValue()) + .toArray(String[]::new); assertEquals(target, actual); startInclusive = false; endInclusive = true; target = new String[] {"month202112"}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x -> x.getValue()) + .toArray(String[]::new); assertEquals(target, actual); startInclusive = true; endInclusive = false; target = new String[] {}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x -> x.getValue()) + .toArray(String[]::new); assertEquals(target, actual); startInclusive = false; endInclusive = false; target = new String[] {}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x -> x.getValue()) + .toArray(String[]::new); assertEquals(target, actual); // day after end of month @@ -248,25 +266,29 @@ public void testIteratorExclusiveInclusive() { startInclusive = true; endInclusive = true; target = new String[] {"month202112"}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x -> x.getValue()) + .toArray(String[]::new); assertEquals(target, actual); startInclusive = false; endInclusive = true; target = new String[] {}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x -> x.getValue()) + .toArray(String[]::new); assertEquals(target, actual); startInclusive = true; endInclusive = false; target = new String[] {"month202112"}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x -> x.getValue()) + .toArray(String[]::new); assertEquals(target, actual); startInclusive = false; endInclusive = false; target = new String[] {}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x -> x.getValue()) + .toArray(String[]::new); assertEquals(target, actual); // day before and after end of month @@ -277,25 +299,29 @@ public void testIteratorExclusiveInclusive() { startInclusive = true; endInclusive = true; target = new String[] {"month202112"}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x -> x.getValue()) + .toArray(String[]::new); assertEquals(target, actual); startInclusive = false; endInclusive = true; target = new String[] {"month202112"}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x -> x.getValue()) + .toArray(String[]::new); assertEquals(target, actual); startInclusive = true; endInclusive = false; target = new String[] {"month202112"}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x -> x.getValue()) + .toArray(String[]::new); assertEquals(target, actual); startInclusive = false; endInclusive = false; target = new String[] {"month202112"}; - actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x->x.getValue()).toArray(String[]::new); + actual = iteratorToStream(cache.iterator(start, end, startInclusive, endInclusive)).map(x -> x.getValue()) + .toArray(String[]::new); assertEquals(target, actual); } } From 64911dab3fe143600f083b109309ec8d51ab2df9 Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Fri, 24 May 2024 10:12:39 -0600 Subject: [PATCH 34/54] Addressing code review. --- .../io/deephaven/time/calendar/TestStaticCalendarMethods.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestStaticCalendarMethods.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestStaticCalendarMethods.java index 31f738d8dbf..6ebb243edf7 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestStaticCalendarMethods.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestStaticCalendarMethods.java @@ -107,8 +107,6 @@ public void testAll() { excludes.add("firstValidDate"); excludes.add("lastValidDate"); excludes.add("clearCache"); - excludes.add("enableFastCache"); - excludes.add("isFastCache"); for (Method m1 : BusinessCalendar.class.getMethods()) { if (m1.getDeclaringClass() == Object.class || From 46320dba7d0362822da428b9dbeb66b830dc5b9a Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Fri, 24 May 2024 10:19:03 -0600 Subject: [PATCH 35/54] Addressing code review. --- .../io/deephaven/time/calendar/CalendarDay.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/CalendarDay.java b/engine/time/src/main/java/io/deephaven/time/calendar/CalendarDay.java index d1f60b68d79..ca31a9bc626 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/CalendarDay.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/CalendarDay.java @@ -120,11 +120,15 @@ public boolean isInclusiveEnd() { * @return length of the day in nanoseconds */ public long businessNanos() { - if (businessNanos < 0) { - businessNanos = Arrays.stream(businessTimeRanges).map(TimeRange::nanos).reduce(0L, Long::sum); + // Only read volatile 1x + long bn = businessNanos; + + if (bn < 0) { + bn = Arrays.stream(businessTimeRanges).map(TimeRange::nanos).reduce(0L, Long::sum); + businessNanos = bn; } - return businessNanos; + return bn; } /** @@ -239,6 +243,7 @@ public boolean isBusinessTime(final T time) { return false; } + @Override @Override public boolean equals(Object o) { if (this == o) @@ -246,7 +251,7 @@ public boolean equals(Object o) { if (!(o instanceof CalendarDay)) return false; CalendarDay that = (CalendarDay) o; - return businessNanos == that.businessNanos && Arrays.equals(businessTimeRanges, that.businessTimeRanges); + return Arrays.equals(businessTimeRanges, that.businessTimeRanges); } @Override From 166074ccd7e79c6a3f9258ff32165e3ab412dfc8 Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Fri, 24 May 2024 10:51:49 -0600 Subject: [PATCH 36/54] Addressing code review. --- .../deephaven/time/calendar/CalendarDay.java | 1 - .../time/calendar/TestBusinessCalendar.java | 53 +++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/CalendarDay.java b/engine/time/src/main/java/io/deephaven/time/calendar/CalendarDay.java index ca31a9bc626..64c24e435ce 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/CalendarDay.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/CalendarDay.java @@ -243,7 +243,6 @@ public boolean isBusinessTime(final T time) { return false; } - @Override @Override public boolean equals(Object o) { if (this == o) diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestBusinessCalendar.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestBusinessCalendar.java index f3cb5128629..7059c81d607 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestBusinessCalendar.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestBusinessCalendar.java @@ -789,6 +789,59 @@ public void testNumberBusinessDates() { assertEquals(targetLong.size(), bCalendar.numberBusinessDates(startLong, endLong)); } + public void testBusinessDatesValidateCacheIteration() { + // Construct a very simple calendar for counting business days that is easy to reason about + + final LocalDate firstValidDateLocal = LocalDate.of(2000, 1, 1); + final LocalDate lastValidDateLocal = LocalDate.of(2030, 12, 31); + final Set weekendDaysLocal = Set.of(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY); + final Map> holidaysLocal = new HashMap<>(); + final BusinessCalendar bc = new BusinessCalendar("Test", "Test", timeZone, firstValidDateLocal, + lastValidDateLocal, schedule, weekendDaysLocal, holidaysLocal); + + // short period -- no caching case + + LocalDate start = LocalDate.of(2024, 5, 15); + LocalDate end = LocalDate.of(2024, 5, 29); + + LocalDate[] target = { + LocalDate.of(2024, 5, 15), + LocalDate.of(2024, 5, 16), + LocalDate.of(2024, 5, 17), + LocalDate.of(2024, 5, 20), + LocalDate.of(2024, 5, 21), + LocalDate.of(2024, 5, 22), + LocalDate.of(2024, 5, 23), + LocalDate.of(2024, 5, 24), + LocalDate.of(2024, 5, 27), + LocalDate.of(2024, 5, 28), + LocalDate.of(2024, 5, 29), + }; + + assertEquals(target, bc.businessDates(start, end)); + assertEquals(target.length, bc.numberBusinessDates(start, end)); + + // long period -- caching case + + start = LocalDate.of(2024, 5, 15); + end = LocalDate.of(2025, 5, 29); + + final ArrayList targetList = new ArrayList<>(); + LocalDate d = start; + + while (!d.isAfter(end)) { + if (!weekendDaysLocal.contains(d.getDayOfWeek())) { + targetList.add(d); + } + d = d.plusDays(1); + } + + target = targetList.toArray(target); + + assertEquals(target, bc.businessDates(start, end)); + assertEquals(target.length, bc.numberBusinessDates(start, end)); + } + public void testNonBusinessDates() { final LocalDate start = LocalDate.of(2023, 7, 3); From 3a1c4124a77b2a2f62347615bea783253c8ed27a Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Fri, 24 May 2024 11:04:22 -0600 Subject: [PATCH 37/54] Addressing code review. --- .../time/calendar/TestBusinessCalendar.java | 95 ++++++++++++++++++- 1 file changed, 90 insertions(+), 5 deletions(-) diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestBusinessCalendar.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestBusinessCalendar.java index 7059c81d607..5e5818f1370 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestBusinessCalendar.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestBusinessCalendar.java @@ -818,8 +818,23 @@ public void testBusinessDatesValidateCacheIteration() { LocalDate.of(2024, 5, 29), }; - assertEquals(target, bc.businessDates(start, end)); - assertEquals(target.length, bc.numberBusinessDates(start, end)); + assertEquals(target, bc.businessDates(start, end, true, true)); + assertEquals(target.length, bc.numberBusinessDates(start, end, true, true)); + + target = new LocalDate[] { + LocalDate.of(2024, 5, 16), + LocalDate.of(2024, 5, 17), + LocalDate.of(2024, 5, 20), + LocalDate.of(2024, 5, 21), + LocalDate.of(2024, 5, 22), + LocalDate.of(2024, 5, 23), + LocalDate.of(2024, 5, 24), + LocalDate.of(2024, 5, 27), + LocalDate.of(2024, 5, 28), + }; + + assertEquals(target, bc.businessDates(start, end, false, false)); + assertEquals(target.length, bc.numberBusinessDates(start, end, false, false)); // long period -- caching case @@ -836,10 +851,80 @@ public void testBusinessDatesValidateCacheIteration() { d = d.plusDays(1); } - target = targetList.toArray(target); + target = targetList.toArray(new LocalDate[0]); + + assertEquals(target, bc.businessDates(start, end, true, true)); + assertEquals(target.length, bc.numberBusinessDates(start, end, true, true)); + + targetList.remove(0); + targetList.remove(targetList.size() - 1); + target = targetList.toArray(new LocalDate[0]); + + assertEquals(target, bc.businessDates(start, end, false, false)); + assertEquals(target.length, bc.numberBusinessDates(start, end, false, false)); + } + + public void testNonBusinessDatesValidateCacheIteration() { + // Construct a very simple calendar for counting business days that is easy to reason about + + final LocalDate firstValidDateLocal = LocalDate.of(2000, 1, 1); + final LocalDate lastValidDateLocal = LocalDate.of(2030, 12, 31); + final Set weekendDaysLocal = Set.of(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY); + final Map> holidaysLocal = new HashMap<>(); + final BusinessCalendar bc = new BusinessCalendar("Test", "Test", timeZone, firstValidDateLocal, + lastValidDateLocal, schedule, weekendDaysLocal, holidaysLocal); + + // short period -- no caching case + + LocalDate start = LocalDate.of(2024, 5, 12); + LocalDate end = LocalDate.of(2024, 5, 26); + + LocalDate[] target = { + LocalDate.of(2024, 5, 12), + LocalDate.of(2024, 5, 18), + LocalDate.of(2024, 5, 19), + LocalDate.of(2024, 5, 25), + LocalDate.of(2024, 5, 26), + }; + + assertEquals(target, bc.nonBusinessDates(start, end, true, true)); + assertEquals(target.length, bc.numberNonBusinessDates(start, end, true, true)); + + target = new LocalDate[] { + LocalDate.of(2024, 5, 18), + LocalDate.of(2024, 5, 19), + LocalDate.of(2024, 5, 25), + }; + + assertEquals(target, bc.nonBusinessDates(start, end, false, false)); + assertEquals(target.length, bc.numberNonBusinessDates(start, end, false, false)); + + // long period -- caching case + + start = LocalDate.of(2024, 5, 19); + end = LocalDate.of(2025, 5, 17); + + final ArrayList targetList = new ArrayList<>(); + LocalDate d = start; + + while (!d.isAfter(end)) { + if (weekendDaysLocal.contains(d.getDayOfWeek())) { + targetList.add(d); + } + d = d.plusDays(1); + } + + target = targetList.toArray(new LocalDate[0]); + + assertEquals(target, bc.nonBusinessDates(start, end, true, true)); + assertEquals(target.length, bc.numberNonBusinessDates(start, end, true, true)); + + targetList.remove(0); + targetList.remove(targetList.size() - 1); + target = targetList.toArray(new LocalDate[0]); - assertEquals(target, bc.businessDates(start, end)); - assertEquals(target.length, bc.numberBusinessDates(start, end)); + assertEquals(target, bc.nonBusinessDates(start, end, false, false)); + assertEquals(target.length, bc.numberNonBusinessDates(start, end, false, false)); } public void testNonBusinessDates() { From 55d43a85d79c28252e77a894b68ca06137dbb0c4 Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Tue, 18 Jun 2024 14:23:43 -0600 Subject: [PATCH 38/54] Made cache clearing package private. --- .../main/java/io/deephaven/time/calendar/BusinessCalendar.java | 2 +- .../time/src/main/java/io/deephaven/time/calendar/Calendar.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java index 192c2e441f7..781c8fbdc72 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java @@ -100,7 +100,7 @@ public SummaryData( private final int yearCacheEnd; @Override - public synchronized void clearCache() { + synchronized void clearCache() { super.clearCache(); schedulesCache.clear(); summaryCache.clear(); diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java index ea06d203c78..a98f75747e5 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java @@ -113,7 +113,7 @@ private SummaryData computeYearSummary(final int year) { /** * Clears the cache. This should not generally be used and is provided for benchmarking. */ - public synchronized void clearCache() { + synchronized void clearCache() { summaryCache.clear(); } From 38f6c0fa3e81d31c630f621fc7b48f964750797e Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Tue, 18 Jun 2024 14:26:34 -0600 Subject: [PATCH 39/54] Reduced CalendarDay memory usage. --- .../src/main/java/io/deephaven/time/calendar/CalendarDay.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/CalendarDay.java b/engine/time/src/main/java/io/deephaven/time/calendar/CalendarDay.java index 64c24e435ce..2660a5d0eb1 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/CalendarDay.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/CalendarDay.java @@ -31,7 +31,6 @@ public class CalendarDay & Temporal> { public static final CalendarDay HOLIDAY = new CalendarDay<>(); private final TimeRange[] businessTimeRanges; - private final List> businessTimeRangesList; private volatile long businessNanos = -1; /** @@ -66,7 +65,6 @@ public class CalendarDay & Temporal> { } this.businessTimeRanges = ranges; - this.businessTimeRangesList = List.of(ranges); } /** @@ -83,7 +81,7 @@ public class CalendarDay & Temporal> { * @return business time ranges for the day */ public List> businessTimeRanges() { - return businessTimeRangesList; + return List.of(businessTimeRanges); } /** From d1f88ecaabdc1c607ea2369114487e6311eb53e2 Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Tue, 18 Jun 2024 14:30:13 -0600 Subject: [PATCH 40/54] Move businessNanos calculation to the constructor for a calendar day. --- .../io/deephaven/time/calendar/CalendarDay.java | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/CalendarDay.java b/engine/time/src/main/java/io/deephaven/time/calendar/CalendarDay.java index 2660a5d0eb1..ac4f33854e9 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/CalendarDay.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/CalendarDay.java @@ -31,7 +31,7 @@ public class CalendarDay & Temporal> { public static final CalendarDay HOLIDAY = new CalendarDay<>(); private final TimeRange[] businessTimeRanges; - private volatile long businessNanos = -1; + private final long businessNanos; /** * Creates a CalendarDay instance. @@ -65,6 +65,7 @@ public class CalendarDay & Temporal> { } this.businessTimeRanges = ranges; + this.businessNanos = Arrays.stream(businessTimeRanges).map(TimeRange::nanos).reduce(0L, Long::sum); } /** @@ -118,15 +119,7 @@ public boolean isInclusiveEnd() { * @return length of the day in nanoseconds */ public long businessNanos() { - // Only read volatile 1x - long bn = businessNanos; - - if (bn < 0) { - bn = Arrays.stream(businessTimeRanges).map(TimeRange::nanos).reduce(0L, Long::sum); - businessNanos = bn; - } - - return bn; + return businessNanos; } /** From 99abd9bc7d17c69382ac65ebdf32915326fd337d Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Tue, 18 Jun 2024 14:33:11 -0600 Subject: [PATCH 41/54] Simplify cache key type definition. --- .../time/calendar/ImmutableConcurrentCache.java | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/ImmutableConcurrentCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/ImmutableConcurrentCache.java index 2d7f43531d4..eb3e7f8ede4 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/ImmutableConcurrentCache.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/ImmutableConcurrentCache.java @@ -70,22 +70,13 @@ public T getValue() { } } - private static class KeyDef extends KeyedIntObjectKey.Strict { + private static class KeyDef extends KeyedIntObjectKey.BasicStrict { @Override public int getIntKey(V v) { return v.getKey(); } - @Override - public int hashIntKey(int i) { - return i; - } - - @Override - public boolean equalIntKey(int i, V v) { - return i == v.getKey(); - } } private final IntFunction valueComputer; From ab00dfc1f1a1f9b800d31747e72eef9f7f197e1d Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Tue, 18 Jun 2024 14:35:03 -0600 Subject: [PATCH 42/54] Rename cache getter. --- .../time/calendar/BusinessCalendar.java | 2 +- .../calendar/ImmutableConcurrentCache.java | 2 +- .../time/calendar/YearMonthSummaryCache.java | 4 ++-- .../TestImmutableConcurrentCache.java | 20 +++++++++---------- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java index 781c8fbdc72..e8989668d73 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java @@ -359,7 +359,7 @@ public CalendarDay calendarDay(final LocalDate date) { } final int yearMonthDay = date.getYear() * 10000 + date.getMonthValue() * 100 + date.getDayOfMonth(); - return schedulesCache.get(yearMonthDay).getValue(); + return schedulesCache.computeIfAbsent(yearMonthDay).getValue(); } /** diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/ImmutableConcurrentCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/ImmutableConcurrentCache.java index eb3e7f8ede4..5366bb8a339 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/ImmutableConcurrentCache.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/ImmutableConcurrentCache.java @@ -107,7 +107,7 @@ synchronized void clear() { * @return the value * @throws IllegalArgumentException if the value is not found */ - public V get(int key) { + public V computeIfAbsent(int key) { V existing = cache.get(key); if (existing != null) { diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java index a467abbc2bd..57a3c5c1d44 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java @@ -43,7 +43,7 @@ synchronized void clear() { * @return the month summary */ T getMonthSummary(int yearMonth) { - return monthCache.get(yearMonth); + return monthCache.computeIfAbsent(yearMonth); } /** @@ -64,7 +64,7 @@ T getMonthSummary(int year, int month) { * @return the year summary */ T getYearSummary(int year) { - return yearCache.get(year); + return yearCache.computeIfAbsent(year); } private class YearMonthSummaryIterator implements Iterator { diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestImmutableConcurrentCache.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestImmutableConcurrentCache.java index f922f2b5e7a..2e15c62a8e9 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestImmutableConcurrentCache.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestImmutableConcurrentCache.java @@ -27,27 +27,27 @@ public void testCache() { final ImmutableConcurrentCache cache = new ImmutableConcurrentCache<>(10, TestImmutableConcurrentCache::makeVal); - assertEquals("A", cache.get(0).getValue()); - assertEquals("A", cache.get(0).getValue()); + assertEquals("A", cache.computeIfAbsent(0).getValue()); + assertEquals("A", cache.computeIfAbsent(0).getValue()); - assertEquals("A", cache.get(0).getValue()); - assertEquals("B", cache.get(1).getValue()); - assertEquals("C", cache.get(2).getValue()); + assertEquals("A", cache.computeIfAbsent(0).getValue()); + assertEquals("B", cache.computeIfAbsent(1).getValue()); + assertEquals("C", cache.computeIfAbsent(2).getValue()); try { - cache.get(3); + cache.computeIfAbsent(3); fail("Expected exception"); } catch (final IllegalArgumentException e) { // pass } cache.clear(); - assertEquals("A", cache.get(0).getValue()); - assertEquals("B", cache.get(1).getValue()); - assertEquals("C", cache.get(2).getValue()); + assertEquals("A", cache.computeIfAbsent(0).getValue()); + assertEquals("B", cache.computeIfAbsent(1).getValue()); + assertEquals("C", cache.computeIfAbsent(2).getValue()); try { - cache.get(3); + cache.computeIfAbsent(3); fail("Expected exception"); } catch (final IllegalArgumentException e) { // pass From 431a244e197caa11333e5526d202dae2ecc60a90 Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Tue, 18 Jun 2024 14:41:28 -0600 Subject: [PATCH 43/54] Remove poorly designed cache methods --- .../time/calendar/YearMonthSummaryCache.java | 15 +++------------ .../time/calendar/TestYearMonthSummaryCache.java | 14 ++++++-------- 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java index 57a3c5c1d44..a0dd5da707a 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java @@ -36,16 +36,6 @@ synchronized void clear() { yearCache.clear(); } - /** - * Gets the month summary for the specified year and month. - * - * @param yearMonth the year and month - * @return the month summary - */ - T getMonthSummary(int yearMonth) { - return monthCache.computeIfAbsent(yearMonth); - } - /** * Gets the month summary for the specified year and month. * @@ -54,7 +44,8 @@ T getMonthSummary(int yearMonth) { * @return the month summary */ T getMonthSummary(int year, int month) { - return getMonthSummary(year * 100 + month); + final int yearMonth = year * 100 + month; + return monthCache.computeIfAbsent(yearMonth); } /** @@ -138,7 +129,7 @@ public T next() { val = getYearSummary(currentYear); incrementCurrentByYear(); } else { - val = getMonthSummary(currentYear * 100 + currentMonth); + val = getMonthSummary(currentYear, currentMonth); incrementCurrentByMonth(); } diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java index b4eb7ffca84..876e0396ed6 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java @@ -43,20 +43,20 @@ public void testGetters() { monthCount[0] = 0; yearCount[0] = 0; - assertEquals("month202101", cache.getMonthSummary(202101).getValue()); + assertEquals("month202101", cache.getMonthSummary(2021, 1).getValue()); assertEquals(1, monthCount[0]); assertEquals(0, yearCount[0]); assertEquals("year2021", cache.getYearSummary(2021).getValue()); assertEquals(1, monthCount[0]); assertEquals(1, yearCount[0]); - assertEquals("month202101", cache.getMonthSummary(202101).getValue()); + assertEquals("month202101", cache.getMonthSummary(2021, 1).getValue()); assertEquals(1, monthCount[0]); assertEquals(1, yearCount[0]); assertEquals("year2021", cache.getYearSummary(2021).getValue()); assertEquals(1, monthCount[0]); assertEquals(1, yearCount[0]); - assertEquals("month202102", cache.getMonthSummary(202102).getValue()); + assertEquals("month202102", cache.getMonthSummary(2021, 2).getValue()); assertEquals(2, monthCount[0]); assertEquals(1, yearCount[0]); assertEquals("year2022", cache.getYearSummary(2022).getValue()); @@ -65,27 +65,25 @@ public void testGetters() { cache.clear(); - assertEquals("month202101", cache.getMonthSummary(202101).getValue()); + assertEquals("month202101", cache.getMonthSummary(2021, 1).getValue()); assertEquals(3, monthCount[0]); assertEquals(2, yearCount[0]); assertEquals("year2021", cache.getYearSummary(2021).getValue()); assertEquals(3, monthCount[0]); assertEquals(3, yearCount[0]); - assertEquals("month202101", cache.getMonthSummary(202101).getValue()); + assertEquals("month202101", cache.getMonthSummary(2021, 1).getValue()); assertEquals(3, monthCount[0]); assertEquals(3, yearCount[0]); assertEquals("year2021", cache.getYearSummary(2021).getValue()); assertEquals(3, monthCount[0]); assertEquals(3, yearCount[0]); - assertEquals("month202102", cache.getMonthSummary(202102).getValue()); + assertEquals("month202102", cache.getMonthSummary(2021, 2).getValue()); assertEquals(4, monthCount[0]); assertEquals(3, yearCount[0]); assertEquals("year2022", cache.getYearSummary(2022).getValue()); assertEquals(4, monthCount[0]); assertEquals(4, yearCount[0]); - - assertEquals(cache.getMonthSummary(202101).getValue(), cache.getMonthSummary(2021, 1).getValue()); } private static Stream iteratorToStream(Iterator iterator) { From 46ab5bff92270fb99e7b23965297ebd728ee3d95 Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Tue, 18 Jun 2024 14:44:21 -0600 Subject: [PATCH 44/54] Consolidate year-monty key calculation. --- .../time/calendar/YearMonthSummaryCache.java | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java index a0dd5da707a..4ebfa8783e0 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java @@ -14,6 +14,17 @@ */ class YearMonthSummaryCache { + /** + * Creates a key for a year and month. + * + * @param year the year + * @param month the month + * @return the key + */ + private static int yearMonthKey(int year, int month) { + return year * 100 + month; + } + private final ImmutableConcurrentCache monthCache; private final ImmutableConcurrentCache yearCache; @@ -44,8 +55,7 @@ synchronized void clear() { * @return the month summary */ T getMonthSummary(int year, int month) { - final int yearMonth = year * 100 + month; - return monthCache.computeIfAbsent(yearMonth); + return monthCache.computeIfAbsent(yearMonthKey(year, month)); } /** @@ -80,7 +90,7 @@ private class YearMonthSummaryIterator implements Iterator { incrementCurrentByMonth(); } - currentYearMonth = currentYear * 100 + currentMonth; + currentYearMonth = yearMonthKey(currentYear, currentMonth); final LocalDate endPlus1 = end.plusDays(1); final int endPlus1Month = endPlus1.getMonthValue(); @@ -97,7 +107,7 @@ private class YearMonthSummaryIterator implements Iterator { } } - finalYearMonth = finalYear * 100 + finalMonth; + finalYearMonth = yearMonthKey(finalYear, finalMonth); } private void incrementCurrentByMonth() { @@ -108,12 +118,12 @@ private void incrementCurrentByMonth() { currentMonth = currentMonth + 1; } - currentYearMonth = currentYear * 100 + currentMonth; + currentYearMonth = yearMonthKey(currentYear, currentMonth); } private void incrementCurrentByYear() { currentYear++; - currentYearMonth = currentYear * 100 + currentMonth; + currentYearMonth = yearMonthKey(currentYear, currentMonth); } @Override From 5888eee7edd43a43042fec4aa653c1d320e01205 Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Tue, 18 Jun 2024 15:25:58 -0600 Subject: [PATCH 45/54] Consolidate cache key calculations. --- .../time/calendar/BusinessCalendar.java | 41 +++++++++++++------ .../time/calendar/YearMonthSummaryCache.java | 34 +++++++++++---- .../time/calendar/TestBusinessCalendar.java | 11 +++++ .../calendar/TestYearMonthSummaryCache.java | 10 ++++- 4 files changed, 75 insertions(+), 21 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java index e8989668d73..03c25c973f8 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java @@ -148,12 +148,12 @@ private SummaryData summarize(final int key, final LocalDate startDate, final Lo nonBusinessDates); } - private SummaryData computeMonthSummary(final int yearMonth) { - final int year = yearMonth / 100; - final int month = yearMonth % 100; + private SummaryData computeMonthSummary(final int key) { + final int year = YearMonthSummaryCache.yearFromMonthKey(key); + final int month = YearMonthSummaryCache.monthFromMonthKey(key); final LocalDate startDate = LocalDate.of(year, month, 1); final LocalDate endDate = startDate.plusMonths(1); // exclusive - return summarize(yearMonth, startDate, endDate); + return summarize(key, startDate, endDate); } private SummaryData computeYearSummary(final int year) { @@ -208,13 +208,28 @@ private SummaryData getYearSummary(final int year) { return summaryCache.getYearSummary(year); } + /** + * Creates a key for the schedules cache. + * + * @param date date + * @return key + */ + static int schedulesCacheKey(final LocalDate date) { + return date.getYear() * 10000 + date.getMonthValue() * 100 + date.getDayOfMonth(); + } + + /** + * Creates a date from a schedules cache key. + * + * @param key key + * @return date + */ + static LocalDate dateFromSchedulesCacheKey(final int key) { + return LocalDate.of(key / 10000, (key % 10000) / 100, key % 100); + } - private ImmutableConcurrentCache.Pair> computeCalendarDay(final int yearMonthDay) { - final int year = yearMonthDay / 10000; - final int monthDay = yearMonthDay % 10000; - final int month = monthDay / 100; - final int day = monthDay % 100; - final LocalDate date = LocalDate.of(year, month, day); + private ImmutableConcurrentCache.Pair> computeCalendarDay(final int key) { + final LocalDate date = dateFromSchedulesCacheKey(key); final CalendarDay h = holidays.get(date); final CalendarDay v; @@ -226,7 +241,7 @@ private ImmutableConcurrentCache.Pair> computeCalendarDay(f v = CalendarDay.toInstant(standardBusinessDay, date, timeZone()); } - return new ImmutableConcurrentCache.Pair<>(yearMonthDay, v); + return new ImmutableConcurrentCache.Pair<>(key, v); } // endregion @@ -358,8 +373,8 @@ public CalendarDay calendarDay(final LocalDate date) { + " lastValidDate=" + lastValidDate); } - final int yearMonthDay = date.getYear() * 10000 + date.getMonthValue() * 100 + date.getDayOfMonth(); - return schedulesCache.computeIfAbsent(yearMonthDay).getValue(); + final int key = schedulesCacheKey(date); + return schedulesCache.computeIfAbsent(key).getValue(); } /** diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java index 4ebfa8783e0..5a5886df6fd 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java @@ -15,16 +15,36 @@ class YearMonthSummaryCache { /** - * Creates a key for a year and month. + * Computes a key for a year and month. * * @param year the year * @param month the month * @return the key */ - private static int yearMonthKey(int year, int month) { + static int monthKey(int year, int month) { return year * 100 + month; } + /** + * Gets the year from a year-month key. + * + * @param key the year month key + * @return the year + */ + static int yearFromMonthKey(int key) { + return key / 100; + } + + /** + * Gets the month from a year-month key. + * + * @param key the year month key + * @return the month + */ + static int monthFromMonthKey(int key) { + return key % 100; + } + private final ImmutableConcurrentCache monthCache; private final ImmutableConcurrentCache yearCache; @@ -55,7 +75,7 @@ synchronized void clear() { * @return the month summary */ T getMonthSummary(int year, int month) { - return monthCache.computeIfAbsent(yearMonthKey(year, month)); + return monthCache.computeIfAbsent(monthKey(year, month)); } /** @@ -90,7 +110,7 @@ private class YearMonthSummaryIterator implements Iterator { incrementCurrentByMonth(); } - currentYearMonth = yearMonthKey(currentYear, currentMonth); + currentYearMonth = monthKey(currentYear, currentMonth); final LocalDate endPlus1 = end.plusDays(1); final int endPlus1Month = endPlus1.getMonthValue(); @@ -107,7 +127,7 @@ private class YearMonthSummaryIterator implements Iterator { } } - finalYearMonth = yearMonthKey(finalYear, finalMonth); + finalYearMonth = monthKey(finalYear, finalMonth); } private void incrementCurrentByMonth() { @@ -118,12 +138,12 @@ private void incrementCurrentByMonth() { currentMonth = currentMonth + 1; } - currentYearMonth = yearMonthKey(currentYear, currentMonth); + currentYearMonth = monthKey(currentYear, currentMonth); } private void incrementCurrentByYear() { currentYear++; - currentYearMonth = yearMonthKey(currentYear, currentMonth); + currentYearMonth = monthKey(currentYear, currentMonth); } @Override diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestBusinessCalendar.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestBusinessCalendar.java index 5e5818f1370..cc5f92e7aa5 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestBusinessCalendar.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestBusinessCalendar.java @@ -43,6 +43,17 @@ protected void setUp() throws Exception { calendar = bCalendar; } + public void testSchedulesCacheKeys() { + final int y = 2023; + final int m = 7; + final int d = 11; + final LocalDate ld = LocalDate.of(y, m, d); + + final int key = BusinessCalendar.schedulesCacheKey(ld); + assertEquals(key, y * 10000 + m * 100 + d); + assertEquals(ld, BusinessCalendar.dateFromSchedulesCacheKey(key)); + } + public void testBusinessGetters() { assertEquals(schedule, bCalendar.standardBusinessDay()); assertEquals(schedule.businessNanos(), bCalendar.standardBusinessNanos()); diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java index 876e0396ed6..c1b1fac4832 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java @@ -9,7 +9,6 @@ import java.util.Iterator; import java.util.Spliterator; import java.util.Spliterators; -import java.util.function.Function; import java.util.function.IntFunction; import java.util.stream.Stream; import java.util.stream.StreamSupport; @@ -23,6 +22,15 @@ private static class Value extends ImmutableConcurrentCache.Pair { } } + public void testKeys() { + final int y = 2021; + final int m = 3; + final int key = YearMonthSummaryCache.monthKey(y, m); + assertEquals(key, 2021 * 100 + m); + assertEquals(y, YearMonthSummaryCache.yearFromMonthKey(key)); + assertEquals(m, YearMonthSummaryCache.monthFromMonthKey(key)); + } + public void testGetters() { final int[] monthCount = new int[] {0}; final int[] yearCount = new int[] {0}; From 43e47249983b8ad86de63bf7e3da442b8683bf1a Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Mon, 24 Jun 2024 14:28:07 -0600 Subject: [PATCH 46/54] Address review comments. --- .../src/main/java/io/deephaven/time/calendar/Calendar.java | 4 ++-- .../src/main/java/io/deephaven/time/calendar/CalendarDay.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java index a98f75747e5..79cbec40702 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java @@ -60,8 +60,8 @@ private SummaryData summarize(final int key, final LocalDate startDate, final Lo } private SummaryData computeMonthSummary(final int yearMonth) { - final int year = yearMonth / 100; - final int month = yearMonth % 100; + final int year = YearMonthSummaryCache.yearFromMonthKey(yearMonth); + final int month = YearMonthSummaryCache.monthFromMonthKey(yearMonth); final LocalDate startDate = LocalDate.of(year, month, 1); final LocalDate endDate = startDate.plusMonths(1); // exclusive return summarize(yearMonth, startDate, endDate); diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/CalendarDay.java b/engine/time/src/main/java/io/deephaven/time/calendar/CalendarDay.java index ac4f33854e9..6d61ccc21f1 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/CalendarDay.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/CalendarDay.java @@ -65,7 +65,7 @@ public class CalendarDay & Temporal> { } this.businessTimeRanges = ranges; - this.businessNanos = Arrays.stream(businessTimeRanges).map(TimeRange::nanos).reduce(0L, Long::sum); + this.businessNanos = Arrays.stream(businessTimeRanges).mapToLong(TimeRange::nanos).sum(); } /** @@ -82,7 +82,7 @@ public class CalendarDay & Temporal> { * @return business time ranges for the day */ public List> businessTimeRanges() { - return List.of(businessTimeRanges); + return Arrays.asList(businessTimeRanges); } /** From 03ba3bd6163dc15f3b64ea059de91a6c1ed4c36f Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Tue, 20 Aug 2024 09:34:44 -0600 Subject: [PATCH 47/54] Improve method naming. --- .../time/calendar/BusinessCalendar.java | 14 +++++++------- .../io/deephaven/time/calendar/Calendar.java | 4 ++-- .../time/calendar/YearMonthSummaryCache.java | 18 +++++++++--------- .../time/calendar/TestBusinessCalendar.java | 4 ++-- .../calendar/TestYearMonthSummaryCache.java | 6 +++--- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java index a149c93a8ea..7054c9f05b1 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java @@ -149,8 +149,8 @@ private SummaryData summarize(final int key, final LocalDate startDate, final Lo } private SummaryData computeMonthSummary(final int key) { - final int year = YearMonthSummaryCache.yearFromMonthKey(key); - final int month = YearMonthSummaryCache.monthFromMonthKey(key); + final int year = YearMonthSummaryCache.yearFromYearMonthKey(key); + final int month = YearMonthSummaryCache.monthFromYearMonthKey(key); final LocalDate startDate = LocalDate.of(year, month, 1); final LocalDate endDate = startDate.plusMonths(1); // exclusive return summarize(key, startDate, endDate); @@ -209,12 +209,12 @@ private SummaryData getYearSummary(final int year) { } /** - * Creates a key for the schedules cache. + * Creates a key for the schedules cache from a date. * * @param date date * @return key */ - static int schedulesCacheKey(final LocalDate date) { + static int schedulesCacheKeyFromDate(final LocalDate date) { return date.getYear() * 10000 + date.getMonthValue() * 100 + date.getDayOfMonth(); } @@ -224,12 +224,12 @@ static int schedulesCacheKey(final LocalDate date) { * @param key key * @return date */ - static LocalDate dateFromSchedulesCacheKey(final int key) { + static LocalDate schedulesCacheDateFromKey(final int key) { return LocalDate.of(key / 10000, (key % 10000) / 100, key % 100); } private ImmutableConcurrentCache.Pair> computeCalendarDay(final int key) { - final LocalDate date = dateFromSchedulesCacheKey(key); + final LocalDate date = schedulesCacheDateFromKey(key); final CalendarDay h = holidays.get(date); final CalendarDay v; @@ -373,7 +373,7 @@ public CalendarDay calendarDay(final LocalDate date) { + " lastValidDate=" + lastValidDate); } - final int key = schedulesCacheKey(date); + final int key = schedulesCacheKeyFromDate(date); return schedulesCache.computeIfAbsent(key).getValue(); } diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java index ed810142947..dfcb59fcafb 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java @@ -60,8 +60,8 @@ private SummaryData summarize(final int key, final LocalDate startDate, final Lo } private SummaryData computeMonthSummary(final int yearMonth) { - final int year = YearMonthSummaryCache.yearFromMonthKey(yearMonth); - final int month = YearMonthSummaryCache.monthFromMonthKey(yearMonth); + final int year = YearMonthSummaryCache.yearFromYearMonthKey(yearMonth); + final int month = YearMonthSummaryCache.monthFromYearMonthKey(yearMonth); final LocalDate startDate = LocalDate.of(year, month, 1); final LocalDate endDate = startDate.plusMonths(1); // exclusive return summarize(yearMonth, startDate, endDate); diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java index 5a5886df6fd..b4fab5f7f60 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java @@ -15,13 +15,13 @@ class YearMonthSummaryCache { /** - * Computes a key for a year and month. + * Computes a year-month key for a year and month. * * @param year the year * @param month the month * @return the key */ - static int monthKey(int year, int month) { + static int yearMonthKey(int year, int month) { return year * 100 + month; } @@ -31,7 +31,7 @@ static int monthKey(int year, int month) { * @param key the year month key * @return the year */ - static int yearFromMonthKey(int key) { + static int yearFromYearMonthKey(int key) { return key / 100; } @@ -41,7 +41,7 @@ static int yearFromMonthKey(int key) { * @param key the year month key * @return the month */ - static int monthFromMonthKey(int key) { + static int monthFromYearMonthKey(int key) { return key % 100; } @@ -75,7 +75,7 @@ synchronized void clear() { * @return the month summary */ T getMonthSummary(int year, int month) { - return monthCache.computeIfAbsent(monthKey(year, month)); + return monthCache.computeIfAbsent(yearMonthKey(year, month)); } /** @@ -110,7 +110,7 @@ private class YearMonthSummaryIterator implements Iterator { incrementCurrentByMonth(); } - currentYearMonth = monthKey(currentYear, currentMonth); + currentYearMonth = yearMonthKey(currentYear, currentMonth); final LocalDate endPlus1 = end.plusDays(1); final int endPlus1Month = endPlus1.getMonthValue(); @@ -127,7 +127,7 @@ private class YearMonthSummaryIterator implements Iterator { } } - finalYearMonth = monthKey(finalYear, finalMonth); + finalYearMonth = yearMonthKey(finalYear, finalMonth); } private void incrementCurrentByMonth() { @@ -138,12 +138,12 @@ private void incrementCurrentByMonth() { currentMonth = currentMonth + 1; } - currentYearMonth = monthKey(currentYear, currentMonth); + currentYearMonth = yearMonthKey(currentYear, currentMonth); } private void incrementCurrentByYear() { currentYear++; - currentYearMonth = monthKey(currentYear, currentMonth); + currentYearMonth = yearMonthKey(currentYear, currentMonth); } @Override diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestBusinessCalendar.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestBusinessCalendar.java index a2927a36907..34e5873d446 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestBusinessCalendar.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestBusinessCalendar.java @@ -49,9 +49,9 @@ public void testSchedulesCacheKeys() { final int d = 11; final LocalDate ld = LocalDate.of(y, m, d); - final int key = BusinessCalendar.schedulesCacheKey(ld); + final int key = BusinessCalendar.schedulesCacheKeyFromDate(ld); assertEquals(key, y * 10000 + m * 100 + d); - assertEquals(ld, BusinessCalendar.dateFromSchedulesCacheKey(key)); + assertEquals(ld, BusinessCalendar.schedulesCacheDateFromKey(key)); } public void testBusinessGetters() { diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java index c1b1fac4832..9ac6b82a438 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java @@ -25,10 +25,10 @@ private static class Value extends ImmutableConcurrentCache.Pair { public void testKeys() { final int y = 2021; final int m = 3; - final int key = YearMonthSummaryCache.monthKey(y, m); + final int key = YearMonthSummaryCache.yearMonthKey(y, m); assertEquals(key, 2021 * 100 + m); - assertEquals(y, YearMonthSummaryCache.yearFromMonthKey(key)); - assertEquals(m, YearMonthSummaryCache.monthFromMonthKey(key)); + assertEquals(y, YearMonthSummaryCache.yearFromYearMonthKey(key)); + assertEquals(m, YearMonthSummaryCache.monthFromYearMonthKey(key)); } public void testGetters() { From d3416ba8297deaf914398f01bdba5ce0ed0c86d4 Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Thu, 3 Oct 2024 15:52:32 -0600 Subject: [PATCH 48/54] Addressing review --- .../io/deephaven/time/calendar/ImmutableConcurrentCache.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/ImmutableConcurrentCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/ImmutableConcurrentCache.java index 5366bb8a339..858324a8ea8 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/ImmutableConcurrentCache.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/ImmutableConcurrentCache.java @@ -105,7 +105,7 @@ synchronized void clear() { * * @param key the key * @return the value - * @throws IllegalArgumentException if the value is not found + * @throws NullPointerException if the value is not found */ public V computeIfAbsent(int key) { V existing = cache.get(key); @@ -117,7 +117,7 @@ public V computeIfAbsent(int key) { final V newValue = valueComputer.apply(key); if (newValue == null) { - throw new IllegalArgumentException("Computed a null value: key=" + key); + throw new NullPointerException("Computed a null value: key=" + key); } existing = cache.putIfAbsent(key, newValue); From d98931501654889086f2f4eae7b157c0d26dd197 Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Thu, 3 Oct 2024 15:58:19 -0600 Subject: [PATCH 49/54] Addressing review --- .../main/java/io/deephaven/time/calendar/BusinessCalendar.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java index 7054c9f05b1..03daa68ca99 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java @@ -112,7 +112,6 @@ private SummaryData summarize(final int key, final LocalDate startDate, final Lo LocalDate date = startDate; long businessTimeNanos = 0; - int days = 0; int businessDays = 0; int nonBusinessDays = 0; final ArrayList businessDates = new ArrayList<>(); @@ -121,7 +120,6 @@ private SummaryData summarize(final int key, final LocalDate startDate, final Lo while (date.isBefore(endDate)) { final CalendarDay bs = calendarDay(date); final boolean ibd = bs.isBusinessDay(); - days += 1; businessDays += ibd ? 1 : 0; nonBusinessDays += ibd ? 0 : 1; businessTimeNanos += bs.businessNanos(); From cde9ab0b8ec44bdfbd5a3a814e4ccf0ae8fe0e3a Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Thu, 3 Oct 2024 16:00:22 -0600 Subject: [PATCH 50/54] Addressing review --- .../io/deephaven/time/calendar/BusinessCalendar.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java index 03daa68ca99..d0b72002a55 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java @@ -119,17 +119,16 @@ private SummaryData summarize(final int key, final LocalDate startDate, final Lo while (date.isBefore(endDate)) { final CalendarDay bs = calendarDay(date); - final boolean ibd = bs.isBusinessDay(); - businessDays += ibd ? 1 : 0; - nonBusinessDays += ibd ? 0 : 1; - businessTimeNanos += bs.businessNanos(); - if (ibd) { + if (bs.isBusinessDay()) { + ++businessDays; businessDates.add(date); } else { + ++nonBusinessDays; nonBusinessDates.add(date); } + businessTimeNanos += bs.businessNanos(); date = date.plusDays(1); } From e057f003735fc567376bfad3f8a153aed42dd331 Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Thu, 3 Oct 2024 16:06:26 -0600 Subject: [PATCH 51/54] Addressing review --- .../io/deephaven/time/calendar/BusinessCalendar.java | 10 +++++----- .../main/java/io/deephaven/time/calendar/Calendar.java | 2 +- ...entCache.java => ReadOptimizedConcurrentCache.java} | 8 ++++---- .../deephaven/time/calendar/YearMonthSummaryCache.java | 10 +++++----- ...ache.java => TestReadOptimizedConcurrentCache.java} | 8 ++++---- .../time/calendar/TestYearMonthSummaryCache.java | 2 +- 6 files changed, 20 insertions(+), 20 deletions(-) rename engine/time/src/main/java/io/deephaven/time/calendar/{ImmutableConcurrentCache.java => ReadOptimizedConcurrentCache.java} (87%) rename engine/time/src/test/java/io/deephaven/time/calendar/{TestImmutableConcurrentCache.java => TestReadOptimizedConcurrentCache.java} (81%) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java index d0b72002a55..66a0c6813fe 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java @@ -57,7 +57,7 @@ public InvalidDateException(final String message, final Throwable cause) { // region Cache - private static class SummaryData extends ImmutableConcurrentCache.IntKeyedValue { + private static class SummaryData extends ReadOptimizedConcurrentCache.IntKeyedValue { private final Instant startInstant; private final LocalDate startDate; private final Instant endInstant; @@ -92,8 +92,8 @@ public SummaryData( } } - private final ImmutableConcurrentCache>> schedulesCache = - new ImmutableConcurrentCache<>(10000, this::computeCalendarDay); + private final ReadOptimizedConcurrentCache>> schedulesCache = + new ReadOptimizedConcurrentCache<>(10000, this::computeCalendarDay); private final YearMonthSummaryCache summaryCache = new YearMonthSummaryCache<>(this::computeMonthSummary, this::computeYearSummary); private final int yearCacheStart; @@ -225,7 +225,7 @@ static LocalDate schedulesCacheDateFromKey(final int key) { return LocalDate.of(key / 10000, (key % 10000) / 100, key % 100); } - private ImmutableConcurrentCache.Pair> computeCalendarDay(final int key) { + private ReadOptimizedConcurrentCache.Pair> computeCalendarDay(final int key) { final LocalDate date = schedulesCacheDateFromKey(key); final CalendarDay h = holidays.get(date); final CalendarDay v; @@ -238,7 +238,7 @@ private ImmutableConcurrentCache.Pair> computeCalendarDay(f v = CalendarDay.toInstant(standardBusinessDay, date, timeZone()); } - return new ImmutableConcurrentCache.Pair<>(key, v); + return new ReadOptimizedConcurrentCache.Pair<>(key, v); } // endregion diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java b/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java index dfcb59fcafb..9cb997112c6 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/Calendar.java @@ -30,7 +30,7 @@ public class Calendar { // region Cache - private static class SummaryData extends ImmutableConcurrentCache.IntKeyedValue { + private static class SummaryData extends ReadOptimizedConcurrentCache.IntKeyedValue { final LocalDate startDate; final LocalDate endDate; // exclusive final List dates; diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/ImmutableConcurrentCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/ReadOptimizedConcurrentCache.java similarity index 87% rename from engine/time/src/main/java/io/deephaven/time/calendar/ImmutableConcurrentCache.java rename to engine/time/src/main/java/io/deephaven/time/calendar/ReadOptimizedConcurrentCache.java index 858324a8ea8..c46d2471fc3 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/ImmutableConcurrentCache.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/ReadOptimizedConcurrentCache.java @@ -9,12 +9,12 @@ import java.util.function.IntFunction; /** - * An immutable cache that is designed to be fast when accessed concurrently with read-heavy workloads. Values are - * populated from a function when they are not found in the cache. All values must be non-null. + * A cache that is designed to be fast when accessed concurrently with read-heavy workloads. Values are populated from a + * function when they are not found in the cache. All values must be non-null. * * @param the value type */ -class ImmutableConcurrentCache { +class ReadOptimizedConcurrentCache { /** * A value that has an included integer key. @@ -88,7 +88,7 @@ public int getIntKey(V v) { * @param initialCapacity the initial capacity * @param valueComputer computes the value for a key. */ - public ImmutableConcurrentCache(final int initialCapacity, final IntFunction valueComputer) { + public ReadOptimizedConcurrentCache(final int initialCapacity, final IntFunction valueComputer) { this.valueComputer = valueComputer; this.cache = new KeyedIntObjectHash<>(initialCapacity, new KeyDef<>()); } diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java index b4fab5f7f60..cc183aad686 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/YearMonthSummaryCache.java @@ -12,7 +12,7 @@ * * @param the type of the summary */ -class YearMonthSummaryCache { +class YearMonthSummaryCache { /** * Computes a year-month key for a year and month. @@ -45,8 +45,8 @@ static int monthFromYearMonthKey(int key) { return key % 100; } - private final ImmutableConcurrentCache monthCache; - private final ImmutableConcurrentCache yearCache; + private final ReadOptimizedConcurrentCache monthCache; + private final ReadOptimizedConcurrentCache yearCache; /** * Creates a new cache. @@ -55,8 +55,8 @@ static int monthFromYearMonthKey(int key) { * @param computeYearSummary the function to compute a year summary */ YearMonthSummaryCache(IntFunction computeMonthSummary, IntFunction computeYearSummary) { - monthCache = new ImmutableConcurrentCache<>(12 * 50, computeMonthSummary); - yearCache = new ImmutableConcurrentCache<>(50, computeYearSummary); + monthCache = new ReadOptimizedConcurrentCache<>(12 * 50, computeMonthSummary); + yearCache = new ReadOptimizedConcurrentCache<>(50, computeYearSummary); } /** diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestImmutableConcurrentCache.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestReadOptimizedConcurrentCache.java similarity index 81% rename from engine/time/src/test/java/io/deephaven/time/calendar/TestImmutableConcurrentCache.java rename to engine/time/src/test/java/io/deephaven/time/calendar/TestReadOptimizedConcurrentCache.java index 2e15c62a8e9..920ac5e4339 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestImmutableConcurrentCache.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestReadOptimizedConcurrentCache.java @@ -5,9 +5,9 @@ import io.deephaven.base.testing.BaseArrayTestCase; -public class TestImmutableConcurrentCache extends BaseArrayTestCase { +public class TestReadOptimizedConcurrentCache extends BaseArrayTestCase { - private static class Value extends ImmutableConcurrentCache.Pair { + private static class Value extends ReadOptimizedConcurrentCache.Pair { Value(int key, String value) { super(key, value); } @@ -24,8 +24,8 @@ private static Value makeVal(Integer key) { } public void testCache() { - final ImmutableConcurrentCache cache = - new ImmutableConcurrentCache<>(10, TestImmutableConcurrentCache::makeVal); + final ReadOptimizedConcurrentCache cache = + new ReadOptimizedConcurrentCache<>(10, TestReadOptimizedConcurrentCache::makeVal); assertEquals("A", cache.computeIfAbsent(0).getValue()); assertEquals("A", cache.computeIfAbsent(0).getValue()); diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java index 9ac6b82a438..7d8568b1f56 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestYearMonthSummaryCache.java @@ -16,7 +16,7 @@ @SuppressWarnings({"DataFlowIssue", "ConstantValue"}) public class TestYearMonthSummaryCache extends BaseArrayTestCase { - private static class Value extends ImmutableConcurrentCache.Pair { + private static class Value extends ReadOptimizedConcurrentCache.Pair { Value(int key, String value) { super(key, value); } From 04d21ba3c93984030f82bd514f4eb55af0f3ef5b Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Thu, 3 Oct 2024 16:09:07 -0600 Subject: [PATCH 52/54] Addressing review --- .../deephaven/time/calendar/ReadOptimizedConcurrentCache.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/ReadOptimizedConcurrentCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/ReadOptimizedConcurrentCache.java index c46d2471fc3..5f7cb62f4ac 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/ReadOptimizedConcurrentCache.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/ReadOptimizedConcurrentCache.java @@ -120,7 +120,7 @@ public V computeIfAbsent(int key) { throw new NullPointerException("Computed a null value: key=" + key); } - existing = cache.putIfAbsent(key, newValue); + existing = cache.put(key, newValue); return existing == null ? newValue : existing; } } From 502bce0e753ebee64b65190d2578080f0b5a6be8 Mon Sep 17 00:00:00 2001 From: Chip Kent Date: Wed, 9 Oct 2024 14:59:37 -0600 Subject: [PATCH 53/54] Fix broken unit tests --- .../time/calendar/TestReadOptimizedConcurrentCache.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/time/src/test/java/io/deephaven/time/calendar/TestReadOptimizedConcurrentCache.java b/engine/time/src/test/java/io/deephaven/time/calendar/TestReadOptimizedConcurrentCache.java index 920ac5e4339..20bca069d3e 100644 --- a/engine/time/src/test/java/io/deephaven/time/calendar/TestReadOptimizedConcurrentCache.java +++ b/engine/time/src/test/java/io/deephaven/time/calendar/TestReadOptimizedConcurrentCache.java @@ -37,7 +37,7 @@ public void testCache() { try { cache.computeIfAbsent(3); fail("Expected exception"); - } catch (final IllegalArgumentException e) { + } catch (final NullPointerException e) { // pass } @@ -49,7 +49,7 @@ public void testCache() { try { cache.computeIfAbsent(3); fail("Expected exception"); - } catch (final IllegalArgumentException e) { + } catch (final NullPointerException e) { // pass } } From e77fa845d15b0ff8cc2433d56c025b65e4788f70 Mon Sep 17 00:00:00 2001 From: Chip Kent <5250374+chipkent@users.noreply.github.com> Date: Wed, 23 Oct 2024 13:22:56 -0600 Subject: [PATCH 54/54] Addressing review comments Minimizing the lifetime of recomputed values. --- .../deephaven/time/calendar/ReadOptimizedConcurrentCache.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/time/src/main/java/io/deephaven/time/calendar/ReadOptimizedConcurrentCache.java b/engine/time/src/main/java/io/deephaven/time/calendar/ReadOptimizedConcurrentCache.java index 5f7cb62f4ac..1c5de8bce04 100644 --- a/engine/time/src/main/java/io/deephaven/time/calendar/ReadOptimizedConcurrentCache.java +++ b/engine/time/src/main/java/io/deephaven/time/calendar/ReadOptimizedConcurrentCache.java @@ -120,7 +120,7 @@ public V computeIfAbsent(int key) { throw new NullPointerException("Computed a null value: key=" + key); } - existing = cache.put(key, newValue); - return existing == null ? newValue : existing; + cache.put(key, newValue); + return newValue; } }