From df6219757eaf210b368aa5e38c0b4638a295152d Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 29 Sep 2023 11:36:16 +0200 Subject: [PATCH 01/11] doc: Move JavaDoc to getters. --- .../transit/model/timetable/TripTimes.java | 106 +++++++++--------- 1 file changed, 50 insertions(+), 56 deletions(-) diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java index e280300787a..9b801b4dc09 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java @@ -31,84 +31,47 @@ public class TripTimes implements Serializable, Comparable { private static final Logger LOG = LoggerFactory.getLogger(TripTimes.class); private static final String[] EMPTY_STRING_ARRAY = new String[0]; - /** The trips whose arrivals and departures are represented by this TripTimes */ private final Trip trip; - /** - * Both trip_headsign and stop_headsign (per stop on a particular trip) are optional GTFS fields. - * If the headsigns array is null, we will report the trip_headsign (which may also be null) at - * every stop on the trip. If all the stop_headsigns are the same as the trip_headsign we may also - * set the headsigns array to null to save space. Field is private to force use of the getter - * method which does the necessary fallbacks. - */ private I18NString[] headsigns; /** - * Contains a list of via names for each stop. This field provides info about intermediate stops - * between current stop and final trip destination. This is 2D array since there can be more than - * one via name/stop per each record in stop sequence). This is mapped from NeTEx - * DestinationDisplay.vias. No GTFS mapping at the moment. Outer array may be null if there are no + * Implementation notes: This is 2D array since there can be more than + * one via name/stop per each record in stop sequence). Outer array may be null if there are no * vias in stop sequence. Inner array may be null if there are no vias for particular stop. This - * is done in order to save space. Field is private to force use of the getter method which does - * the necessary fallbacks. + * is done in order to save space. */ private final String[][] headsignVias; - /** - * The time in seconds after midnight at which the vehicle should arrive at each stop according to - * the original schedule. - */ private final int[] scheduledArrivalTimes; - /** - * The time in seconds after midnight at which the vehicle should leave each stop according to the - * original schedule. - */ private final int[] scheduledDepartureTimes; private final List dropOffBookingInfos; private final List pickupBookingInfos; - /** - * These are the GTFS stop sequence numbers, which show the order in which the vehicle visits the - * stops. Despite the face that the StopPattern or TripPattern enclosing this TripTimes provides - * an ordered list of Stops, the original stop sequence numbers may still be needed for matching - * with GTFS-RT update messages. Unfortunately, each individual trip can have totally different - * sequence numbers for the same stops, so we need to store them at the individual trip level. An - * effort is made to re-use the sequence number arrays when they are the same across different - * trips in the same pattern. - */ private final int[] originalGtfsStopSequence; - /** A Set of stop indexes that are marked as timepoints in the GTFS input. */ private final BitSet timepoints; /** - * This allows re-using the same scheduled arrival and departure time arrays for many different + * This allows re-using the same scheduled arrival and departure time arrays for many * TripTimes. It is also used in materializing frequency-based TripTimes. */ private int timeShift; - // not final because these are set later, after TripTimes construction. + + /** Implementation notes: not final because these are set later, after TripTimes construction. */ private int serviceCode = -1; - /** - * The time in seconds after midnight at which the vehicle arrives at each stop, accounting for - * any real-time updates. Non-final to allow updates. - */ + /** Implementation notes: Non-final to allow updates. */ private int[] arrivalTimes; - /** - * The time in seconds after midnight at which the vehicle leaves each stop, accounting for any - * real-time updates. Non-final to allow updates. - */ + /** Implementation notes: Non-final to allow updates. */ private int[] departureTimes; /** - * States of the stops in the trip. If the state is DEFAULT for a stop, {@link #realTimeState} - * should determine the realtime state of the stop. + * States of the stops in the trip. If the state is DEFAULT for a stop, + * {@link #realTimeState} should determine the realtime state of the stop. *

* This is only for API-purposes (does not affect routing). Non-final to allow updates. */ private StopRealTimeState[] stopRealTimeStates; /** - * This is only for API-purposes (does not affect routing). Non-final to allow updates. + * Implementation notes: Non-final to allow updates. */ private OccupancyStatus[] occupancyStatus; - /** - * The real-time state of this TripTimes. - */ private RealTimeState realTimeState = RealTimeState.SCHEDULED; private Accessibility wheelchairAccessibility; @@ -187,7 +150,8 @@ public TripTimes(final TripTimes object) { } /** - * Trips may also have null headsigns, in which case we should fall back on a Timetable or + * Both trip_headsign and stop_headsign (per stop on a particular trip) are optional GTFS fields. + * A trip may not have a headsign, in which case we should fall back on a Timetable or * Pattern-level headsign. Such a string will be available when we give TripPatterns or * StopPatterns unique human readable route variant names, but a TripTimes currently does not have * a pointer to its enclosing timetable or pattern. @@ -224,12 +188,18 @@ public I18NString getTripHeadsign() { return trip.getHeadsign(); } - /** @return the time in seconds after midnight that the vehicle arrives at the stop. */ + /** + * The time in seconds after midnight at which the vehicle should arrive at the given stop + * according to the original schedule. + */ public int getScheduledArrivalTime(final int stop) { return scheduledArrivalTimes[stop] + timeShift; } - /** @return the amount of time in seconds that the vehicle waits at the stop. */ + /** + * The time in seconds after midnight at which the vehicle should leave the given stop according + * to the original schedule. + */ public int getScheduledDepartureTime(final int stop) { return scheduledDepartureTimes[stop] + timeShift; } @@ -244,14 +214,20 @@ public int sortIndex() { return getDepartureTime(0); } - /** @return the time in seconds after midnight that the vehicle arrives at the stop. */ + /** + * The time in seconds after midnight at which the vehicle arrives at each stop, accounting for + * any real-time updates. + */ public int getArrivalTime(final int stop) { if (arrivalTimes == null) { return getScheduledArrivalTime(stop); } else return arrivalTimes[stop]; // updated times are not time shifted. } - /** @return the amount of time in seconds that the vehicle waits at the stop. */ + /** + * The time in seconds after midnight at which the vehicle leaves each stop, accounting for any + * real-time updates. + */ public int getDepartureTime(final int stop) { if (departureTimes == null) { return getScheduledDepartureTime(stop); @@ -288,6 +264,12 @@ public void setPredictionInaccurate(int stop) { stopRealTimeStates[stop] = StopRealTimeState.INACCURATE_PREDICTIONS; } + /** + * The real-time states for a given stops. If the state is DEFAULT for a stop, + * the {@link #getRealTimeState()} should determine the realtime state of the stop. + *

+ * This is only for API-purposes (does not affect routing). + */ public boolean isCancelledStop(int stop) { if (stopRealTimeStates == null) { return false; @@ -295,7 +277,6 @@ public boolean isCancelledStop(int stop) { return stopRealTimeStates[stop] == StopRealTimeState.CANCELLED; } - // TODO OTP2 - Unused, but will be used by Transmodel API public boolean isRecordedStop(int stop) { if (stopRealTimeStates == null) { return false; @@ -322,6 +303,9 @@ public void setOccupancyStatus(int stop, OccupancyStatus occupancyStatus) { this.occupancyStatus[stop] = occupancyStatus; } + /** + * This is only for API-purposes (does not affect routing). + */ public OccupancyStatus getOccupancyStatus(int stop) { if (this.occupancyStatus == null) { return OccupancyStatus.NO_DATA_AVAILABLE; @@ -554,7 +538,15 @@ public void timeShift(Duration duration) { } /** - * Returns the GTFS sequence number of the given 0-based stop index. + * Returns the GTFS sequence number of the given 0-based stop position. + * + * These are the GTFS stop sequence numbers, which show the order in which the vehicle visits the + * stops. Despite the face that the StopPattern or TripPattern enclosing this TripTimes provides + * an ordered list of Stops, the original stop sequence numbers may still be needed for matching + * with GTFS-RT update messages. Unfortunately, each individual trip can have totally different + * sequence numbers for the same stops, so we need to store them at the individual trip level. An + * effort is made to re-use the sequence number arrays when they are the same across different + * trips in the same pattern. */ public int gtfsSequenceOfStopIndex(final int stop) { return originalGtfsStopSequence[stop]; @@ -576,7 +568,9 @@ public OptionalInt stopIndexOfGtfsSequence(int stopSequence) { return OptionalInt.empty(); } - /** @return whether or not stopIndex is considered a timepoint in this TripTimes. */ + /** + * Whether or not stopIndex is considered a GTFS timepoint. + */ public boolean isTimepoint(final int stopIndex) { return timepoints.get(stopIndex); } From d046a8f505cb088f921caaf38c83408077533d14 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 29 Sep 2023 11:48:06 +0200 Subject: [PATCH 02/11] refactor: Remove duplicated code in stopRealTimeStates setter/getter. --- .../transit/model/timetable/TripTimes.java | 62 ++++++++----------- 1 file changed, 26 insertions(+), 36 deletions(-) diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java index 9b801b4dc09..3ff2e7ec699 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java @@ -59,12 +59,7 @@ public class TripTimes implements Serializable, Comparable { /** Implementation notes: Non-final to allow updates. */ private int[] departureTimes; - /** - * States of the stops in the trip. If the state is DEFAULT for a stop, - * {@link #realTimeState} should determine the realtime state of the stop. - *

- * This is only for API-purposes (does not affect routing). Non-final to allow updates. - */ + /** Implementation notes: Non-final to allow updates. */ private StopRealTimeState[] stopRealTimeStates; /** @@ -245,57 +240,35 @@ public int getDepartureDelay(final int stop) { } public void setRecorded(int stop) { - prepareForRealTimeUpdates(); - stopRealTimeStates[stop] = StopRealTimeState.RECORDED; + setStopRealTimeStates(stop, StopRealTimeState.RECORDED); } public void setCancelled(int stop) { - prepareForRealTimeUpdates(); - stopRealTimeStates[stop] = StopRealTimeState.CANCELLED; + setStopRealTimeStates(stop, StopRealTimeState.CANCELLED); } public void setNoData(int stop) { - prepareForRealTimeUpdates(); - stopRealTimeStates[stop] = StopRealTimeState.NO_DATA; + setStopRealTimeStates(stop, StopRealTimeState.NO_DATA); } public void setPredictionInaccurate(int stop) { - prepareForRealTimeUpdates(); - stopRealTimeStates[stop] = StopRealTimeState.INACCURATE_PREDICTIONS; + setStopRealTimeStates(stop, StopRealTimeState.INACCURATE_PREDICTIONS); } - /** - * The real-time states for a given stops. If the state is DEFAULT for a stop, - * the {@link #getRealTimeState()} should determine the realtime state of the stop. - *

- * This is only for API-purposes (does not affect routing). - */ public boolean isCancelledStop(int stop) { - if (stopRealTimeStates == null) { - return false; - } - return stopRealTimeStates[stop] == StopRealTimeState.CANCELLED; + return isStopRealTimeStates(stop, StopRealTimeState.CANCELLED); } public boolean isRecordedStop(int stop) { - if (stopRealTimeStates == null) { - return false; - } - return stopRealTimeStates[stop] == StopRealTimeState.RECORDED; + return isStopRealTimeStates(stop, StopRealTimeState.RECORDED); } public boolean isNoDataStop(int stop) { - if (stopRealTimeStates == null) { - return false; - } - return stopRealTimeStates[stop] == StopRealTimeState.NO_DATA; + return isStopRealTimeStates(stop, StopRealTimeState.NO_DATA); } public boolean isPredictionInaccurate(int stop) { - if (stopRealTimeStates == null) { - return false; - } - return stopRealTimeStates[stop] == StopRealTimeState.INACCURATE_PREDICTIONS; + return isStopRealTimeStates(stop, StopRealTimeState.INACCURATE_PREDICTIONS); } public void setOccupancyStatus(int stop, OccupancyStatus occupancyStatus) { @@ -651,6 +624,23 @@ public boolean adjustTimesBeforeWhenRequired(int firstUpdatedIndex, boolean setN return hasAdjustedTimes; } + /* private member methods */ + + private void setStopRealTimeStates(int stop, StopRealTimeState state) { + prepareForRealTimeUpdates(); + this.stopRealTimeStates[stop] = state; + } + + /** + * The real-time states for a given stops. If the state is DEFAULT for a stop, + * the {@link #getRealTimeState()} should determine the realtime state of the stop. + *

+ * This is only for API-purposes (does not affect routing). + */ + private boolean isStopRealTimeStates(int stop, StopRealTimeState state) { + return stopRealTimeStates != null && stopRealTimeStates[stop] == state; + } + /** * @return either an array of headsigns (one for each stop on this trip) or null if the headsign * is the same at all stops (including null) and can be found in the Trip object. From dc82ddff1f1988d90a864e3da22780bc45305f3a Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 29 Sep 2023 12:25:54 +0200 Subject: [PATCH 03/11] doc: Do not repeat scope in TripTimes JavaDoc. --- .../transit/model/timetable/TripTimes.java | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java index 3ff2e7ec699..1d6e7ad79fe 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java @@ -52,7 +52,7 @@ public class TripTimes implements Serializable, Comparable { */ private int timeShift; - /** Implementation notes: not final because these are set later, after TripTimes construction. */ + /** Implementation notes: not final because these are set after construction. */ private int serviceCode = -1; /** Implementation notes: Non-final to allow updates. */ private int[] arrivalTimes; @@ -295,39 +295,36 @@ public BookingInfo getPickupBookingInfo(int stop) { } /** - * @return true if this TripTimes represents an unmodified, scheduled trip from a published - * timetable or false if it is a updated, cancelled, or otherwise modified one. This method - * differs from {@link #getRealTimeState()} in that it checks whether real-time information is - * actually available in this TripTimes. + * Return {@code true} if the trip is unmodified, a scheduled trip from a published timetable. + * Return {@code false} if the trip is an updated, cancelled, or otherwise modified one. This + * method differs from {@link #getRealTimeState()} in that it checks whether real-time + * information is actually available. */ public boolean isScheduled() { return realTimeState == RealTimeState.SCHEDULED; } /** - * @return true if this TripTimes is canceled or soft-deleted + * Return {@code true} if canceled or soft-deleted */ public boolean isCanceledOrDeleted() { return isCanceled() || isDeleted(); } /** - * @return true if this TripTimes is canceled + * Return {@code true} if canceled */ public boolean isCanceled() { return realTimeState == RealTimeState.CANCELED; } /** - * @return true if this TripTimes is soft-deleted, and should not be visible to the user + * Return true if trip is soft-deleted, and should not be visible to the user */ public boolean isDeleted() { return realTimeState == RealTimeState.DELETED; } - /** - * @return the real-time state of this TripTimes - */ public RealTimeState getRealTimeState() { return realTimeState; } @@ -481,7 +478,7 @@ public int getNumStops() { return scheduledArrivalTimes.length; } - /** Sort TripTimes based on first departure time. */ + /** Sort trips based on first departure time. */ @Override public int compareTo(final TripTimes other) { return this.getDepartureTime(0) - other.getDepartureTime(0); @@ -557,7 +554,7 @@ public void setServiceCode(int serviceCode) { this.serviceCode = serviceCode; } - /** The trips whose arrivals and departures are represented by this TripTimes */ + /** The trips whose arrivals and departures are represented by this class */ public Trip getTrip() { return trip; } From c5b396571a5c6b4ddf4fa5f53abc8fd8135b75a8 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 29 Sep 2023 15:42:07 +0200 Subject: [PATCH 04/11] refactor: Extract ScheduledTripTimes from TripTimes --- .../framework/lang/IntUtils.java | 6 +- .../module/TimeZoneAdjusterModule.java | 11 +- .../org/opentripplanner/model/Timetable.java | 20 + .../model/timetable/ScheduledTripTimes.java | 444 ++++++++++++++++++ .../transit/model/timetable/TripTimes.java | 295 +++--------- 5 files changed, 543 insertions(+), 233 deletions(-) create mode 100644 src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java diff --git a/src/main/java/org/opentripplanner/framework/lang/IntUtils.java b/src/main/java/org/opentripplanner/framework/lang/IntUtils.java index 01040bf0f93..04eeff4695e 100644 --- a/src/main/java/org/opentripplanner/framework/lang/IntUtils.java +++ b/src/main/java/org/opentripplanner/framework/lang/IntUtils.java @@ -59,9 +59,9 @@ public static int[] intArray(int size, int initialValue) { * Copy int array and shift all values by the given {@code offset}. */ public static int[] shiftArray(int offset, int[] array) { - int[] a = new int[array.length]; - for (int i = 0; i < array.length; i++) { - a[i] = array[i] + offset; + int[] a = Arrays.copyOf(array, array.length); + for (int i = 0; i < array.length; ++i) { + a[i] += offset; } return a; } diff --git a/src/main/java/org/opentripplanner/graph_builder/module/TimeZoneAdjusterModule.java b/src/main/java/org/opentripplanner/graph_builder/module/TimeZoneAdjusterModule.java index 59766ac092b..eb3674bf76a 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/TimeZoneAdjusterModule.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/TimeZoneAdjusterModule.java @@ -7,7 +7,6 @@ import java.util.HashMap; import java.util.Map; import org.opentripplanner.graph_builder.model.GraphBuilderModule; -import org.opentripplanner.model.Timetable; import org.opentripplanner.transit.service.TransitModel; /** @@ -46,13 +45,9 @@ public void buildGraph() { return; } - final Timetable scheduledTimetable = pattern.getScheduledTimetable(); - - scheduledTimetable.getTripTimes().forEach(tripTimes -> tripTimes.timeShift(timeShift)); - - scheduledTimetable - .getFrequencyEntries() - .forEach(frequencyEntry -> frequencyEntry.tripTimes.timeShift(timeShift)); + pattern + .getScheduledTimetable() + .updateAllTripTimes(it -> it.adjustTimesToGraphTimeZone(timeShift)); }); } } diff --git a/src/main/java/org/opentripplanner/model/Timetable.java b/src/main/java/org/opentripplanner/model/Timetable.java index 2496fa11a41..6f8d495f3bf 100644 --- a/src/main/java/org/opentripplanner/model/Timetable.java +++ b/src/main/java/org/opentripplanner/model/Timetable.java @@ -21,6 +21,7 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.function.UnaryOperator; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.opentripplanner.framework.time.ServiceDateUtils; @@ -396,6 +397,25 @@ public void addTripTimes(TripTimes tt) { tripTimes.add(tt); } + /** + * Apply the same update to all trip-times inculuding scheduled and frequency based + * trip times. + *

+ * THIS IS NOT THREAD-SAFE - ONLY USE THIS METHOD DURING GRAPH-BUILD! + */ + public void updateAllTripTimes(UnaryOperator update) { + tripTimes.replaceAll(update); + frequencyEntries.replaceAll(it -> + new FrequencyEntry( + it.startTime, + it.endTime, + it.headway, + it.exactTimes, + update.apply(it.tripTimes) + ) + ); + } + /** * Add a frequency entry to this Timetable. See addTripTimes method. Maybe Frequency Entries * should just be TripTimes for simplicity. diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java new file mode 100644 index 00000000000..5eed8e1f1a8 --- /dev/null +++ b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java @@ -0,0 +1,444 @@ +package org.opentripplanner.transit.model.timetable; + +import static org.opentripplanner.transit.model.timetable.ValidationError.ErrorCode.NEGATIVE_DWELL_TIME; +import static org.opentripplanner.transit.model.timetable.ValidationError.ErrorCode.NEGATIVE_HOP_TIME; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.function.Supplier; +import javax.annotation.Nullable; +import org.opentripplanner.framework.i18n.I18NString; +import org.opentripplanner.framework.lang.IntUtils; +import org.opentripplanner.model.BookingInfo; +import org.opentripplanner.model.StopTime; +import org.opentripplanner.transit.model.basic.Accessibility; +import org.opentripplanner.transit.model.framework.Deduplicator; + +final class ScheduledTripTimes implements Serializable, Comparable { + + private static final String[] EMPTY_STRING_ARRAY = new String[0]; + private static final int NOT_SET = -1; + + private final int[] scheduledArrivalTimes; + private final int[] scheduledDepartureTimes; + /** + * This allows re-using the same scheduled arrival and departure time arrays for many + * ScheduledTripTimes. It is also used in materializing frequency-based ScheduledTripTimes. + */ + private final int timeShift; + + /** Implementation notes: not final because these are set after construction. */ + private int serviceCode = NOT_SET; + private final BitSet timepoints; + private final List dropOffBookingInfos; + private final List pickupBookingInfos; + private final Trip trip; + + @Nullable + private final I18NString[] headsigns; + + /** + * Implementation notes: This is 2D array since there can be more than + * one via name/stop per each record in stop sequence). Outer array may be null if there are no + * vias in stop sequence. Inner array may be null if there are no vias for particular stop. This + * is done in order to save space. + */ + @Nullable + private final String[][] headsignVias; + + private final int[] originalGtfsStopSequence; + + /** + * The provided stopTimes are assumed to be pre-filtered, valid, and monotonically increasing. The + * non-interpolated stoptimes should already be marked at timepoints by a previous filtering + * step. + */ + public ScheduledTripTimes( + final Trip trip, + final Collection stopTimes, + final Deduplicator deduplicator + ) { + this.trip = trip; + final int nStops = stopTimes.size(); + final int[] departures = new int[nStops]; + final int[] arrivals = new int[nStops]; + final int[] sequences = new int[nStops]; + final BitSet timepoints = new BitSet(nStops); + // Times are always shifted to zero. This is essential for frequencies and deduplication. + this.timeShift = stopTimes.iterator().next().getArrivalTime(); + final List dropOffBookingInfos = new ArrayList<>(); + final List pickupBookingInfos = new ArrayList<>(); + int s = 0; + for (final StopTime st : stopTimes) { + departures[s] = st.getDepartureTime() - timeShift; + arrivals[s] = st.getArrivalTime() - timeShift; + sequences[s] = st.getStopSequence(); + timepoints.set(s, st.getTimepoint() == 1); + + dropOffBookingInfos.add(st.getDropOffBookingInfo()); + pickupBookingInfos.add(st.getPickupBookingInfo()); + s++; + } + this.scheduledDepartureTimes = deduplicator.deduplicateIntArray(departures); + this.scheduledArrivalTimes = deduplicator.deduplicateIntArray(arrivals); + this.originalGtfsStopSequence = deduplicator.deduplicateIntArray(sequences); + this.headsigns = + deduplicator.deduplicateObjectArray(I18NString.class, makeHeadsignsArray(stopTimes)); + this.headsignVias = deduplicator.deduplicateString2DArray(makeHeadsignViasArray(stopTimes)); + + this.dropOffBookingInfos = + deduplicator.deduplicateImmutableList(BookingInfo.class, dropOffBookingInfos); + this.pickupBookingInfos = + deduplicator.deduplicateImmutableList(BookingInfo.class, pickupBookingInfos); + this.timepoints = deduplicator.deduplicateBitSet(timepoints); + } + + /** This copy constructor does not copy the actual times, only the scheduled times. */ + public ScheduledTripTimes(final ScheduledTripTimes object) { + this.timeShift = object.timeShift; + this.trip = object.trip; + this.serviceCode = object.serviceCode; + this.headsigns = object.headsigns; + this.headsignVias = object.headsignVias; + this.scheduledArrivalTimes = object.scheduledArrivalTimes; + this.scheduledDepartureTimes = object.scheduledDepartureTimes; + this.pickupBookingInfos = object.pickupBookingInfos; + this.dropOffBookingInfos = object.dropOffBookingInfos; + this.originalGtfsStopSequence = object.originalGtfsStopSequence; + this.timepoints = object.timepoints; + } + + /** + * This is a temporary constructor to allow timeShifting. + * TODO TT - It should be replaced by a builder. + */ + ScheduledTripTimes(final ScheduledTripTimes object, int timeShiftDelta) { + this.timeShift = object.timeShift + timeShiftDelta; + this.trip = object.trip; + this.serviceCode = object.serviceCode; + this.headsigns = object.headsigns; + this.headsignVias = object.headsignVias; + this.scheduledArrivalTimes = object.scheduledArrivalTimes; + this.scheduledDepartureTimes = object.scheduledDepartureTimes; + this.pickupBookingInfos = object.pickupBookingInfos; + this.dropOffBookingInfos = object.dropOffBookingInfos; + this.originalGtfsStopSequence = object.originalGtfsStopSequence; + this.timepoints = object.timepoints; + } + + /** + * Both trip_headsign and stop_headsign (per stop on a particular trip) are optional GTFS fields. + * A trip may not have a headsign, in which case we should fall back on a Timetable or + * Pattern-level headsign. Such a string will be available when we give TripPatterns or + * StopPatterns unique human readable route variant names, but a ScheduledTripTimes currently + * does not have a pointer to its enclosing timetable or pattern. + */ + @Nullable + public I18NString getHeadsign(final int stop) { + return (headsigns != null && headsigns[stop] != null) + ? headsigns[stop] + : getTrip().getHeadsign(); + } + + /** + * Return list of via names per particular stop. This field provides info about intermediate stops + * between current stop and final trip destination. Mapped from NeTEx DestinationDisplay.vias. No + * GTFS mapping at the moment. + * + * @return Empty list if there are no vias registered for a stop. + */ + public List getHeadsignVias(final int stop) { + if (headsignVias == null || headsignVias[stop] == null) { + return List.of(); + } + return List.of(headsignVias[stop]); + } + + /** + * @return the whole trip's headsign. Individual stops can have different headsigns. + */ + public I18NString getTripHeadsign() { + return trip.getHeadsign(); + } + + /** + * The time in seconds after midnight at which the vehicle should arrive at the given stop + * according to the original schedule. + */ + public int getScheduledArrivalTime(final int stop) { + return scheduledArrivalTimes[stop] + timeShift; + } + + /** + * The time in seconds after midnight at which the vehicle should leave the given stop according + * to the original schedule. + */ + public int getScheduledDepartureTime(final int stop) { + return scheduledDepartureTimes[stop] + timeShift; + } + + /** + * Return an integer which can be used to sort TripTimes in order of departure/arrivals. + *

+ * This sorted trip times is used to search for trips. OTP assume one trip do NOT pass another + * trip down the line. + */ + public int sortIndex() { + return getDepartureTime(0); + } + + /** + * The time in seconds after midnight at which the vehicle arrives at each stop, accounting for + * any real-time updates. + */ + public int getArrivalTime(final int stop) { + return getScheduledArrivalTime(stop); + } + + /** + * The time in seconds after midnight at which the vehicle leaves each stop, accounting for any + * real-time updates. + */ + public int getDepartureTime(final int stop) { + return getScheduledDepartureTime(stop); + } + + /** @return the difference between the scheduled and actual arrival times at this stop. */ + public int getArrivalDelay(final int stop) { + return getArrivalTime(stop) - (scheduledArrivalTimes[stop] + timeShift); + } + + /** @return the difference between the scheduled and actual departure times at this stop. */ + public int getDepartureDelay(final int stop) { + return getDepartureTime(stop) - (scheduledDepartureTimes[stop] + timeShift); + } + + /** + * This is only for API-purposes (does not affect routing). + */ + public OccupancyStatus getOccupancyStatus(int stop) { + return OccupancyStatus.NO_DATA_AVAILABLE; + } + + public BookingInfo getDropOffBookingInfo(int stop) { + return dropOffBookingInfos.get(stop); + } + + public BookingInfo getPickupBookingInfo(int stop) { + return pickupBookingInfos.get(stop); + } + + /** + * Return {@code true} if the trip is unmodified, a scheduled trip from a published timetable. + * Return {@code false} if the trip is an updated, cancelled, or otherwise modified one. This + * method differs from {@link #getRealTimeState()} in that it checks whether real-time + * information is actually available. + */ + public boolean isScheduled() { + return true; + } + + /** + * Return {@code true} if canceled or soft-deleted + */ + public boolean isCanceledOrDeleted() { + return false; + } + + /** + * Return {@code true} if canceled + */ + public boolean isCanceled() { + return false; + } + + /** + * Return true if trip is soft-deleted, and should not be visible to the user + */ + public boolean isDeleted() { + return false; + } + + public RealTimeState getRealTimeState() { + return RealTimeState.SCHEDULED; + } + + /** + * When creating ScheduledTripTimes or wrapping it in updates, we could potentially imply + * negative running or dwell times. We really don't want those being used in routing. This method + * checks that all times are increasing. + * + * @return empty if times were found to be increasing, stop index of the first error otherwise + */ + public Optional validateNonIncreasingTimes() { + final int nStops = scheduledArrivalTimes.length; + int prevDep = -9_999_999; + for (int s = 0; s < nStops; s++) { + final int arr = getArrivalTime(s); + final int dep = getDepartureTime(s); + + if (dep < arr) { + return Optional.of(new ValidationError(NEGATIVE_DWELL_TIME, s)); + } + if (prevDep > arr) { + return Optional.of(new ValidationError(NEGATIVE_HOP_TIME, s)); + } + prevDep = dep; + } + return Optional.empty(); + } + + public Accessibility getWheelchairAccessibility() { + return trip.getWheelchairBoarding(); + } + + public int getNumStops() { + return scheduledArrivalTimes.length; + } + + /** Sort trips based on first departure time. */ + @Override + public int compareTo(final ScheduledTripTimes other) { + return this.getDepartureTime(0) - other.getDepartureTime(0); + } + + /** + * Returns the GTFS sequence number of the given 0-based stop position. + * + * These are the GTFS stop sequence numbers, which show the order in which the vehicle visits the + * stops. Despite the face that the StopPattern or TripPattern enclosing this class provides + * an ordered list of Stops, the original stop sequence numbers may still be needed for matching + * with GTFS-RT update messages. Unfortunately, each individual trip can have totally different + * sequence numbers for the same stops, so we need to store them at the individual trip level. An + * effort is made to re-use the sequence number arrays when they are the same across different + * trips in the same pattern. + */ + public int gtfsSequenceOfStopIndex(final int stop) { + return originalGtfsStopSequence[stop]; + } + + /** + * Returns the 0-based stop index of the given GTFS sequence number. + */ + public OptionalInt stopIndexOfGtfsSequence(int stopSequence) { + if (originalGtfsStopSequence == null) { + return OptionalInt.empty(); + } + for (int i = 0; i < originalGtfsStopSequence.length; i++) { + var sequence = originalGtfsStopSequence[i]; + if (sequence == stopSequence) { + return OptionalInt.of(i); + } + } + return OptionalInt.empty(); + } + + /** + * Whether or not stopIndex is considered a GTFS timepoint. + */ + public boolean isTimepoint(final int stopIndex) { + return timepoints.get(stopIndex); + } + + /** The code for the service on which this trip runs. For departure search optimizations. */ + public int getServiceCode() { + return serviceCode; + } + + public void setServiceCode(int serviceCode) { + this.serviceCode = serviceCode; + } + + /** The trips whose arrivals and departures are represented by this class */ + public Trip getTrip() { + return trip; + } + + /* package local - only visible to timetable classes */ + + int[] copyArrivalTimes() { + return IntUtils.shiftArray(timeShift, scheduledArrivalTimes); + } + + int[] copyDepartureTimes() { + return IntUtils.shiftArray(timeShift, scheduledDepartureTimes); + } + + I18NString[] copyHeadsigns(Supplier defaultValue) { + return headsigns == null ? defaultValue.get() : Arrays.copyOf(headsigns, headsigns.length); + } + + /* private member methods */ + + /** + * @return either an array of headsigns (one for each stop on this trip) or null if the headsign + * is the same at all stops (including null) and can be found in the Trip object. + */ + private I18NString[] makeHeadsignsArray(final Collection stopTimes) { + final I18NString tripHeadsign = trip.getHeadsign(); + boolean useStopHeadsigns = false; + if (tripHeadsign == null) { + useStopHeadsigns = true; + } else { + for (final StopTime st : stopTimes) { + if (!(tripHeadsign.equals(st.getStopHeadsign()))) { + useStopHeadsigns = true; + break; + } + } + } + if (!useStopHeadsigns) { + return null; //defer to trip_headsign + } + boolean allNull = true; + int i = 0; + final I18NString[] hs = new I18NString[stopTimes.size()]; + for (final StopTime st : stopTimes) { + final I18NString headsign = st.getStopHeadsign(); + hs[i++] = headsign; + if (headsign != null) allNull = false; + } + if (allNull) { + return null; + } else { + return hs; + } + } + + /** + * Create 2D String array for via names for each stop in sequence. + * + * @return May be null if no vias are present in stop sequence. + */ + private String[][] makeHeadsignViasArray(final Collection stopTimes) { + if ( + stopTimes + .stream() + .allMatch(st -> st.getHeadsignVias() == null || st.getHeadsignVias().isEmpty()) + ) { + return null; + } + + String[][] vias = new String[stopTimes.size()][]; + + int i = 0; + for (final StopTime st : stopTimes) { + if (st.getHeadsignVias() == null) { + vias[i] = EMPTY_STRING_ARRAY; + i++; + continue; + } + + vias[i] = st.getHeadsignVias().toArray(EMPTY_STRING_ARRAY); + i++; + } + + return vias; + } +} diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java index 1d6e7ad79fe..4cf4cd1491e 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java @@ -3,23 +3,20 @@ import static org.opentripplanner.transit.model.timetable.ValidationError.ErrorCode.NEGATIVE_DWELL_TIME; import static org.opentripplanner.transit.model.timetable.ValidationError.ErrorCode.NEGATIVE_HOP_TIME; -import jakarta.annotation.Nullable; import java.io.Serializable; import java.time.Duration; -import java.util.ArrayList; import java.util.Arrays; -import java.util.BitSet; import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.OptionalInt; +import java.util.function.IntUnaryOperator; +import javax.annotation.Nullable; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.model.BookingInfo; import org.opentripplanner.model.StopTime; import org.opentripplanner.transit.model.basic.Accessibility; import org.opentripplanner.transit.model.framework.Deduplicator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * A TripTimes represents the arrival and departure times for a single trip in an Timetable. It is @@ -27,48 +24,16 @@ * when realtime updates have been applied. All times are expressed as seconds since midnight (as in * GTFS). */ -public class TripTimes implements Serializable, Comparable { +public final class TripTimes implements Serializable, Comparable { - private static final Logger LOG = LoggerFactory.getLogger(TripTimes.class); - private static final String[] EMPTY_STRING_ARRAY = new String[0]; - private final Trip trip; - private I18NString[] headsigns; - /** - * Implementation notes: This is 2D array since there can be more than - * one via name/stop per each record in stop sequence). Outer array may be null if there are no - * vias in stop sequence. Inner array may be null if there are no vias for particular stop. This - * is done in order to save space. - */ - private final String[][] headsignVias; - private final int[] scheduledArrivalTimes; - private final int[] scheduledDepartureTimes; - private final List dropOffBookingInfos; - private final List pickupBookingInfos; - private final int[] originalGtfsStopSequence; - private final BitSet timepoints; - /** - * This allows re-using the same scheduled arrival and departure time arrays for many - * TripTimes. It is also used in materializing frequency-based TripTimes. - */ - private int timeShift; + private final ScheduledTripTimes scheduledTripTimes; - /** Implementation notes: not final because these are set after construction. */ - private int serviceCode = -1; - /** Implementation notes: Non-final to allow updates. */ private int[] arrivalTimes; - /** Implementation notes: Non-final to allow updates. */ private int[] departureTimes; - - /** Implementation notes: Non-final to allow updates. */ + private RealTimeState realTimeState; private StopRealTimeState[] stopRealTimeStates; - - /** - * Implementation notes: Non-final to allow updates. - */ + private I18NString[] headsigns; private OccupancyStatus[] occupancyStatus; - - private RealTimeState realTimeState = RealTimeState.SCHEDULED; - private Accessibility wheelchairAccessibility; /** @@ -81,67 +46,41 @@ public TripTimes( final Collection stopTimes, final Deduplicator deduplicator ) { - this.trip = trip; - final int nStops = stopTimes.size(); - final int[] departures = new int[nStops]; - final int[] arrivals = new int[nStops]; - final int[] sequences = new int[nStops]; - final BitSet timepoints = new BitSet(nStops); - // Times are always shifted to zero. This is essential for frequencies and deduplication. - this.timeShift = stopTimes.iterator().next().getArrivalTime(); - final List dropOffBookingInfos = new ArrayList<>(); - final List pickupBookingInfos = new ArrayList<>(); - int s = 0; - for (final StopTime st : stopTimes) { - departures[s] = st.getDepartureTime() - timeShift; - arrivals[s] = st.getArrivalTime() - timeShift; - sequences[s] = st.getStopSequence(); - timepoints.set(s, st.getTimepoint() == 1); - - dropOffBookingInfos.add(st.getDropOffBookingInfo()); - pickupBookingInfos.add(st.getPickupBookingInfo()); - s++; - } - this.scheduledDepartureTimes = deduplicator.deduplicateIntArray(departures); - this.scheduledArrivalTimes = deduplicator.deduplicateIntArray(arrivals); - this.originalGtfsStopSequence = deduplicator.deduplicateIntArray(sequences); - this.headsigns = - deduplicator.deduplicateObjectArray(I18NString.class, makeHeadsignsArray(stopTimes)); - this.headsignVias = deduplicator.deduplicateString2DArray(makeHeadsignViasArray(stopTimes)); - - this.dropOffBookingInfos = - deduplicator.deduplicateImmutableList(BookingInfo.class, dropOffBookingInfos); - this.pickupBookingInfos = - deduplicator.deduplicateImmutableList(BookingInfo.class, pickupBookingInfos); + this.scheduledTripTimes = new ScheduledTripTimes(trip, stopTimes, deduplicator); // We set these to null to indicate that this is a non-updated/scheduled TripTimes. - // We cannot point to the scheduled times because they are shifted, and updated times are not. + // We cannot point to the scheduled times because we do not want to make an unnecessary copy. this.arrivalTimes = null; this.departureTimes = null; + this.realTimeState = scheduledTripTimes.getRealTimeState(); this.stopRealTimeStates = null; - this.timepoints = deduplicator.deduplicateBitSet(timepoints); - this.wheelchairAccessibility = trip.getWheelchairBoarding(); - LOG.trace("trip {} has timepoint at indexes {}", trip, timepoints); + this.headsigns = null; + this.occupancyStatus = null; + this.wheelchairAccessibility = scheduledTripTimes.getWheelchairAccessibility(); } - /** This copy constructor does not copy the actual times, only the scheduled times. */ + /** + * This copy constructor does not copy the actual times, only the scheduled times. + */ public TripTimes(final TripTimes object) { - this.timeShift = object.timeShift; - this.trip = object.trip; - this.serviceCode = object.serviceCode; - this.headsigns = object.headsigns; - this.headsignVias = object.headsignVias; - this.scheduledArrivalTimes = object.scheduledArrivalTimes; - this.scheduledDepartureTimes = object.scheduledDepartureTimes; + this.scheduledTripTimes = object.scheduledTripTimes; this.arrivalTimes = null; this.departureTimes = null; - this.stopRealTimeStates = object.stopRealTimeStates; - this.pickupBookingInfos = object.pickupBookingInfos; - this.dropOffBookingInfos = object.dropOffBookingInfos; - this.originalGtfsStopSequence = object.originalGtfsStopSequence; this.realTimeState = object.realTimeState; - this.timepoints = object.timepoints; + this.stopRealTimeStates = object.stopRealTimeStates; + this.headsigns = object.headsigns; + this.occupancyStatus = object.occupancyStatus; this.wheelchairAccessibility = object.wheelchairAccessibility; + } + + public TripTimes(final TripTimes object, int timeShiftDelta) { + this.scheduledTripTimes = new ScheduledTripTimes(object.scheduledTripTimes, timeShiftDelta); + this.arrivalTimes = null; + this.departureTimes = null; + this.realTimeState = object.realTimeState; + this.stopRealTimeStates = object.stopRealTimeStates; + this.headsigns = object.headsigns; this.occupancyStatus = object.occupancyStatus; + this.wheelchairAccessibility = object.wheelchairAccessibility; } /** @@ -153,13 +92,9 @@ public TripTimes(final TripTimes object) { */ @Nullable public I18NString getHeadsign(final int stop) { - I18NString tripHeadsign = getTrip().getHeadsign(); - if (headsigns == null) { - return tripHeadsign; - } else { - I18NString stopHeadsign = headsigns[stop]; - return stopHeadsign != null ? stopHeadsign : tripHeadsign; - } + return (headsigns != null && headsigns[stop] != null) + ? headsigns[stop] + : scheduledTripTimes.getHeadsign(stop); } /** @@ -170,17 +105,14 @@ public I18NString getHeadsign(final int stop) { * @return Empty list if there are no vias registered for a stop. */ public List getHeadsignVias(final int stop) { - if (headsignVias == null || headsignVias[stop] == null) { - return List.of(); - } - return List.of(headsignVias[stop]); + return scheduledTripTimes.getHeadsignVias(stop); } /** * @return the whole trip's headsign. Individual stops can have different headsigns. */ public I18NString getTripHeadsign() { - return trip.getHeadsign(); + return scheduledTripTimes.getTripHeadsign(); } /** @@ -188,7 +120,7 @@ public I18NString getTripHeadsign() { * according to the original schedule. */ public int getScheduledArrivalTime(final int stop) { - return scheduledArrivalTimes[stop] + timeShift; + return scheduledTripTimes.getScheduledArrivalTime(stop); } /** @@ -196,7 +128,7 @@ public int getScheduledArrivalTime(final int stop) { * to the original schedule. */ public int getScheduledDepartureTime(final int stop) { - return scheduledDepartureTimes[stop] + timeShift; + return scheduledTripTimes.getScheduledDepartureTime(stop); } /** @@ -214,9 +146,7 @@ public int sortIndex() { * any real-time updates. */ public int getArrivalTime(final int stop) { - if (arrivalTimes == null) { - return getScheduledArrivalTime(stop); - } else return arrivalTimes[stop]; // updated times are not time shifted. + return getOrElse(stop, arrivalTimes, scheduledTripTimes::getScheduledArrivalTime); } /** @@ -224,19 +154,17 @@ public int getArrivalTime(final int stop) { * real-time updates. */ public int getDepartureTime(final int stop) { - if (departureTimes == null) { - return getScheduledDepartureTime(stop); - } else return departureTimes[stop]; // updated times are not time shifted. + return getOrElse(stop, departureTimes, scheduledTripTimes::getScheduledDepartureTime); } /** @return the difference between the scheduled and actual arrival times at this stop. */ public int getArrivalDelay(final int stop) { - return getArrivalTime(stop) - (scheduledArrivalTimes[stop] + timeShift); + return getArrivalTime(stop) - scheduledTripTimes.getScheduledArrivalTime(stop); } /** @return the difference between the scheduled and actual departure times at this stop. */ public int getDepartureDelay(final int stop) { - return getDepartureTime(stop) - (scheduledDepartureTimes[stop] + timeShift); + return getDepartureTime(stop) - scheduledTripTimes.getScheduledDepartureTime(stop); } public void setRecorded(int stop) { @@ -287,11 +215,11 @@ public OccupancyStatus getOccupancyStatus(int stop) { } public BookingInfo getDropOffBookingInfo(int stop) { - return dropOffBookingInfos.get(stop); + return scheduledTripTimes.getDropOffBookingInfo(stop); } public BookingInfo getPickupBookingInfo(int stop) { - return pickupBookingInfos.get(stop); + return scheduledTripTimes.getPickupBookingInfo(stop); } /** @@ -342,7 +270,7 @@ public void setRealTimeState(final RealTimeState realTimeState) { * @return empty if times were found to be increasing, stop index of the first error otherwise */ public Optional validateNonIncreasingTimes() { - final int nStops = scheduledArrivalTimes.length; + final int nStops = arrivalTimes.length; int prevDep = -9_999_999; for (int s = 0; s < nStops; s++) { final int arr = getArrivalTime(s); @@ -453,7 +381,7 @@ public void updateDepartureTime(final int stop, final int time) { public void updateDepartureDelay(final int stop, final int delay) { prepareForRealTimeUpdates(); - departureTimes[stop] = scheduledDepartureTimes[stop] + timeShift + delay; + departureTimes[stop] = scheduledTripTimes.getScheduledDepartureTime(stop) + delay; } public void updateArrivalTime(final int stop, final int time) { @@ -463,10 +391,12 @@ public void updateArrivalTime(final int stop, final int time) { public void updateArrivalDelay(final int stop, final int delay) { prepareForRealTimeUpdates(); - arrivalTimes[stop] = scheduledArrivalTimes[stop] + timeShift + delay; + arrivalTimes[stop] = scheduledTripTimes.getScheduledArrivalTime(stop) + delay; } + @Nullable public Accessibility getWheelchairAccessibility() { + // No need to fall back to scheduled state, since it is copied over in the constructor return wheelchairAccessibility; } @@ -475,7 +405,7 @@ public void updateWheelchairAccessibility(Accessibility wheelchairAccessibility) } public int getNumStops() { - return scheduledArrivalTimes.length; + return scheduledTripTimes.getNumStops(); } /** Sort trips based on first departure time. */ @@ -494,17 +424,17 @@ public TripTimes timeShift(final int stop, final int time, final boolean depart) if (arrivalTimes != null || departureTimes != null) { return null; } - final TripTimes shifted = new TripTimes(this); // Adjust 0-based times to match desired stoptime. final int shift = time - (depart ? getDepartureTime(stop) : getArrivalTime(stop)); - // existing shift should usually (always?) be 0 on freqs - shifted.timeShift = shifted.timeShift + shift; - return shifted; + + return new TripTimes(this, shift); } - // Time-shift all times on this trip. This is used when updating the time zone for the trip. - public void timeShift(Duration duration) { - timeShift += duration.toSeconds(); + /** + * Time-shift all times on this trip. This is used when updating the time zone for the trip. + */ + public TripTimes adjustTimesToGraphTimeZone(Duration duration) { + return new TripTimes(this, (int) duration.toSeconds()); } /** @@ -519,44 +449,35 @@ public void timeShift(Duration duration) { * trips in the same pattern. */ public int gtfsSequenceOfStopIndex(final int stop) { - return originalGtfsStopSequence[stop]; + return scheduledTripTimes.gtfsSequenceOfStopIndex(stop); } /** * Returns the 0-based stop index of the given GTFS sequence number. */ public OptionalInt stopIndexOfGtfsSequence(int stopSequence) { - if (originalGtfsStopSequence == null) { - return OptionalInt.empty(); - } - for (int i = 0; i < originalGtfsStopSequence.length; i++) { - var sequence = originalGtfsStopSequence[i]; - if (sequence == stopSequence) { - return OptionalInt.of(i); - } - } - return OptionalInt.empty(); + return scheduledTripTimes.stopIndexOfGtfsSequence(stopSequence); } /** * Whether or not stopIndex is considered a GTFS timepoint. */ public boolean isTimepoint(final int stopIndex) { - return timepoints.get(stopIndex); + return scheduledTripTimes.isTimepoint(stopIndex); } /** The code for the service on which this trip runs. For departure search optimizations. */ public int getServiceCode() { - return serviceCode; + return scheduledTripTimes.getServiceCode(); } public void setServiceCode(int serviceCode) { - this.serviceCode = serviceCode; + scheduledTripTimes.setServiceCode(serviceCode); } /** The trips whose arrivals and departures are represented by this class */ public Trip getTrip() { - return trip; + return scheduledTripTimes.getTrip(); } /** @@ -638,48 +559,12 @@ private boolean isStopRealTimeStates(int stop, StopRealTimeState state) { return stopRealTimeStates != null && stopRealTimeStates[stop] == state; } - /** - * @return either an array of headsigns (one for each stop on this trip) or null if the headsign - * is the same at all stops (including null) and can be found in the Trip object. - */ - private I18NString[] makeHeadsignsArray(final Collection stopTimes) { - final I18NString tripHeadsign = trip.getHeadsign(); - boolean useStopHeadsigns = false; - if (tripHeadsign == null) { - useStopHeadsigns = true; - } else { - for (final StopTime st : stopTimes) { - if (!(tripHeadsign.equals(st.getStopHeadsign()))) { - useStopHeadsigns = true; - break; - } - } - } - if (!useStopHeadsigns) { - return null; //defer to trip_headsign - } - boolean allNull = true; - int i = 0; - final I18NString[] hs = new I18NString[stopTimes.size()]; - for (final StopTime st : stopTimes) { - final I18NString headsign = st.getStopHeadsign(); - hs[i++] = headsign; - if (headsign != null) allNull = false; - } - if (allNull) { - return null; - } else { - return hs; - } - } - public void setHeadsign(int index, I18NString headsign) { if (headsigns == null) { - if (headsign.equals(trip.getHeadsign())) { + if (headsign.equals(getTrip().getHeadsign())) { return; } - - this.headsigns = new I18NString[scheduledArrivalTimes.length]; + this.headsigns = scheduledTripTimes.copyHeadsigns(() -> new I18NString[getNumStops()]); this.headsigns[index] = headsign; return; } @@ -688,35 +573,8 @@ public void setHeadsign(int index, I18NString headsign) { headsigns[index] = headsign; } - /** - * Create 2D String array for via names for each stop in sequence. - * - * @return May be null if no vias are present in stop sequence. - */ - private String[][] makeHeadsignViasArray(final Collection stopTimes) { - if ( - stopTimes - .stream() - .allMatch(st -> st.getHeadsignVias() == null || st.getHeadsignVias().isEmpty()) - ) { - return null; - } - - String[][] vias = new String[stopTimes.size()][]; - - int i = 0; - for (final StopTime st : stopTimes) { - if (st.getHeadsignVias() == null) { - vias[i] = EMPTY_STRING_ARRAY; - i++; - continue; - } - - vias[i] = st.getHeadsignVias().toArray(EMPTY_STRING_ARRAY); - i++; - } - - return vias; + private static int getOrElse(int index, int[] array, IntUnaryOperator defaultValue) { + return array != null ? array[index] : defaultValue.applyAsInt(index); } /** @@ -727,23 +585,16 @@ private String[][] makeHeadsignViasArray(final Collection stopTimes) { */ private void prepareForRealTimeUpdates() { if (arrivalTimes == null) { - this.arrivalTimes = Arrays.copyOf(scheduledArrivalTimes, scheduledArrivalTimes.length); - this.departureTimes = Arrays.copyOf(scheduledDepartureTimes, scheduledDepartureTimes.length); + this.arrivalTimes = scheduledTripTimes.copyArrivalTimes(); + this.departureTimes = scheduledTripTimes.copyDepartureTimes(); + // Update the real-time state + this.realTimeState = RealTimeState.UPDATED; this.stopRealTimeStates = new StopRealTimeState[arrivalTimes.length]; + Arrays.fill(stopRealTimeStates, StopRealTimeState.DEFAULT); + this.headsigns = scheduledTripTimes.copyHeadsigns(() -> null); this.occupancyStatus = new OccupancyStatus[arrivalTimes.length]; - if (headsigns != null) { - headsigns = Arrays.copyOf(headsigns, headsigns.length); - } - - for (int i = 0; i < arrivalTimes.length; i++) { - arrivalTimes[i] += timeShift; - departureTimes[i] += timeShift; - stopRealTimeStates[i] = StopRealTimeState.DEFAULT; - occupancyStatus[i] = OccupancyStatus.NO_DATA_AVAILABLE; - } - - // Update the real-time state - realTimeState = RealTimeState.UPDATED; + Arrays.fill(occupancyStatus, OccupancyStatus.NO_DATA_AVAILABLE); + // skip immutable types: scheduledTripTimes & wheelchairAccessibility } } } From 3bbd17de092c73e842a2f1810425e7fcd833204e Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Sat, 30 Sep 2023 16:51:00 +0200 Subject: [PATCH 05/11] refactor: Extract mapping and builder code from ScheduledTripTimes --- .../model/timetable/ScheduledTripTimes.java | 169 ++++-------------- .../timetable/ScheduledTripTimesBuilder.java | 119 ++++++++++++ .../StopTimeToScheduledTripTimesMapper.java | 138 ++++++++++++++ .../transit/model/timetable/TripTimes.java | 2 +- 4 files changed, 294 insertions(+), 134 deletions(-) create mode 100644 src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesBuilder.java create mode 100644 src/main/java/org/opentripplanner/transit/model/timetable/StopTimeToScheduledTripTimesMapper.java diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java index 5eed8e1f1a8..0102ff6366f 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java @@ -4,10 +4,8 @@ import static org.opentripplanner.transit.model.timetable.ValidationError.ErrorCode.NEGATIVE_HOP_TIME; import java.io.Serializable; -import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; -import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.OptionalInt; @@ -16,13 +14,11 @@ import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.framework.lang.IntUtils; import org.opentripplanner.model.BookingInfo; -import org.opentripplanner.model.StopTime; import org.opentripplanner.transit.model.basic.Accessibility; import org.opentripplanner.transit.model.framework.Deduplicator; final class ScheduledTripTimes implements Serializable, Comparable { - private static final String[] EMPTY_STRING_ARRAY = new String[0]; private static final int NOT_SET = -1; private final int[] scheduledArrivalTimes; @@ -34,7 +30,7 @@ final class ScheduledTripTimes implements Serializable, Comparable dropOffBookingInfos; private final List pickupBookingInfos; @@ -54,66 +50,6 @@ final class ScheduledTripTimes implements Serializable, Comparable stopTimes, - final Deduplicator deduplicator - ) { - this.trip = trip; - final int nStops = stopTimes.size(); - final int[] departures = new int[nStops]; - final int[] arrivals = new int[nStops]; - final int[] sequences = new int[nStops]; - final BitSet timepoints = new BitSet(nStops); - // Times are always shifted to zero. This is essential for frequencies and deduplication. - this.timeShift = stopTimes.iterator().next().getArrivalTime(); - final List dropOffBookingInfos = new ArrayList<>(); - final List pickupBookingInfos = new ArrayList<>(); - int s = 0; - for (final StopTime st : stopTimes) { - departures[s] = st.getDepartureTime() - timeShift; - arrivals[s] = st.getArrivalTime() - timeShift; - sequences[s] = st.getStopSequence(); - timepoints.set(s, st.getTimepoint() == 1); - - dropOffBookingInfos.add(st.getDropOffBookingInfo()); - pickupBookingInfos.add(st.getPickupBookingInfo()); - s++; - } - this.scheduledDepartureTimes = deduplicator.deduplicateIntArray(departures); - this.scheduledArrivalTimes = deduplicator.deduplicateIntArray(arrivals); - this.originalGtfsStopSequence = deduplicator.deduplicateIntArray(sequences); - this.headsigns = - deduplicator.deduplicateObjectArray(I18NString.class, makeHeadsignsArray(stopTimes)); - this.headsignVias = deduplicator.deduplicateString2DArray(makeHeadsignViasArray(stopTimes)); - - this.dropOffBookingInfos = - deduplicator.deduplicateImmutableList(BookingInfo.class, dropOffBookingInfos); - this.pickupBookingInfos = - deduplicator.deduplicateImmutableList(BookingInfo.class, pickupBookingInfos); - this.timepoints = deduplicator.deduplicateBitSet(timepoints); - } - - /** This copy constructor does not copy the actual times, only the scheduled times. */ - public ScheduledTripTimes(final ScheduledTripTimes object) { - this.timeShift = object.timeShift; - this.trip = object.trip; - this.serviceCode = object.serviceCode; - this.headsigns = object.headsigns; - this.headsignVias = object.headsignVias; - this.scheduledArrivalTimes = object.scheduledArrivalTimes; - this.scheduledDepartureTimes = object.scheduledDepartureTimes; - this.pickupBookingInfos = object.pickupBookingInfos; - this.dropOffBookingInfos = object.dropOffBookingInfos; - this.originalGtfsStopSequence = object.originalGtfsStopSequence; - this.timepoints = object.timepoints; - } - /** * This is a temporary constructor to allow timeShifting. * TODO TT - It should be replaced by a builder. @@ -132,6 +68,41 @@ public ScheduledTripTimes(final ScheduledTripTimes object) { this.timepoints = object.timepoints; } + ScheduledTripTimes(ScheduledTripTimesBuilder builder) { + this.timeShift = builder.timeShift; + this.trip = builder.trip; + this.serviceCode = builder.serviceCode; + this.headsigns = builder.headsigns; + this.headsignVias = builder.headsignVias; + this.scheduledArrivalTimes = builder.arrivalTimes; + this.scheduledDepartureTimes = builder.departureTimes; + this.pickupBookingInfos = builder.pickupBookingInfos; + this.dropOffBookingInfos = builder.dropOffBookingInfos; + this.originalGtfsStopSequence = builder.originalGtfsStopSequence; + this.timepoints = builder.timepoints; + } + + public static ScheduledTripTimesBuilder of(Deduplicator deduplicator) { + return new ScheduledTripTimesBuilder(deduplicator); + } + + public ScheduledTripTimesBuilder copyOf(Deduplicator deduplicator) { + return new ScheduledTripTimesBuilder( + scheduledArrivalTimes, + scheduledDepartureTimes, + timeShift, + serviceCode, + timepoints, + dropOffBookingInfos, + pickupBookingInfos, + trip, + headsigns, + headsignVias, + originalGtfsStopSequence, + deduplicator + ); + } + /** * Both trip_headsign and stop_headsign (per stop on a particular trip) are optional GTFS fields. * A trip may not have a headsign, in which case we should fall back on a Timetable or @@ -373,72 +344,4 @@ int[] copyDepartureTimes() { I18NString[] copyHeadsigns(Supplier defaultValue) { return headsigns == null ? defaultValue.get() : Arrays.copyOf(headsigns, headsigns.length); } - - /* private member methods */ - - /** - * @return either an array of headsigns (one for each stop on this trip) or null if the headsign - * is the same at all stops (including null) and can be found in the Trip object. - */ - private I18NString[] makeHeadsignsArray(final Collection stopTimes) { - final I18NString tripHeadsign = trip.getHeadsign(); - boolean useStopHeadsigns = false; - if (tripHeadsign == null) { - useStopHeadsigns = true; - } else { - for (final StopTime st : stopTimes) { - if (!(tripHeadsign.equals(st.getStopHeadsign()))) { - useStopHeadsigns = true; - break; - } - } - } - if (!useStopHeadsigns) { - return null; //defer to trip_headsign - } - boolean allNull = true; - int i = 0; - final I18NString[] hs = new I18NString[stopTimes.size()]; - for (final StopTime st : stopTimes) { - final I18NString headsign = st.getStopHeadsign(); - hs[i++] = headsign; - if (headsign != null) allNull = false; - } - if (allNull) { - return null; - } else { - return hs; - } - } - - /** - * Create 2D String array for via names for each stop in sequence. - * - * @return May be null if no vias are present in stop sequence. - */ - private String[][] makeHeadsignViasArray(final Collection stopTimes) { - if ( - stopTimes - .stream() - .allMatch(st -> st.getHeadsignVias() == null || st.getHeadsignVias().isEmpty()) - ) { - return null; - } - - String[][] vias = new String[stopTimes.size()][]; - - int i = 0; - for (final StopTime st : stopTimes) { - if (st.getHeadsignVias() == null) { - vias[i] = EMPTY_STRING_ARRAY; - i++; - continue; - } - - vias[i] = st.getHeadsignVias().toArray(EMPTY_STRING_ARRAY); - i++; - } - - return vias; - } } diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesBuilder.java b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesBuilder.java new file mode 100644 index 00000000000..2b6727dacf6 --- /dev/null +++ b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesBuilder.java @@ -0,0 +1,119 @@ +package org.opentripplanner.transit.model.timetable; + +import java.util.BitSet; +import java.util.List; +import org.opentripplanner.framework.i18n.I18NString; +import org.opentripplanner.model.BookingInfo; +import org.opentripplanner.transit.model.framework.Deduplicator; + +public class ScheduledTripTimesBuilder { + + private final int NOT_SET = -1; + + int[] arrivalTimes; + int[] departureTimes; + int timeShift; + int serviceCode = NOT_SET; + BitSet timepoints; + List dropOffBookingInfos; + List pickupBookingInfos; + Trip trip; + I18NString[] headsigns; + String[][] headsignVias; + int[] originalGtfsStopSequence; + + private final Deduplicator deduplicator; + + ScheduledTripTimesBuilder(Deduplicator deduplicator) { + this.deduplicator = deduplicator; + } + + ScheduledTripTimesBuilder( + int[] arrivalTimes, + int[] departureTimes, + int timeShift, + int serviceCode, + BitSet timepoints, + List dropOffBookingInfos, + List pickupBookingInfos, + Trip trip, + I18NString[] headsigns, + String[][] headsignVias, + int[] originalGtfsStopSequence, + Deduplicator deduplicator + ) { + this(deduplicator); + this.arrivalTimes = arrivalTimes; + this.departureTimes = departureTimes; + this.timeShift = timeShift; + this.serviceCode = serviceCode; + this.timepoints = timepoints; + this.dropOffBookingInfos = dropOffBookingInfos; + this.pickupBookingInfos = pickupBookingInfos; + this.trip = trip; + this.headsigns = headsigns; + this.headsignVias = headsignVias; + this.originalGtfsStopSequence = originalGtfsStopSequence; + } + + public ScheduledTripTimesBuilder withArrivalTimes(int[] arrivalTimes) { + this.arrivalTimes = deduplicator.deduplicateIntArray(arrivalTimes); + return this; + } + + public ScheduledTripTimesBuilder withDepartureTimes(int[] departureTimes) { + this.departureTimes = deduplicator.deduplicateIntArray(departureTimes); + return this; + } + + public ScheduledTripTimesBuilder withTimeShift(int timeShift) { + this.timeShift = timeShift; + return this; + } + + public ScheduledTripTimesBuilder withServiceCode(int serviceCode) { + this.serviceCode = serviceCode; + return this; + } + + public ScheduledTripTimesBuilder withTimepoints(BitSet timepoints) { + this.timepoints = deduplicator.deduplicateBitSet(timepoints); + return this; + } + + public ScheduledTripTimesBuilder withDropOffBookingInfos(List dropOffBookingInfos) { + this.dropOffBookingInfos = + deduplicator.deduplicateImmutableList(BookingInfo.class, dropOffBookingInfos); + return this; + } + + public ScheduledTripTimesBuilder withPickupBookingInfos(List pickupBookingInfos) { + this.pickupBookingInfos = + deduplicator.deduplicateImmutableList(BookingInfo.class, pickupBookingInfos); + return this; + } + + public ScheduledTripTimesBuilder withTrip(Trip trip) { + this.trip = trip; + return this; + } + + public ScheduledTripTimesBuilder withHeadsigns(I18NString[] headsigns) { + this.headsigns = deduplicator.deduplicateObjectArray(I18NString.class, headsigns); + return this; + } + + public ScheduledTripTimesBuilder withHeadsignVias(String[][] headsignVias) { + this.headsignVias = deduplicator.deduplicateString2DArray(headsignVias); + return this; + } + + public ScheduledTripTimesBuilder withOriginalGtfsStopSequence(int[] originalGtfsStopSequence) { + this.originalGtfsStopSequence = deduplicator.deduplicateIntArray(originalGtfsStopSequence); + return this; + } + + public ScheduledTripTimes build() { + return new ScheduledTripTimes(this); + } +} diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/StopTimeToScheduledTripTimesMapper.java b/src/main/java/org/opentripplanner/transit/model/timetable/StopTimeToScheduledTripTimesMapper.java new file mode 100644 index 00000000000..bf98c6e77b8 --- /dev/null +++ b/src/main/java/org/opentripplanner/transit/model/timetable/StopTimeToScheduledTripTimesMapper.java @@ -0,0 +1,138 @@ +package org.opentripplanner.transit.model.timetable; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collection; +import java.util.List; +import org.opentripplanner.framework.i18n.I18NString; +import org.opentripplanner.model.BookingInfo; +import org.opentripplanner.model.StopTime; +import org.opentripplanner.transit.model.framework.Deduplicator; + +class StopTimeToScheduledTripTimesMapper { + + private final Trip trip; + private final ScheduledTripTimesBuilder builder; + + private static final String[] EMPTY_STRING_ARRAY = new String[0]; + + private StopTimeToScheduledTripTimesMapper(Trip trip, Deduplicator deduplicator) { + this.trip = trip; + this.builder = ScheduledTripTimes.of(deduplicator).withTrip(trip); + } + + /** + * The provided stopTimes are assumed to be pre-filtered, valid, and monotonically increasing. + * The non-interpolated stoptimes should already be marked at timepoints by a previous filtering + * step. + */ + public static ScheduledTripTimes map( + Trip trip, + Collection stopTimes, + Deduplicator deduplicator + ) { + return new StopTimeToScheduledTripTimesMapper(trip, deduplicator).doMap(stopTimes); + } + + private ScheduledTripTimes doMap(Collection stopTimes) { + final int nStops = stopTimes.size(); + final int[] departures = new int[nStops]; + final int[] arrivals = new int[nStops]; + final int[] sequences = new int[nStops]; + final BitSet timepoints = new BitSet(nStops); + // Times are always shifted to zero. This is essential for frequencies and deduplication. + int timeShift = stopTimes.iterator().next().getArrivalTime(); + builder.withTimeShift(timeShift); + + final List dropOffBookingInfos = new ArrayList<>(); + final List pickupBookingInfos = new ArrayList<>(); + int s = 0; + for (final StopTime st : stopTimes) { + departures[s] = st.getDepartureTime() - timeShift; + arrivals[s] = st.getArrivalTime() - timeShift; + sequences[s] = st.getStopSequence(); + timepoints.set(s, st.getTimepoint() == 1); + + dropOffBookingInfos.add(st.getDropOffBookingInfo()); + pickupBookingInfos.add(st.getPickupBookingInfo()); + s++; + } + builder + .withDepartureTimes(departures) + .withArrivalTimes(arrivals) + .withOriginalGtfsStopSequence(sequences) + .withHeadsigns(makeHeadsignsArray(stopTimes)) + .withHeadsignVias(makeHeadsignViasArray(stopTimes)) + .withDropOffBookingInfos(dropOffBookingInfos) + .withPickupBookingInfos(pickupBookingInfos) + .withTimepoints(timepoints); + + return builder.build(); + } + + /** + * @return either an array of headsigns (one for each stop on this trip) or null if the headsign + * is the same at all stops (including null) and can be found in the Trip object. + */ + private I18NString[] makeHeadsignsArray(final Collection stopTimes) { + final I18NString tripHeadsign = trip.getHeadsign(); + boolean useStopHeadsigns = false; + if (tripHeadsign == null) { + useStopHeadsigns = true; + } else { + for (final StopTime st : stopTimes) { + if (!(tripHeadsign.equals(st.getStopHeadsign()))) { + useStopHeadsigns = true; + break; + } + } + } + if (!useStopHeadsigns) { + return null; //defer to trip_headsign + } + boolean allNull = true; + int i = 0; + final I18NString[] hs = new I18NString[stopTimes.size()]; + for (final StopTime st : stopTimes) { + final I18NString headsign = st.getStopHeadsign(); + hs[i++] = headsign; + if (headsign != null) allNull = false; + } + if (allNull) { + return null; + } else { + return hs; + } + } + + /** + * Create 2D String array for via names for each stop in sequence. + * + * @return May be null if no vias are present in stop sequence. + */ + private String[][] makeHeadsignViasArray(final Collection stopTimes) { + if ( + stopTimes + .stream() + .allMatch(st -> st.getHeadsignVias() == null || st.getHeadsignVias().isEmpty()) + ) { + return null; + } + + String[][] vias = new String[stopTimes.size()][]; + + int i = 0; + for (final StopTime st : stopTimes) { + if (st.getHeadsignVias() == null) { + vias[i] = EMPTY_STRING_ARRAY; + i++; + continue; + } + + vias[i] = st.getHeadsignVias().toArray(EMPTY_STRING_ARRAY); + i++; + } + + return vias; + } +} diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java index 4cf4cd1491e..7459b4345d0 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java @@ -46,7 +46,7 @@ public TripTimes( final Collection stopTimes, final Deduplicator deduplicator ) { - this.scheduledTripTimes = new ScheduledTripTimes(trip, stopTimes, deduplicator); + this.scheduledTripTimes = StopTimeToScheduledTripTimesMapper.map(trip, stopTimes, deduplicator); // We set these to null to indicate that this is a non-updated/scheduled TripTimes. // We cannot point to the scheduled times because we do not want to make an unnecessary copy. this.arrivalTimes = null; From 66a30669f7332c659d4a546d745407a7506a06bd Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Sat, 30 Sep 2023 18:21:16 +0200 Subject: [PATCH 06/11] refactor: Replace TripTimes copy constructors with factory methods This decouple TripTimes from StopTime and make the code more readable. For example `new TripTimes(tt)`` is replaced with `tt.copyOfScheduledTimes()``. --- .../ext/emissions/EmissionsTest.java | 4 +- .../RealtimeResolverTest.java | 2 +- .../ext/siri/ModifiedTripBuilderTest.java | 3 +- .../ext/siri/TimetableHelperTest.java | 3 +- .../ext/siri/AddedTripBuilder.java | 9 +- .../ext/siri/ModifiedTripBuilder.java | 2 +- .../ext/siri/SiriTimetableSnapshotSource.java | 2 +- .../gtfs/GenerateTripPatternsOperation.java | 3 +- .../org/opentripplanner/model/Timetable.java | 2 +- .../netex/mapping/TripPatternMapper.java | 7 +- .../transit/model/timetable/TripTimes.java | 86 ++++++++++--------- .../model/timetable/TripTimesFactory.java | 28 ++++++ .../updater/trip/TimetableSnapshotSource.java | 7 +- .../apis/gtfs/GraphQLIntegrationTest.java | 4 +- .../interlining/InterlineProcessorTest.java | 4 +- .../model/TripTimeOnDateTest.java | 4 +- ...pTransitServiceBuilderLimitPeriodTest.java | 4 +- .../model/plan/TestItineraryBuilder.java | 3 +- .../transit/TripPatternForDateTest.java | 3 +- .../mappers/TripPatternForDateMapperTest.java | 3 +- ...rRoutingRequestTransitDataCreatorTest.java | 3 +- ...eRequestTransitDataProviderFilterTest.java | 5 +- .../transit/request/TestRouteData.java | 3 +- .../trippattern/FrequencyEntryTest.java | 3 +- .../model/timetable/TripTimesTest.java | 48 +++++------ .../RealtimeVehicleMatcherTest.java | 8 +- 26 files changed, 155 insertions(+), 98 deletions(-) create mode 100644 src/main/java/org/opentripplanner/transit/model/timetable/TripTimesFactory.java diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java index 043a91038d0..82058567c38 100644 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java @@ -26,7 +26,7 @@ import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.Route; import org.opentripplanner.transit.model.timetable.Trip; -import org.opentripplanner.transit.model.timetable.TripTimes; +import org.opentripplanner.transit.model.timetable.TripTimesFactory; class EmissionsTest { @@ -130,7 +130,7 @@ private ScheduledTransitLeg createTransitLeg(Route route) { .withRoute(route) .build(); return new ScheduledTransitLegBuilder<>() - .withTripTimes(new TripTimes(trip, stopTimes, new Deduplicator())) + .withTripTimes(TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator())) .withTripPattern(pattern) .withBoardStopIndexInPattern(0) .withAlightStopIndexInPattern(2) diff --git a/src/ext-test/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolverTest.java b/src/ext-test/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolverTest.java index 38e60335fc8..43be9c6d735 100644 --- a/src/ext-test/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolverTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolverTest.java @@ -137,7 +137,7 @@ private static TripPattern delay(TripPattern pattern1, int seconds) { } private static TripTimes delay(TripTimes tt, int seconds) { - var delayed = new TripTimes(tt); + var delayed = tt.copyOfScheduledTimes(); IntStream .range(0, delayed.getNumStops()) .forEach(i -> { diff --git a/src/ext-test/java/org/opentripplanner/ext/siri/ModifiedTripBuilderTest.java b/src/ext-test/java/org/opentripplanner/ext/siri/ModifiedTripBuilderTest.java index a9e9ad73c53..2bba22b3283 100644 --- a/src/ext-test/java/org/opentripplanner/ext/siri/ModifiedTripBuilderTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/siri/ModifiedTripBuilderTest.java @@ -29,6 +29,7 @@ import org.opentripplanner.transit.model.timetable.RealTimeState; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripTimes; +import org.opentripplanner.transit.model.timetable.TripTimesFactory; import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.StopModel; import org.opentripplanner.transit.service.TransitModel; @@ -111,7 +112,7 @@ class ModifiedTripBuilderTest { STOP_TIME_C_1.setStopSequence(1); } - private static final TripTimes TRIP_TIMES = new TripTimes( + private static final TripTimes TRIP_TIMES = TripTimesFactory.tripTimes( TRIP, List.of(STOP_TIME_A_1, STOP_TIME_B_1, STOP_TIME_C_1), DEDUPLICATOR diff --git a/src/ext-test/java/org/opentripplanner/ext/siri/TimetableHelperTest.java b/src/ext-test/java/org/opentripplanner/ext/siri/TimetableHelperTest.java index 2c8e25e7308..97bd3be2398 100644 --- a/src/ext-test/java/org/opentripplanner/ext/siri/TimetableHelperTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/siri/TimetableHelperTest.java @@ -21,6 +21,7 @@ import org.opentripplanner.transit.model.timetable.OccupancyStatus; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripTimes; +import org.opentripplanner.transit.model.timetable.TripTimesFactory; import org.opentripplanner.transit.service.StopModel; import uk.org.siri.siri20.OccupancyEnumeration; @@ -74,7 +75,7 @@ public void setUp() { .build(); Trip trip = Trip.of(new FeedScopedId(FEED_ID, "TRIP_ID")).withRoute(route).build(); - tripTimes = new TripTimes(trip, List.of(stopTime), new Deduplicator()); + tripTimes = TripTimesFactory.tripTimes(trip, List.of(stopTime), new Deduplicator()); } @Test diff --git a/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java b/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java index 23d7cbb1705..2d7789422ea 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java +++ b/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java @@ -29,6 +29,7 @@ import org.opentripplanner.transit.model.timetable.RealTimeState; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripTimes; +import org.opentripplanner.transit.model.timetable.TripTimesFactory; import org.opentripplanner.transit.service.TransitModel; import org.opentripplanner.updater.spi.TripTimesValidationMapper; import org.opentripplanner.updater.spi.UpdateError; @@ -201,10 +202,14 @@ Result build() { .withStopPattern(stopPattern) .build(); - TripTimes tripTimes = new TripTimes(trip, aimedStopTimes, transitModel.getDeduplicator()); + TripTimes tripTimes = TripTimesFactory.tripTimes( + trip, + aimedStopTimes, + transitModel.getDeduplicator() + ); tripTimes.setServiceCode(transitModel.getServiceCodes().get(trip.getServiceId())); pattern.add(tripTimes); - TripTimes updatedTripTimes = new TripTimes(tripTimes); + TripTimes updatedTripTimes = tripTimes.copyOfScheduledTimes(); // Loop through calls again and apply updates for (int stopSequence = 0; stopSequence < calls.size(); stopSequence++) { diff --git a/src/ext/java/org/opentripplanner/ext/siri/ModifiedTripBuilder.java b/src/ext/java/org/opentripplanner/ext/siri/ModifiedTripBuilder.java index c7d6d9b3373..f56d3c6a75f 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/ModifiedTripBuilder.java +++ b/src/ext/java/org/opentripplanner/ext/siri/ModifiedTripBuilder.java @@ -94,7 +94,7 @@ public ModifiedTripBuilder( * in form the SIRI-ET update. */ public Result build() { - TripTimes newTimes = new TripTimes(existingTripTimes); + TripTimes newTimes = existingTripTimes.copyOfScheduledTimes(); StopPattern stopPattern = createStopPattern(pattern, calls, entityResolver); diff --git a/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java b/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java index 3e6b4a62fd4..0cedc491f79 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java +++ b/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java @@ -395,7 +395,7 @@ private boolean markScheduledTripAsDeleted(Trip trip, final LocalDate serviceDat if (tripTimes == null) { LOG.warn("Could not mark scheduled trip as deleted {}", trip.getId()); } else { - final TripTimes newTripTimes = new TripTimes(tripTimes); + final TripTimes newTripTimes = tripTimes.copyOfScheduledTimes(); newTripTimes.deleteTrip(); buffer.update(pattern, newTripTimes, serviceDate); success = true; diff --git a/src/main/java/org/opentripplanner/gtfs/GenerateTripPatternsOperation.java b/src/main/java/org/opentripplanner/gtfs/GenerateTripPatternsOperation.java index 979876cf523..3feb105b87c 100644 --- a/src/main/java/org/opentripplanner/gtfs/GenerateTripPatternsOperation.java +++ b/src/main/java/org/opentripplanner/gtfs/GenerateTripPatternsOperation.java @@ -25,6 +25,7 @@ import org.opentripplanner.transit.model.timetable.FrequencyEntry; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripTimes; +import org.opentripplanner.transit.model.timetable.TripTimesFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -132,7 +133,7 @@ private void buildTripPatternForTrip(Trip trip) { TripPattern tripPattern = findOrCreateTripPattern(stopPattern, trip, direction); // Create a TripTimes object for this list of stoptimes, which form one trip. - TripTimes tripTimes = new TripTimes(trip, stopTimes, deduplicator); + TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, deduplicator); // If this trip is referenced by one or more lines in frequencies.txt, wrap it in a FrequencyEntry. List frequencies = frequenciesForTrip.get(trip); diff --git a/src/main/java/org/opentripplanner/model/Timetable.java b/src/main/java/org/opentripplanner/model/Timetable.java index 6f8d495f3bf..170ed49092d 100644 --- a/src/main/java/org/opentripplanner/model/Timetable.java +++ b/src/main/java/org/opentripplanner/model/Timetable.java @@ -203,7 +203,7 @@ public Result createUpdatedTripTimesFromGTFSRT( LOG.trace("tripId {} found at index {} in timetable.", tripId, tripIndex); } - TripTimes newTimes = new TripTimes(getTripTimes(tripIndex)); + TripTimes newTimes = getTripTimes(tripIndex).copyOfScheduledTimes(); List skippedStopIndices = new ArrayList<>(); // The GTFS-RT reference specifies that StopTimeUpdates are sorted by stop_sequence. diff --git a/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java b/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java index 0133e0fdda2..675b9eab5c9 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java @@ -30,6 +30,7 @@ import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripOnServiceDate; import org.opentripplanner.transit.model.timetable.TripTimes; +import org.opentripplanner.transit.model.timetable.TripTimesFactory; import org.rutebanken.netex.model.DatedServiceJourney; import org.rutebanken.netex.model.DatedServiceJourneyRefStructure; import org.rutebanken.netex.model.DestinationDisplay; @@ -366,7 +367,11 @@ private void createTripTimes(List trips, TripPattern tripPattern) { trip.getId() ); } else { - TripTimes tripTimes = new TripTimes(trip, result.tripStopTimes.get(trip), deduplicator); + TripTimes tripTimes = TripTimesFactory.tripTimes( + trip, + result.tripStopTimes.get(trip), + deduplicator + ); tripPattern.add(tripTimes); } } diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java index 7459b4345d0..645470555d4 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java @@ -6,7 +6,6 @@ import java.io.Serializable; import java.time.Duration; import java.util.Arrays; -import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.OptionalInt; @@ -14,9 +13,7 @@ import javax.annotation.Nullable; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.model.BookingInfo; -import org.opentripplanner.model.StopTime; import org.opentripplanner.transit.model.basic.Accessibility; -import org.opentripplanner.transit.model.framework.Deduplicator; /** * A TripTimes represents the arrival and departure times for a single trip in an Timetable. It is @@ -36,51 +33,62 @@ public final class TripTimes implements Serializable, Comparable { private OccupancyStatus[] occupancyStatus; private Accessibility wheelchairAccessibility; - /** - * The provided stopTimes are assumed to be pre-filtered, valid, and monotonically increasing. The - * non-interpolated stoptimes should already be marked at timepoints by a previous filtering - * step. - */ - public TripTimes( - final Trip trip, - final Collection stopTimes, - final Deduplicator deduplicator + private TripTimes(final TripTimes original, int timeShiftDelta) { + this(original, new ScheduledTripTimes(original.scheduledTripTimes, timeShiftDelta)); + } + + TripTimes(ScheduledTripTimes scheduledTripTimes) { + this( + scheduledTripTimes, + scheduledTripTimes.getRealTimeState(), + null, + null, + null, + scheduledTripTimes.getWheelchairAccessibility() + ); + } + + private TripTimes(TripTimes original, ScheduledTripTimes scheduledTripTimes) { + this( + scheduledTripTimes, + original.realTimeState, + original.stopRealTimeStates, + original.headsigns, + original.occupancyStatus, + original.wheelchairAccessibility + ); + } + + private TripTimes( + ScheduledTripTimes scheduledTripTimes, + RealTimeState realTimeState, + StopRealTimeState[] stopRealTimeStates, + I18NString[] headsigns, + OccupancyStatus[] occupancyStatus, + Accessibility wheelchairAccessibility ) { - this.scheduledTripTimes = StopTimeToScheduledTripTimesMapper.map(trip, stopTimes, deduplicator); + this.scheduledTripTimes = scheduledTripTimes; + this.realTimeState = realTimeState; + this.stopRealTimeStates = stopRealTimeStates; + this.headsigns = headsigns; + this.occupancyStatus = occupancyStatus; + this.wheelchairAccessibility = wheelchairAccessibility; + // We set these to null to indicate that this is a non-updated/scheduled TripTimes. // We cannot point to the scheduled times because we do not want to make an unnecessary copy. this.arrivalTimes = null; this.departureTimes = null; - this.realTimeState = scheduledTripTimes.getRealTimeState(); - this.stopRealTimeStates = null; - this.headsigns = null; - this.occupancyStatus = null; - this.wheelchairAccessibility = scheduledTripTimes.getWheelchairAccessibility(); } - /** - * This copy constructor does not copy the actual times, only the scheduled times. - */ - public TripTimes(final TripTimes object) { - this.scheduledTripTimes = object.scheduledTripTimes; - this.arrivalTimes = null; - this.departureTimes = null; - this.realTimeState = object.realTimeState; - this.stopRealTimeStates = object.stopRealTimeStates; - this.headsigns = object.headsigns; - this.occupancyStatus = object.occupancyStatus; - this.wheelchairAccessibility = object.wheelchairAccessibility; + public static TripTimes of(ScheduledTripTimes scheduledTripTimes) { + return new TripTimes(scheduledTripTimes); } - public TripTimes(final TripTimes object, int timeShiftDelta) { - this.scheduledTripTimes = new ScheduledTripTimes(object.scheduledTripTimes, timeShiftDelta); - this.arrivalTimes = null; - this.departureTimes = null; - this.realTimeState = object.realTimeState; - this.stopRealTimeStates = object.stopRealTimeStates; - this.headsigns = object.headsigns; - this.occupancyStatus = object.occupancyStatus; - this.wheelchairAccessibility = object.wheelchairAccessibility; + /** + * Copy scheduled times, but not the actual times. + */ + public TripTimes copyOfScheduledTimes() { + return new TripTimes(this, scheduledTripTimes); } /** diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimesFactory.java b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimesFactory.java new file mode 100644 index 00000000000..1bb953b81c5 --- /dev/null +++ b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimesFactory.java @@ -0,0 +1,28 @@ +package org.opentripplanner.transit.model.timetable; + +import java.util.Collection; +import org.opentripplanner.model.StopTime; +import org.opentripplanner.transit.model.framework.Deduplicator; + +/** + * The responsibility of this class is to create TripTimes based on StopTimes. The + * TripTimes should not have a dependency to StopTimes, so this class act as a middleman. + * Eventually this class should not be needed - the intermediate step to map feeds into + * StopTimes and then map stop-times into TripTimes is unnecessary - we should map the + * feeds directly into {@link ScheduledTripTimes} using the builder instead. + */ +public class TripTimesFactory { + + /** + * The provided stopTimes are assumed to be pre-filtered, valid, and monotonically increasing. The + * non-interpolated stoptimes should already be marked at timepoints by a previous filtering + * step. + */ + public static TripTimes tripTimes( + Trip trip, + Collection stopTimes, + Deduplicator deduplicator + ) { + return new TripTimes(StopTimeToScheduledTripTimesMapper.map(trip, stopTimes, deduplicator)); + } +} diff --git a/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java b/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java index d9a3a683c1a..6cda95eb983 100644 --- a/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java +++ b/src/main/java/org/opentripplanner/updater/trip/TimetableSnapshotSource.java @@ -55,6 +55,7 @@ import org.opentripplanner.transit.model.timetable.RealTimeState; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripTimes; +import org.opentripplanner.transit.model.timetable.TripTimesFactory; import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.TransitEditorService; import org.opentripplanner.transit.service.TransitModel; @@ -885,7 +886,7 @@ private Result addTripToGraphAndBuffer( ); // Create new trip times - final TripTimes newTripTimes = new TripTimes(trip, stopTimes, deduplicator); + final TripTimes newTripTimes = TripTimesFactory.tripTimes(trip, stopTimes, deduplicator); // Update all times to mark trip times as realtime // TODO: should we incorporate the delay field if present? @@ -945,7 +946,7 @@ private boolean cancelScheduledTrip( if (tripIndex == -1) { debug(tripId, "Could not cancel scheduled trip because it's not in the timetable"); } else { - final TripTimes newTripTimes = new TripTimes(timetable.getTripTimes(tripIndex)); + final TripTimes newTripTimes = timetable.getTripTimes(tripIndex).copyOfScheduledTimes(); switch (cancelationType) { case CANCEL -> newTripTimes.cancelTrip(); case DELETE -> newTripTimes.deleteTrip(); @@ -985,7 +986,7 @@ private boolean cancelPreviouslyAddedTrip( if (tripIndex == -1) { debug(tripId, "Could not cancel previously added trip on {}", serviceDate); } else { - final TripTimes newTripTimes = new TripTimes(timetable.getTripTimes(tripIndex)); + final TripTimes newTripTimes = timetable.getTripTimes(tripIndex).copyOfScheduledTimes(); switch (cancelationType) { case CANCEL -> newTripTimes.cancelTrip(); case DELETE -> newTripTimes.deleteTrip(); diff --git a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java b/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java index 16b078b3e77..a293c940713 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java @@ -82,7 +82,7 @@ import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.site.StopLocation; -import org.opentripplanner.transit.model.timetable.TripTimes; +import org.opentripplanner.transit.model.timetable.TripTimesFactory; import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.TransitModel; @@ -138,7 +138,7 @@ static void setup() { final TripPattern pattern = TEST_MODEL.pattern(BUS).build(); var trip = TransitModelForTest.trip("123").withHeadsign(I18NString.of("Trip Headsign")).build(); var stopTimes = TEST_MODEL.stopTimesEvery5Minutes(3, trip, T11_00); - var tripTimes = new TripTimes(trip, stopTimes, DEDUPLICATOR); + var tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, DEDUPLICATOR); pattern.add(tripTimes); transitModel.addTripPattern(id("pattern-1"), pattern); diff --git a/src/test/java/org/opentripplanner/graph_builder/module/interlining/InterlineProcessorTest.java b/src/test/java/org/opentripplanner/graph_builder/module/interlining/InterlineProcessorTest.java index 1519cce3372..3c6b226b3b2 100644 --- a/src/test/java/org/opentripplanner/graph_builder/module/interlining/InterlineProcessorTest.java +++ b/src/test/java/org/opentripplanner/graph_builder/module/interlining/InterlineProcessorTest.java @@ -21,7 +21,7 @@ import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.StopPattern; import org.opentripplanner.transit.model.network.TripPattern; -import org.opentripplanner.transit.model.timetable.TripTimes; +import org.opentripplanner.transit.model.timetable.TripTimesFactory; class InterlineProcessorTest implements PlanTestConstants { @@ -167,7 +167,7 @@ private static TripPattern tripPattern(String tripId, String blockId, String ser .withRoute(trip.getRoute()) .withStopPattern(stopPattern) .build(); - var tripTimes = new TripTimes(trip, stopTimes, new Deduplicator()); + var tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); tp.add(tripTimes); return tp; } diff --git a/src/test/java/org/opentripplanner/model/TripTimeOnDateTest.java b/src/test/java/org/opentripplanner/model/TripTimeOnDateTest.java index d3c21909e74..78002e46eae 100644 --- a/src/test/java/org/opentripplanner/model/TripTimeOnDateTest.java +++ b/src/test/java/org/opentripplanner/model/TripTimeOnDateTest.java @@ -8,7 +8,7 @@ import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.framework.Deduplicator; -import org.opentripplanner.transit.model.timetable.TripTimes; +import org.opentripplanner.transit.model.timetable.TripTimesFactory; class TripTimeOnDateTest implements PlanTestConstants { @@ -19,7 +19,7 @@ void gtfsSequence() { var trip = TransitModelForTest.trip("123").build(); var stopTimes = testModel.stopTimesEvery5Minutes(3, trip, T11_00); - var tripTimes = new TripTimes(trip, stopTimes, new Deduplicator()); + var tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); var subject = new TripTimeOnDate(tripTimes, 2, pattern); diff --git a/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceBuilderLimitPeriodTest.java b/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceBuilderLimitPeriodTest.java index 90a077eb205..6450b1f765a 100644 --- a/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceBuilderLimitPeriodTest.java +++ b/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceBuilderLimitPeriodTest.java @@ -25,8 +25,8 @@ import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.timetable.Direction; import org.opentripplanner.transit.model.timetable.Trip; -import org.opentripplanner.transit.model.timetable.TripTimes; import org.opentripplanner.transit.service.StopModel; +import org.opentripplanner.transit.model.timetable.TripTimesFactory; /** * This test will create a Transit service builder and then limit the service period. The services @@ -193,7 +193,7 @@ private TripPattern createTripPattern(Collection trips) { .build(); for (Trip trip : trips) { - p.add(new TripTimes(trip, STOP_TIMES, DEDUPLICATOR)); + p.add(TripTimesFactory.tripTimes(trip, STOP_TIMES, DEDUPLICATOR)); } return p; } diff --git a/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java b/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java index 3eccdaaabd1..dafcc639ceb 100644 --- a/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java +++ b/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java @@ -44,6 +44,7 @@ import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripTimes; +import org.opentripplanner.transit.model.timetable.TripTimesFactory; /** * This is a helper class to allow unit-testing on Itineraries. The builder does not necessarily @@ -475,7 +476,7 @@ public TestItineraryBuilder transit( .withRoute(route) .withStopPattern(stopPattern) .build(); - final TripTimes tripTimes = new TripTimes(trip, stopTimes, new Deduplicator()); + final TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); tripPattern.add(tripTimes); ScheduledTransitLeg leg; diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TripPatternForDateTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TripPatternForDateTest.java index ed13d6cbf19..ecd75f5d273 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TripPatternForDateTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TripPatternForDateTest.java @@ -20,13 +20,14 @@ import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.timetable.FrequencyEntry; import org.opentripplanner.transit.model.timetable.TripTimes; +import org.opentripplanner.transit.model.timetable.TripTimesFactory; class TripPatternForDateTest { private static final TransitModelForTest TEST_MODEL = TransitModelForTest.of(); private static final RegularStop STOP = TEST_MODEL.stop("TEST:STOP", 0, 0).build(); private static final Route ROUTE = TransitModelForTest.route("1").build(); - private static final TripTimes tripTimes = new TripTimes( + private static final TripTimes tripTimes = TripTimesFactory.tripTimes( TransitModelForTest.trip("1").withRoute(ROUTE).build(), List.of(new StopTime()), new Deduplicator() diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TripPatternForDateMapperTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TripPatternForDateMapperTest.java index 6ee2d7bcd11..020dc6305a4 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TripPatternForDateMapperTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TripPatternForDateMapperTest.java @@ -19,6 +19,7 @@ import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.timetable.TripTimes; +import org.opentripplanner.transit.model.timetable.TripTimesFactory; public class TripPatternForDateMapperTest { @@ -37,7 +38,7 @@ public static void setUp() throws Exception { var pattern = TEST_MODEL.pattern(BUS).build(); timetable = new Timetable(pattern); var trip = TransitModelForTest.trip("1").build(); - var tripTimes = new TripTimes( + var tripTimes = TripTimesFactory.tripTimes( trip, TEST_MODEL.stopTimesEvery5Minutes(5, trip, PlanTestConstants.T11_00), new Deduplicator() diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java index b5fbcf55e1e..20ca21a50a6 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java @@ -22,6 +22,7 @@ import org.opentripplanner.transit.model.network.StopPattern; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.timetable.TripTimes; +import org.opentripplanner.transit.model.timetable.TripTimesFactory; public class RaptorRoutingRequestTransitDataCreatorTest { @@ -103,7 +104,7 @@ private TripTimes createTripTimesForTest() { stopTime1.setDepartureTime(0); stopTime2.setArrivalTime(7200); - return new TripTimes( + return TripTimesFactory.tripTimes( TransitModelForTest.trip("Test").build(), Arrays.asList(stopTime1, stopTime2), new Deduplicator() diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RouteRequestTransitDataProviderFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RouteRequestTransitDataProviderFilterTest.java index 77d5cce6980..86d5f9404ed 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RouteRequestTransitDataProviderFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RouteRequestTransitDataProviderFilterTest.java @@ -44,6 +44,7 @@ import org.opentripplanner.transit.model.timetable.TripAlteration; import org.opentripplanner.transit.model.timetable.TripBuilder; import org.opentripplanner.transit.model.timetable.TripTimes; +import org.opentripplanner.transit.model.timetable.TripTimesFactory; class RouteRequestTransitDataProviderFilterTest { @@ -811,7 +812,7 @@ private TripPatternForDate createTestTripPatternForDate() { .build() .getRoutingTripPattern(); - TripTimes tripTimes = new TripTimes( + TripTimes tripTimes = TripTimesFactory.tripTimes( TransitModelForTest.trip("1").withRoute(route).build(), List.of(new StopTime()), new Deduplicator() @@ -884,7 +885,7 @@ private TripTimes createTestTripTimes( stopTime.setDepartureTime(60); stopTime.setStopSequence(0); - return new TripTimes(trip, List.of(stopTime), new Deduplicator()); + return TripTimesFactory.tripTimes(trip, List.of(stopTime), new Deduplicator()); } private TripTimes createTestTripTimesWithSubmode(String submode) { diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TestRouteData.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TestRouteData.java index 2c14a8b0b9d..a7a0c2fd160 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TestRouteData.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TestRouteData.java @@ -26,6 +26,7 @@ import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripTimes; +import org.opentripplanner.transit.model.timetable.TripTimesFactory; public class TestRouteData { @@ -139,7 +140,7 @@ private Trip parseTripInfo( .build(); var stopTimes = stopTimes(trip, stops, tripTimes); this.stopTimesByTrip.put(trip, stopTimes); - this.tripTimesByTrip.put(trip, new TripTimes(trip, stopTimes, deduplicator)); + this.tripTimesByTrip.put(trip, TripTimesFactory.tripTimes(trip, stopTimes, deduplicator)); return trip; } diff --git a/src/test/java/org/opentripplanner/routing/trippattern/FrequencyEntryTest.java b/src/test/java/org/opentripplanner/routing/trippattern/FrequencyEntryTest.java index bfc4887f756..49ca6d9221b 100644 --- a/src/test/java/org/opentripplanner/routing/trippattern/FrequencyEntryTest.java +++ b/src/test/java/org/opentripplanner/routing/trippattern/FrequencyEntryTest.java @@ -14,6 +14,7 @@ import org.opentripplanner.transit.model.timetable.FrequencyEntry; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripTimes; +import org.opentripplanner.transit.model.timetable.TripTimesFactory; public class FrequencyEntryTest { @@ -43,7 +44,7 @@ public class FrequencyEntryTest { stopTimes.add(stopTime); } - tripTimes = new TripTimes(trip, stopTimes, new Deduplicator()); + tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); } @Test diff --git a/src/test/java/org/opentripplanner/transit/model/timetable/TripTimesTest.java b/src/test/java/org/opentripplanner/transit/model/timetable/TripTimesTest.java index 6456dd732eb..4c2cc41cef2 100644 --- a/src/test/java/org/opentripplanner/transit/model/timetable/TripTimesTest.java +++ b/src/test/java/org/opentripplanner/transit/model/timetable/TripTimesTest.java @@ -23,7 +23,7 @@ class TripTimesTest { - private static TransitModelForTest TEST_MODEL = TransitModelForTest.of(); + private static final TransitModelForTest TEST_MODEL = TransitModelForTest.of(); private static final String TRIP_ID = "testTripId"; @@ -54,7 +54,7 @@ static TripTimes createInitialTripTimes() { stopTimes.add(stopTime); } - return new TripTimes(trip, stopTimes, new Deduplicator()); + return TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); } @Nested @@ -71,7 +71,7 @@ void shouldHandleBothNullScenario() { Trip trip = TransitModelForTest.trip("TRIP").build(); Collection stopTimes = List.of(EMPTY_STOPPOINT, EMPTY_STOPPOINT, EMPTY_STOPPOINT); - TripTimes tripTimes = new TripTimes(trip, stopTimes, new Deduplicator()); + TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); I18NString headsignFirstStop = tripTimes.getHeadsign(0); assertNull(headsignFirstStop); @@ -82,7 +82,7 @@ void shouldHandleTripOnlyHeadSignScenario() { Trip trip = TransitModelForTest.trip("TRIP").withHeadsign(DIRECTION).build(); Collection stopTimes = List.of(EMPTY_STOPPOINT, EMPTY_STOPPOINT, EMPTY_STOPPOINT); - TripTimes tripTimes = new TripTimes(trip, stopTimes, new Deduplicator()); + TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); I18NString headsignFirstStop = tripTimes.getHeadsign(0); assertEquals(DIRECTION, headsignFirstStop); @@ -99,7 +99,7 @@ void shouldHandleStopsOnlyHeadSignScenario() { stopWithHeadsign ); - TripTimes tripTimes = new TripTimes(trip, stopTimes, new Deduplicator()); + TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); I18NString headsignFirstStop = tripTimes.getHeadsign(0); assertEquals(STOP_TEST_DIRECTION, headsignFirstStop); @@ -116,7 +116,7 @@ void shouldHandleStopsEqualToTripHeadSignScenario() { stopWithHeadsign ); - TripTimes tripTimes = new TripTimes(trip, stopTimes, new Deduplicator()); + TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); I18NString headsignFirstStop = tripTimes.getHeadsign(0); assertEquals(DIRECTION, headsignFirstStop); @@ -129,7 +129,7 @@ void shouldHandleDifferingTripAndStopHeadSignScenario() { stopWithHeadsign.setStopHeadsign(STOP_TEST_DIRECTION); Collection stopTimes = List.of(stopWithHeadsign, EMPTY_STOPPOINT, EMPTY_STOPPOINT); - TripTimes tripTimes = new TripTimes(trip, stopTimes, new Deduplicator()); + TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); I18NString headsignFirstStop = tripTimes.getHeadsign(0); assertEquals(STOP_TEST_DIRECTION, headsignFirstStop); @@ -141,7 +141,7 @@ void shouldHandleDifferingTripAndStopHeadSignScenario() { @Test public void testStopUpdate() { - TripTimes updatedTripTimesA = new TripTimes(createInitialTripTimes()); + TripTimes updatedTripTimesA = createInitialTripTimes().copyOfScheduledTimes(); updatedTripTimesA.updateArrivalTime(3, 190); updatedTripTimesA.updateDepartureTime(3, 190); @@ -156,7 +156,7 @@ public void testStopUpdate() { @Test public void testPassedUpdate() { - TripTimes updatedTripTimesA = new TripTimes(createInitialTripTimes()); + TripTimes updatedTripTimesA = createInitialTripTimes().copyOfScheduledTimes(); updatedTripTimesA.updateDepartureTime(0, 30); @@ -166,7 +166,7 @@ public void testPassedUpdate() { @Test public void testNegativeDwellTime() { - TripTimes updatedTripTimesA = new TripTimes(createInitialTripTimes()); + TripTimes updatedTripTimesA = createInitialTripTimes().copyOfScheduledTimes(); updatedTripTimesA.updateArrivalTime(1, 60); updatedTripTimesA.updateDepartureTime(1, 59); @@ -179,7 +179,7 @@ public void testNegativeDwellTime() { @Test public void testNegativeHopTime() { - TripTimes updatedTripTimesB = new TripTimes(createInitialTripTimes()); + TripTimes updatedTripTimesB = createInitialTripTimes().copyOfScheduledTimes(); updatedTripTimesB.updateDepartureTime(6, 421); updatedTripTimesB.updateArrivalTime(7, 420); @@ -200,7 +200,7 @@ public void testNegativeHopTime() { */ @Test public void testNegativeHopTimeWithStopCancellations() { - TripTimes updatedTripTimes = new TripTimes(createInitialTripTimes()); + TripTimes updatedTripTimes = createInitialTripTimes(); updatedTripTimes.updateDepartureTime(5, 421); updatedTripTimes.updateArrivalTime(6, 481); @@ -227,7 +227,7 @@ public void testNegativeHopTimeWithStopCancellations() { */ @Test public void testPositiveHopTimeWithStopCancellationsLate() { - TripTimes updatedTripTimes = new TripTimes(createInitialTripTimes()); + TripTimes updatedTripTimes = createInitialTripTimes(); updatedTripTimes.updateDepartureTime(5, 400); updatedTripTimes.updateArrivalTime(6, 460); @@ -253,7 +253,7 @@ public void testPositiveHopTimeWithStopCancellationsLate() { */ @Test public void testPositiveHopTimeWithStopCancellationsEarly() { - TripTimes updatedTripTimes = new TripTimes(createInitialTripTimes()); + TripTimes updatedTripTimes = createInitialTripTimes(); updatedTripTimes.updateDepartureTime(5, 300); updatedTripTimes.setCancelled(6); @@ -278,7 +278,7 @@ public void testPositiveHopTimeWithStopCancellationsEarly() { */ @Test public void testPositiveHopTimeWithTerminalCancellation() { - TripTimes updatedTripTimes = new TripTimes(createInitialTripTimes()); + TripTimes updatedTripTimes = createInitialTripTimes(); updatedTripTimes.setCancelled(0); updatedTripTimes.setCancelled(1); @@ -304,7 +304,7 @@ public void testPositiveHopTimeWithTerminalCancellation() { */ @Test public void testInterpolationWithTerminalCancellation() { - TripTimes updatedTripTimes = new TripTimes(createInitialTripTimes()); + TripTimes updatedTripTimes = createInitialTripTimes(); updatedTripTimes.setCancelled(6); updatedTripTimes.setCancelled(7); @@ -323,7 +323,7 @@ public void testInterpolationWithTerminalCancellation() { */ @Test public void testInterpolationWithMultipleStopCancellations() { - TripTimes updatedTripTimes = new TripTimes(createInitialTripTimes()); + TripTimes updatedTripTimes = createInitialTripTimes(); updatedTripTimes.setCancelled(1); updatedTripTimes.setCancelled(2); @@ -352,7 +352,7 @@ public void testInterpolationWithMultipleStopCancellations() { */ @Test public void testInterpolationWithMultipleStopCancellations2() { - TripTimes updatedTripTimes = new TripTimes(createInitialTripTimes()); + TripTimes updatedTripTimes = createInitialTripTimes(); updatedTripTimes.setCancelled(1); updatedTripTimes.setCancelled(2); @@ -375,7 +375,7 @@ public void testInterpolationWithMultipleStopCancellations2() { @Test public void testNonIncreasingUpdateCrossingMidnight() { - TripTimes updatedTripTimesA = new TripTimes(createInitialTripTimes()); + TripTimes updatedTripTimesA = createInitialTripTimes().copyOfScheduledTimes(); updatedTripTimesA.updateArrivalTime(0, -300); //"Yesterday" updatedTripTimesA.updateDepartureTime(0, 50); @@ -385,7 +385,7 @@ public void testNonIncreasingUpdateCrossingMidnight() { @Test public void testDelay() { - TripTimes updatedTripTimesA = new TripTimes(createInitialTripTimes()); + TripTimes updatedTripTimesA = createInitialTripTimes().copyOfScheduledTimes(); updatedTripTimesA.updateDepartureDelay(0, 10); updatedTripTimesA.updateArrivalDelay(6, 13); @@ -395,14 +395,14 @@ public void testDelay() { @Test public void testCancel() { - TripTimes updatedTripTimesA = new TripTimes(createInitialTripTimes()); + TripTimes updatedTripTimesA = createInitialTripTimes().copyOfScheduledTimes(); updatedTripTimesA.cancelTrip(); assertEquals(RealTimeState.CANCELED, updatedTripTimesA.getRealTimeState()); } @Test public void testNoData() { - TripTimes updatedTripTimesA = new TripTimes(createInitialTripTimes()); + TripTimes updatedTripTimesA = createInitialTripTimes().copyOfScheduledTimes(); updatedTripTimesA.setNoData(1); assertFalse(updatedTripTimesA.isNoDataStop(0)); assertTrue(updatedTripTimesA.isNoDataStop(1)); @@ -463,9 +463,9 @@ public void testApply() { stopTimes.add(stopTime1); stopTimes.add(stopTime2); - TripTimes differingTripTimes = new TripTimes(trip, stopTimes, new Deduplicator()); + TripTimes differingTripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); - TripTimes updatedTripTimesA = new TripTimes(differingTripTimes); + TripTimes updatedTripTimesA = differingTripTimes.copyOfScheduledTimes(); updatedTripTimesA.updateArrivalTime(1, 89); updatedTripTimesA.updateDepartureTime(1, 98); diff --git a/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java b/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java index 0654211d1e1..29d03844260 100644 --- a/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java +++ b/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java @@ -37,7 +37,7 @@ import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.timetable.OccupancyStatus; import org.opentripplanner.transit.model.timetable.Trip; -import org.opentripplanner.transit.model.timetable.TripTimes; +import org.opentripplanner.transit.model.timetable.TripTimesFactory; public class RealtimeVehicleMatcherTest { @@ -345,7 +345,7 @@ void inferServiceDayOfTripAt6(String time, String expectedDate) { testModel.stopTime(trip, 1, fivePast6) ); - var tripTimes = new TripTimes(trip, stopTimes, new Deduplicator()); + var tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); var instant = OffsetDateTime.parse(time).toInstant(); var inferredDate = RealtimeVehiclePatternMatcher.inferServiceDate(tripTimes, zoneId, instant); @@ -364,7 +364,7 @@ void inferServiceDateCloseToMidnight() { testModel.stopTime(trip, 1, fivePastMidnight) ); - var tripTimes = new TripTimes(trip, stopTimes, new Deduplicator()); + var tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); var time = OffsetDateTime.parse("2022-04-05T00:04:00+02:00").toInstant(); @@ -384,7 +384,7 @@ private static TripPattern tripPattern(Trip trip, List stopTimes) { .build(); pattern .getScheduledTimetable() - .addTripTimes(new TripTimes(trip, stopTimes, new Deduplicator())); + .addTripTimes(TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator())); return pattern; } From 03ee1c48a552174889e2ce3dc368cb42698670fd Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 16 Nov 2023 18:59:59 +0100 Subject: [PATCH 07/11] refactor: Create a NOOP deduplicator for making unit-testing easier. The need to create a Deduplicator to unit test is make the unit-test setup more messy than it needs to, and it introduces a unknown into the test equation - something that is not under-test. Signed-off-by: Thomas Gran # Conflicts: # src/test/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReferenceTest.java --- .../transit/model/framework/Deduplicator.java | 15 +++-- .../model/framework/DeduplicatorNoop.java | 56 +++++++++++++++++++ .../model/framework/DeduplicatorService.java | 44 +++++++++++++++ .../timetable/ScheduledTripTimesBuilder.java | 8 ++- ...pTransitServiceBuilderLimitPeriodTest.java | 2 +- .../ScheduledTransitLegReferenceTest.java | 4 +- 6 files changed, 117 insertions(+), 12 deletions(-) create mode 100644 src/main/java/org/opentripplanner/transit/model/framework/DeduplicatorNoop.java create mode 100644 src/main/java/org/opentripplanner/transit/model/framework/DeduplicatorService.java diff --git a/src/main/java/org/opentripplanner/transit/model/framework/Deduplicator.java b/src/main/java/org/opentripplanner/transit/model/framework/Deduplicator.java index b223ac7412f..587ad2871cd 100644 --- a/src/main/java/org/opentripplanner/transit/model/framework/Deduplicator.java +++ b/src/main/java/org/opentripplanner/transit/model/framework/Deduplicator.java @@ -20,7 +20,7 @@ * Does the same thing as String.intern, but for several different types. Java's String.intern uses * perm gen space and is broken anyway. */ -public class Deduplicator implements Serializable { +public class Deduplicator implements DeduplicatorService, Serializable { private static final String ZERO_COUNT = sizeAndCount(0, 0); @@ -50,6 +50,7 @@ public void reset() { canonicalLists.clear(); } + @Override @Nullable public BitSet deduplicateBitSet(BitSet original) { if (original == null) { @@ -64,7 +65,7 @@ public BitSet deduplicateBitSet(BitSet original) { return canonical; } - /** Used to deduplicate time and stop sequence arrays. The same times may occur in many trips. */ + @Override @Nullable public int[] deduplicateIntArray(int[] original) { if (original == null) { @@ -80,6 +81,7 @@ public int[] deduplicateIntArray(int[] original) { return canonical.array; } + @Override @Nullable public String deduplicateString(String original) { if (original == null) { @@ -90,6 +92,7 @@ public String deduplicateString(String original) { return canonical == null ? original : canonical; } + @Override @Nullable public String[] deduplicateStringArray(String[] original) { if (original == null) { @@ -104,10 +107,7 @@ public String[] deduplicateStringArray(String[] original) { return canonical.array; } - /** - * Used to deduplicate list of via places for stop destinationDisplay. Stops may have the same via - * arrays. - */ + @Override @Nullable public String[][] deduplicateString2DArray(String[][] original) { if (original == null) { @@ -122,6 +122,7 @@ public String[][] deduplicateString2DArray(String[][] original) { return canonical.array; } + @Override @SuppressWarnings("unchecked") @Nullable public T deduplicateObject(Class cl, T original) { @@ -137,6 +138,7 @@ public T deduplicateObject(Class cl, T original) { return canonical == null ? original : canonical; } + @Override @Nullable public T[] deduplicateObjectArray(Class type, T[] original) { if (original == null) { @@ -160,6 +162,7 @@ public T[] deduplicateObjectArray(Class type, T[] original) { return canonical.array(); } + @Override @Nullable public List deduplicateImmutableList(Class clazz, List original) { if (original == null) { diff --git a/src/main/java/org/opentripplanner/transit/model/framework/DeduplicatorNoop.java b/src/main/java/org/opentripplanner/transit/model/framework/DeduplicatorNoop.java new file mode 100644 index 00000000000..568772c29fc --- /dev/null +++ b/src/main/java/org/opentripplanner/transit/model/framework/DeduplicatorNoop.java @@ -0,0 +1,56 @@ +package org.opentripplanner.transit.model.framework; + +import java.util.BitSet; +import java.util.List; +import javax.annotation.Nullable; + +class DeduplicatorNoop implements DeduplicatorService { + + @Nullable + @Override + public BitSet deduplicateBitSet(BitSet original) { + return original; + } + + @Nullable + @Override + public int[] deduplicateIntArray(int[] original) { + return original; + } + + @Nullable + @Override + public String deduplicateString(String original) { + return original; + } + + @Nullable + @Override + public String[] deduplicateStringArray(String[] original) { + return original; + } + + @Nullable + @Override + public String[][] deduplicateString2DArray(String[][] original) { + return original; + } + + @Nullable + @Override + public T deduplicateObject(Class cl, T original) { + return original; + } + + @Nullable + @Override + public T[] deduplicateObjectArray(Class type, T[] original) { + return original; + } + + @Nullable + @Override + public List deduplicateImmutableList(Class clazz, List original) { + return original; + } +} diff --git a/src/main/java/org/opentripplanner/transit/model/framework/DeduplicatorService.java b/src/main/java/org/opentripplanner/transit/model/framework/DeduplicatorService.java new file mode 100644 index 00000000000..d8c4a9ceb82 --- /dev/null +++ b/src/main/java/org/opentripplanner/transit/model/framework/DeduplicatorService.java @@ -0,0 +1,44 @@ +package org.opentripplanner.transit.model.framework; + +import java.util.BitSet; +import java.util.List; +import javax.annotation.Nullable; + +/** + * The deduplication service is used to reduce memory consumption by returning the + * same instance if to value-objects are the same. The value-objects must implement + * hashCode and equals. + *

+ * This is also used with arrays of primitive types. Arrays are mutable, so be sure they are + * well protected and do not change if you deduplicate them. + *

+ * Note! The deduplicator should ONLY be used with immutable types and well protected + * fields - guaranteed not to be changed. + */ +public interface DeduplicatorService { + DeduplicatorService NOOP = new DeduplicatorNoop(); + + @Nullable + BitSet deduplicateBitSet(BitSet original); + + @Nullable + int[] deduplicateIntArray(int[] original); + + @Nullable + String deduplicateString(String original); + + @Nullable + String[] deduplicateStringArray(String[] original); + + @Nullable + String[][] deduplicateString2DArray(String[][] original); + + @Nullable + T deduplicateObject(Class cl, T original); + + @Nullable + T[] deduplicateObjectArray(Class type, T[] original); + + @Nullable + List deduplicateImmutableList(Class clazz, List original); +} diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesBuilder.java b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesBuilder.java index 2b6727dacf6..b935944d3fa 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesBuilder.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesBuilder.java @@ -2,9 +2,11 @@ import java.util.BitSet; import java.util.List; +import javax.annotation.Nullable; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.model.BookingInfo; import org.opentripplanner.transit.model.framework.Deduplicator; +import org.opentripplanner.transit.model.framework.DeduplicatorService; public class ScheduledTripTimesBuilder { @@ -22,10 +24,10 @@ public class ScheduledTripTimesBuilder { String[][] headsignVias; int[] originalGtfsStopSequence; - private final Deduplicator deduplicator; + private final DeduplicatorService deduplicator; - ScheduledTripTimesBuilder(Deduplicator deduplicator) { - this.deduplicator = deduplicator; + ScheduledTripTimesBuilder(@Nullable DeduplicatorService deduplicator) { + this.deduplicator = deduplicator == null ? DeduplicatorService.NOOP : deduplicator; } ScheduledTripTimesBuilder( diff --git a/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceBuilderLimitPeriodTest.java b/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceBuilderLimitPeriodTest.java index 6450b1f765a..5d1fef21271 100644 --- a/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceBuilderLimitPeriodTest.java +++ b/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceBuilderLimitPeriodTest.java @@ -25,8 +25,8 @@ import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.timetable.Direction; import org.opentripplanner.transit.model.timetable.Trip; -import org.opentripplanner.transit.service.StopModel; import org.opentripplanner.transit.model.timetable.TripTimesFactory; +import org.opentripplanner.transit.service.StopModel; /** * This test will create a Transit service builder and then limit the service period. The services diff --git a/src/test/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReferenceTest.java b/src/test/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReferenceTest.java index 22252093aba..a3fab1838cb 100644 --- a/src/test/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReferenceTest.java +++ b/src/test/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReferenceTest.java @@ -23,7 +23,7 @@ import org.opentripplanner.transit.model.site.Station; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripOnServiceDate; -import org.opentripplanner.transit.model.timetable.TripTimes; +import org.opentripplanner.transit.model.timetable.TripTimesFactory; import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.StopModel; import org.opentripplanner.transit.service.TransitModel; @@ -64,7 +64,7 @@ static void buildTransitService() { stopIdAtPosition0 = tripPattern.getStop(0).getId(); stopIdAtPosition1 = tripPattern.getStop(1).getId(); stopIdAtPosition2 = tripPattern.getStop(2).getId(); - TripTimes tripTimes = new TripTimes( + var tripTimes = TripTimesFactory.tripTimes( trip, TEST_MODEL.stopTimesEvery5Minutes(5, trip, PlanTestConstants.T11_00), new Deduplicator() From 620cadf727891cf0017d17d739bede7be538b258 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Sun, 1 Oct 2023 00:20:57 +0200 Subject: [PATCH 08/11] refactor: Make ScheduledTripTimes immutable --- .../model/timetable/ScheduledTripTimes.java | 53 ++++++++----------- .../timetable/ScheduledTripTimesBuilder.java | 9 ++++ .../transit/model/timetable/TripTimes.java | 22 +++++--- 3 files changed, 46 insertions(+), 38 deletions(-) diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java index 0102ff6366f..89b578a7446 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java @@ -19,18 +19,15 @@ final class ScheduledTripTimes implements Serializable, Comparable { - private static final int NOT_SET = -1; - private final int[] scheduledArrivalTimes; private final int[] scheduledDepartureTimes; /** - * This allows re-using the same scheduled arrival and departure time arrays for many - * ScheduledTripTimes. It is also used in materializing frequency-based ScheduledTripTimes. + * Implementation notes: This allows re-using the same scheduled arrival and departure time + * arrays for many ScheduledTripTimes. It is also used in materializing frequency-based + * ScheduledTripTimes. */ private final int timeShift; - - /** Implementation notes: not final because these are set after construction. */ - private int serviceCode; + private final int serviceCode; private final BitSet timepoints; private final List dropOffBookingInfos; private final List pickupBookingInfos; @@ -50,24 +47,6 @@ final class ScheduledTripTimes implements Serializable, Comparable + * Always provide a deduplicator when building the graph. No deduplication is ok when changing + * simple fields like {@code timeShift} and {@code serviceCode} or even the prefered way in a + * unittest. + */ + public ScheduledTripTimesBuilder copyOf(@Nullable Deduplicator deduplicator) { return new ScheduledTripTimesBuilder( scheduledArrivalTimes, scheduledDepartureTimes, @@ -103,11 +89,18 @@ public ScheduledTripTimesBuilder copyOf(Deduplicator deduplicator) { ); } + /** + * @see #copyOf(Deduplicator) copyOf(null) + */ + public ScheduledTripTimesBuilder copyOfNoDuplication() { + return copyOf(null); + } + /** * Both trip_headsign and stop_headsign (per stop on a particular trip) are optional GTFS fields. * A trip may not have a headsign, in which case we should fall back on a Timetable or * Pattern-level headsign. Such a string will be available when we give TripPatterns or - * StopPatterns unique human readable route variant names, but a ScheduledTripTimes currently + * StopPatterns unique human-readable route variant names, but a ScheduledTripTimes currently * does not have a pointer to its enclosing timetable or pattern. */ @Nullable @@ -322,10 +315,6 @@ public int getServiceCode() { return serviceCode; } - public void setServiceCode(int serviceCode) { - this.serviceCode = serviceCode; - } - /** The trips whose arrivals and departures are represented by this class */ public Trip getTrip() { return trip; diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesBuilder.java b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesBuilder.java index b935944d3fa..4ab2b389cc1 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesBuilder.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesBuilder.java @@ -73,6 +73,15 @@ public ScheduledTripTimesBuilder withTimeShift(int timeShift) { return this; } + /** + * Add the {@code delta} to the existing timeShift. This is useful when moving a trip + * from one time-zone to another. + */ + public ScheduledTripTimesBuilder plusTimeShift(int delta) { + this.timeShift += delta; + return this; + } + public ScheduledTripTimesBuilder withServiceCode(int serviceCode) { this.serviceCode = serviceCode; return this; diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java index 645470555d4..986c172b3c4 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java @@ -23,7 +23,7 @@ */ public final class TripTimes implements Serializable, Comparable { - private final ScheduledTripTimes scheduledTripTimes; + private ScheduledTripTimes scheduledTripTimes; private int[] arrivalTimes; private int[] departureTimes; @@ -34,7 +34,10 @@ public final class TripTimes implements Serializable, Comparable { private Accessibility wheelchairAccessibility; private TripTimes(final TripTimes original, int timeShiftDelta) { - this(original, new ScheduledTripTimes(original.scheduledTripTimes, timeShiftDelta)); + this( + original, + original.scheduledTripTimes.copyOfNoDuplication().plusTimeShift(timeShiftDelta).build() + ); } TripTimes(ScheduledTripTimes scheduledTripTimes) { @@ -435,14 +438,20 @@ public TripTimes timeShift(final int stop, final int time, final boolean depart) // Adjust 0-based times to match desired stoptime. final int shift = time - (depart ? getDepartureTime(stop) : getArrivalTime(stop)); - return new TripTimes(this, shift); + return new TripTimes( + this, + scheduledTripTimes.copyOfNoDuplication().plusTimeShift(shift).build() + ); } /** * Time-shift all times on this trip. This is used when updating the time zone for the trip. */ - public TripTimes adjustTimesToGraphTimeZone(Duration duration) { - return new TripTimes(this, (int) duration.toSeconds()); + public TripTimes adjustTimesToGraphTimeZone(Duration shiftDelta) { + return new TripTimes( + this, + scheduledTripTimes.copyOfNoDuplication().plusTimeShift((int) shiftDelta.toSeconds()).build() + ); } /** @@ -480,7 +489,8 @@ public int getServiceCode() { } public void setServiceCode(int serviceCode) { - scheduledTripTimes.setServiceCode(serviceCode); + this.scheduledTripTimes = + scheduledTripTimes.copyOfNoDuplication().withServiceCode(serviceCode).build(); } /** The trips whose arrivals and departures are represented by this class */ From 81f95323675872daed5db23ce12105a6e5ab12cc Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Sun, 1 Oct 2023 00:25:28 +0200 Subject: [PATCH 09/11] refactor: Add equals and hashCode to ScheduledTripTimes --- .../transit/model/timetable/ScheduledTripTimes.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java index 89b578a7446..94d565fc17c 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java @@ -315,6 +315,16 @@ public int getServiceCode() { return serviceCode; } + @Override + public boolean equals(Object o) { + throw new UnsupportedOperationException("Not implemented, implement if needed!"); + } + + @Override + public int hashCode() { + throw new UnsupportedOperationException("Not implemented, implement if needed!"); + } + /** The trips whose arrivals and departures are represented by this class */ public Trip getTrip() { return trip; From 829704921afa0c52d9c0d61958605c357dedd364 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Sun, 1 Oct 2023 00:42:28 +0200 Subject: [PATCH 10/11] refactor: Reorder fields and methods from most to least important/relevant --- .../model/timetable/ScheduledTripTimes.java | 206 +++++++++--------- .../timetable/ScheduledTripTimesBuilder.java | 49 ++--- 2 files changed, 127 insertions(+), 128 deletions(-) diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java index 94d565fc17c..42af310f998 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java @@ -19,8 +19,6 @@ final class ScheduledTripTimes implements Serializable, Comparable { - private final int[] scheduledArrivalTimes; - private final int[] scheduledDepartureTimes; /** * Implementation notes: This allows re-using the same scheduled arrival and departure time * arrays for many ScheduledTripTimes. It is also used in materializing frequency-based @@ -28,19 +26,21 @@ final class ScheduledTripTimes implements Serializable, Comparable dropOffBookingInfos; private final List pickupBookingInfos; - private final Trip trip; @Nullable private final I18NString[] headsigns; /** - * Implementation notes: This is 2D array since there can be more than - * one via name/stop per each record in stop sequence). Outer array may be null if there are no - * vias in stop sequence. Inner array may be null if there are no vias for particular stop. This - * is done in order to save space. + * Implementation notes: This is 2D array since there can be more than one via name/stop per each + * record in stop sequence). Outer array may be null if there are no vias in stop sequence. Inner + * array may be null if there are no vias for particular stop. This is done in order to save + * space. */ @Nullable private final String[][] headsignVias; @@ -49,16 +49,16 @@ final class ScheduledTripTimes implements Serializable, Comparable getHeadsignVias(final int stop) { - if (headsignVias == null || headsignVias[stop] == null) { - return List.of(); - } - return List.of(headsignVias[stop]); - } - - /** - * @return the whole trip's headsign. Individual stops can have different headsigns. - */ - public I18NString getTripHeadsign() { - return trip.getHeadsign(); + /** The code for the service on which this trip runs. For departure search optimizations. */ + public int getServiceCode() { + return serviceCode; } /** @@ -136,33 +106,28 @@ public I18NString getTripHeadsign() { * according to the original schedule. */ public int getScheduledArrivalTime(final int stop) { - return scheduledArrivalTimes[stop] + timeShift; + return arrivalTimes[stop] + timeShift; } /** - * The time in seconds after midnight at which the vehicle should leave the given stop according - * to the original schedule. + * The time in seconds after midnight at which the vehicle arrives at each stop, accounting for + * any real-time updates. */ - public int getScheduledDepartureTime(final int stop) { - return scheduledDepartureTimes[stop] + timeShift; + public int getArrivalTime(final int stop) { + return getScheduledArrivalTime(stop); } - /** - * Return an integer which can be used to sort TripTimes in order of departure/arrivals. - *

- * This sorted trip times is used to search for trips. OTP assume one trip do NOT pass another - * trip down the line. - */ - public int sortIndex() { - return getDepartureTime(0); + /** @return the difference between the scheduled and actual arrival times at this stop. */ + public int getArrivalDelay(final int stop) { + return getArrivalTime(stop) - (arrivalTimes[stop] + timeShift); } /** - * The time in seconds after midnight at which the vehicle arrives at each stop, accounting for - * any real-time updates. + * The time in seconds after midnight at which the vehicle should leave the given stop according + * to the original schedule. */ - public int getArrivalTime(final int stop) { - return getScheduledArrivalTime(stop); + public int getScheduledDepartureTime(final int stop) { + return departureTimes[stop] + timeShift; } /** @@ -173,21 +138,31 @@ public int getDepartureTime(final int stop) { return getScheduledDepartureTime(stop); } - /** @return the difference between the scheduled and actual arrival times at this stop. */ - public int getArrivalDelay(final int stop) { - return getArrivalTime(stop) - (scheduledArrivalTimes[stop] + timeShift); - } - /** @return the difference between the scheduled and actual departure times at this stop. */ public int getDepartureDelay(final int stop) { - return getDepartureTime(stop) - (scheduledDepartureTimes[stop] + timeShift); + return getDepartureTime(stop) - (departureTimes[stop] + timeShift); } /** - * This is only for API-purposes (does not affect routing). + * Whether or not stopIndex is considered a GTFS timepoint. */ - public OccupancyStatus getOccupancyStatus(int stop) { - return OccupancyStatus.NO_DATA_AVAILABLE; + public boolean isTimepoint(final int stopIndex) { + return timepoints.get(stopIndex); + } + + /** The trips whose arrivals and departures are represented by this class */ + public Trip getTrip() { + return trip; + } + + /** + * Return an integer which can be used to sort TripTimes in order of departure/arrivals. + *

+ * This sorted trip times is used to search for trips. OTP assume one trip do NOT pass another + * trip down the line. + */ + public int sortIndex() { + return getDepartureTime(0); } public BookingInfo getDropOffBookingInfo(int stop) { @@ -233,6 +208,56 @@ public RealTimeState getRealTimeState() { return RealTimeState.SCHEDULED; } + /** + * @return the whole trip's headsign. Individual stops can have different headsigns. + */ + public I18NString getTripHeadsign() { + return trip.getHeadsign(); + } + + /** + * Both trip_headsign and stop_headsign (per stop on a particular trip) are optional GTFS fields. + * A trip may not have a headsign, in which case we should fall back on a Timetable or + * Pattern-level headsign. Such a string will be available when we give TripPatterns or + * StopPatterns unique human-readable route variant names, but a ScheduledTripTimes currently + * does not have a pointer to its enclosing timetable or pattern. + */ + @Nullable + public I18NString getHeadsign(final int stop) { + return (headsigns != null && headsigns[stop] != null) + ? headsigns[stop] + : getTrip().getHeadsign(); + } + + /** + * Return list of via names per particular stop. This field provides info about intermediate stops + * between current stop and final trip destination. Mapped from NeTEx DestinationDisplay.vias. No + * GTFS mapping at the moment. + * + * @return Empty list if there are no vias registered for a stop. + */ + public List getHeadsignVias(final int stop) { + if (headsignVias == null || headsignVias[stop] == null) { + return List.of(); + } + return List.of(headsignVias[stop]); + } + + public int getNumStops() { + return arrivalTimes.length; + } + + public Accessibility getWheelchairAccessibility() { + return trip.getWheelchairBoarding(); + } + + /** + * This is only for API-purposes (does not affect routing). + */ + public OccupancyStatus getOccupancyStatus(int stop) { + return OccupancyStatus.NO_DATA_AVAILABLE; + } + /** * When creating ScheduledTripTimes or wrapping it in updates, we could potentially imply * negative running or dwell times. We really don't want those being used in routing. This method @@ -241,7 +266,7 @@ public RealTimeState getRealTimeState() { * @return empty if times were found to be increasing, stop index of the first error otherwise */ public Optional validateNonIncreasingTimes() { - final int nStops = scheduledArrivalTimes.length; + final int nStops = arrivalTimes.length; int prevDep = -9_999_999; for (int s = 0; s < nStops; s++) { final int arr = getArrivalTime(s); @@ -258,14 +283,6 @@ public Optional validateNonIncreasingTimes() { return Optional.empty(); } - public Accessibility getWheelchairAccessibility() { - return trip.getWheelchairBoarding(); - } - - public int getNumStops() { - return scheduledArrivalTimes.length; - } - /** Sort trips based on first departure time. */ @Override public int compareTo(final ScheduledTripTimes other) { @@ -303,18 +320,6 @@ public OptionalInt stopIndexOfGtfsSequence(int stopSequence) { return OptionalInt.empty(); } - /** - * Whether or not stopIndex is considered a GTFS timepoint. - */ - public boolean isTimepoint(final int stopIndex) { - return timepoints.get(stopIndex); - } - - /** The code for the service on which this trip runs. For departure search optimizations. */ - public int getServiceCode() { - return serviceCode; - } - @Override public boolean equals(Object o) { throw new UnsupportedOperationException("Not implemented, implement if needed!"); @@ -325,19 +330,14 @@ public int hashCode() { throw new UnsupportedOperationException("Not implemented, implement if needed!"); } - /** The trips whose arrivals and departures are represented by this class */ - public Trip getTrip() { - return trip; - } - /* package local - only visible to timetable classes */ int[] copyArrivalTimes() { - return IntUtils.shiftArray(timeShift, scheduledArrivalTimes); + return IntUtils.shiftArray(timeShift, arrivalTimes); } int[] copyDepartureTimes() { - return IntUtils.shiftArray(timeShift, scheduledDepartureTimes); + return IntUtils.shiftArray(timeShift, departureTimes); } I18NString[] copyHeadsigns(Supplier defaultValue) { diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesBuilder.java b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesBuilder.java index 4ab2b389cc1..d6515321f74 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesBuilder.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesBuilder.java @@ -12,18 +12,17 @@ public class ScheduledTripTimesBuilder { private final int NOT_SET = -1; - int[] arrivalTimes; - int[] departureTimes; int timeShift; int serviceCode = NOT_SET; + int[] arrivalTimes; + int[] departureTimes; BitSet timepoints; + Trip trip; List dropOffBookingInfos; List pickupBookingInfos; - Trip trip; I18NString[] headsigns; String[][] headsignVias; int[] originalGtfsStopSequence; - private final DeduplicatorService deduplicator; ScheduledTripTimesBuilder(@Nullable DeduplicatorService deduplicator) { @@ -31,43 +30,33 @@ public class ScheduledTripTimesBuilder { } ScheduledTripTimesBuilder( - int[] arrivalTimes, - int[] departureTimes, int timeShift, int serviceCode, + int[] arrivalTimes, + int[] departureTimes, BitSet timepoints, + Trip trip, List dropOffBookingInfos, List pickupBookingInfos, - Trip trip, I18NString[] headsigns, String[][] headsignVias, int[] originalGtfsStopSequence, Deduplicator deduplicator ) { this(deduplicator); - this.arrivalTimes = arrivalTimes; - this.departureTimes = departureTimes; this.timeShift = timeShift; this.serviceCode = serviceCode; + this.arrivalTimes = arrivalTimes; + this.departureTimes = departureTimes; this.timepoints = timepoints; + this.trip = trip; this.dropOffBookingInfos = dropOffBookingInfos; this.pickupBookingInfos = pickupBookingInfos; - this.trip = trip; this.headsigns = headsigns; this.headsignVias = headsignVias; this.originalGtfsStopSequence = originalGtfsStopSequence; } - public ScheduledTripTimesBuilder withArrivalTimes(int[] arrivalTimes) { - this.arrivalTimes = deduplicator.deduplicateIntArray(arrivalTimes); - return this; - } - - public ScheduledTripTimesBuilder withDepartureTimes(int[] departureTimes) { - this.departureTimes = deduplicator.deduplicateIntArray(departureTimes); - return this; - } - public ScheduledTripTimesBuilder withTimeShift(int timeShift) { this.timeShift = timeShift; return this; @@ -87,11 +76,26 @@ public ScheduledTripTimesBuilder withServiceCode(int serviceCode) { return this; } + public ScheduledTripTimesBuilder withArrivalTimes(int[] arrivalTimes) { + this.arrivalTimes = deduplicator.deduplicateIntArray(arrivalTimes); + return this; + } + + public ScheduledTripTimesBuilder withDepartureTimes(int[] departureTimes) { + this.departureTimes = deduplicator.deduplicateIntArray(departureTimes); + return this; + } + public ScheduledTripTimesBuilder withTimepoints(BitSet timepoints) { this.timepoints = deduplicator.deduplicateBitSet(timepoints); return this; } + public ScheduledTripTimesBuilder withTrip(Trip trip) { + this.trip = trip; + return this; + } + public ScheduledTripTimesBuilder withDropOffBookingInfos(List dropOffBookingInfos) { this.dropOffBookingInfos = deduplicator.deduplicateImmutableList(BookingInfo.class, dropOffBookingInfos); @@ -104,11 +108,6 @@ public ScheduledTripTimesBuilder withPickupBookingInfos(List pickup return this; } - public ScheduledTripTimesBuilder withTrip(Trip trip) { - this.trip = trip; - return this; - } - public ScheduledTripTimesBuilder withHeadsigns(I18NString[] headsigns) { this.headsigns = deduplicator.deduplicateObjectArray(I18NString.class, headsigns); return this; From 90929bbcde8f7d6f98a05c23cf48f76c2347f7e5 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Tue, 21 Nov 2023 16:39:10 +0100 Subject: [PATCH 11/11] review: Apply spelling fixes to doc. --- .../transit/model/timetable/ScheduledTripTimes.java | 4 ++-- .../opentripplanner/transit/model/timetable/TripTimes.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java index 42af310f998..c5a5d8f097c 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java @@ -263,7 +263,7 @@ public OccupancyStatus getOccupancyStatus(int stop) { * negative running or dwell times. We really don't want those being used in routing. This method * checks that all times are increasing. * - * @return empty if times were found to be increasing, stop index of the first error otherwise + * @return empty if times were found to be increasing, the first validation error otherwise */ public Optional validateNonIncreasingTimes() { final int nStops = arrivalTimes.length; @@ -293,7 +293,7 @@ public int compareTo(final ScheduledTripTimes other) { * Returns the GTFS sequence number of the given 0-based stop position. * * These are the GTFS stop sequence numbers, which show the order in which the vehicle visits the - * stops. Despite the face that the StopPattern or TripPattern enclosing this class provides + * stops. Despite the fact that the StopPattern or TripPattern enclosing this class provides * an ordered list of Stops, the original stop sequence numbers may still be needed for matching * with GTFS-RT update messages. Unfortunately, each individual trip can have totally different * sequence numbers for the same stops, so we need to store them at the individual trip level. An diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java index 986c172b3c4..b4ca4850a18 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java @@ -278,7 +278,7 @@ public void setRealTimeState(final RealTimeState realTimeState) { * checks that all internal times are increasing. Thus, this check should be used at the end of * updating trip times, after any propagating or interpolating delay operations. * - * @return empty if times were found to be increasing, stop index of the first error otherwise + * @return empty if times were found to be increasing, the first validation error otherwise */ public Optional validateNonIncreasingTimes() { final int nStops = arrivalTimes.length; @@ -458,7 +458,7 @@ public TripTimes adjustTimesToGraphTimeZone(Duration shiftDelta) { * Returns the GTFS sequence number of the given 0-based stop position. * * These are the GTFS stop sequence numbers, which show the order in which the vehicle visits the - * stops. Despite the face that the StopPattern or TripPattern enclosing this TripTimes provides + * stops. Despite the fact that the StopPattern or TripPattern enclosing this TripTimes provides * an ordered list of Stops, the original stop sequence numbers may still be needed for matching * with GTFS-RT update messages. Unfortunately, each individual trip can have totally different * sequence numbers for the same stops, so we need to store them at the individual trip level. An