-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Report issue for duplicate journey patterns in NeTEx #5571
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,10 +5,12 @@ | |
import jakarta.xml.bind.JAXBElement; | ||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
import java.util.HashMap; | ||
import java.util.HashSet; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
import java.util.Optional; | ||
import java.util.stream.Collectors; | ||
import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; | ||
import org.opentripplanner.model.StopTime; | ||
|
@@ -84,8 +86,6 @@ class TripPatternMapper { | |
|
||
private final Deduplicator deduplicator; | ||
|
||
private TripPatternMapperResult result; | ||
|
||
TripPatternMapper( | ||
DataImportIssueStore issueStore, | ||
FeedScopedIdFactory idFactory, | ||
|
@@ -157,9 +157,7 @@ class TripPatternMapper { | |
} | ||
} | ||
|
||
TripPatternMapperResult mapTripPattern(JourneyPattern_VersionStructure journeyPattern) { | ||
// Make sure the result is clean, by creating a new object. | ||
result = new TripPatternMapperResult(); | ||
Optional<TripPatternMapperResult> mapTripPattern(JourneyPattern_VersionStructure journeyPattern) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Observation: The original result class did handle the empty case, so this is weakening the existing functionality. The good thing is that a it is a bit less code to maintain, but since this does not reduce the code in the caller - it complicates it, than gain is minimal. I do not suggest to change it, but just wanted to mention it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wanted to make it explicit that if we return a result then it will have a single TripPattern. I could have added an optional/nullable Now the caller has to handle the case where the result is empty, it's a bit more complicated at the call site but IMO it's worth it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My point is that you are wrapping a result wrapper with another result wrapper - Optional and TripPatternMapperResult have some of the same responsibilities. You should not wrap the field inside There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another thing is that we avoid using records with arrays and collections. Arrays can not be protected, while collections are hard to protect - classes are simpler/less error prune. Even if the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In this PR my idea is to change the responsibilities so that the result class is only responsible for representing an actual result. The Optional is responsible for representing presence/absence of a result. I think this is a pretty common pattern. And I think it is a good pattern since the result class would only have to concern itself with a single responsibility.
The thing is that I want the result to have a single instance of a TripPattern instead of a collection of TripPatterns. Because I want the caller to be able to see what the resulting id is and I wanted it to be obvous that the issue for duplicated ids is only reported once. In this codebase, would it be more idiomatic to put a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Is this in order to protect the collections from mutation? I think that's a good concern. But then having collections as public attributes should be just as bad, or am I missing something? In this refactored code it wouldn't be that hard to make the collections unmodifiable when creating the record to protect them from modification. It's not perfect but I think it's a win for this style. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Of cause not. The But we can agree to disagree - that is fine. I will approve the PR. |
||
Collection<ServiceJourney> serviceJourneys = serviceJourniesByPatternId.get( | ||
journeyPattern.getId() | ||
); | ||
|
@@ -170,10 +168,14 @@ TripPatternMapperResult mapTripPattern(JourneyPattern_VersionStructure journeyPa | |
"ServiceJourneyPattern %s does not contain any serviceJourneys.", | ||
journeyPattern.getId() | ||
); | ||
return result; | ||
return Optional.empty(); | ||
} | ||
|
||
List<Trip> trips = new ArrayList<>(); | ||
ArrayListMultimap<String, String> scheduledStopPointsIndex = ArrayListMultimap.create(); | ||
HashMap<Trip, List<StopTime>> tripStopTimes = new HashMap<>(); | ||
Map<String, StopTime> stopTimeByNetexId = new HashMap<>(); | ||
ArrayList<TripOnServiceDate> tripOnServiceDates = new ArrayList<>(); | ||
|
||
for (ServiceJourney serviceJourney : serviceJourneys) { | ||
Trip trip = mapTrip(journeyPattern, serviceJourney); | ||
|
@@ -184,7 +186,7 @@ TripPatternMapperResult mapTripPattern(JourneyPattern_VersionStructure journeyPa | |
} | ||
|
||
// Add the dated service journey to the model for this trip [if it exists] | ||
mapDatedServiceJourney(journeyPattern, serviceJourney, trip); | ||
tripOnServiceDates.addAll(mapDatedServiceJourney(journeyPattern, serviceJourney, trip)); | ||
|
||
StopTimesMapperResult stopTimes = stopTimesMapper.mapToStopTimes( | ||
journeyPattern, | ||
|
@@ -198,25 +200,22 @@ TripPatternMapperResult mapTripPattern(JourneyPattern_VersionStructure journeyPa | |
continue; | ||
} | ||
|
||
result.scheduledStopPointsIndex.putAll( | ||
serviceJourney.getId(), | ||
stopTimes.scheduledStopPointIds | ||
); | ||
result.tripStopTimes.put(trip, stopTimes.stopTimes); | ||
result.stopTimeByNetexId.putAll(stopTimes.stopTimeByNetexId); | ||
scheduledStopPointsIndex.putAll(serviceJourney.getId(), stopTimes.scheduledStopPointIds); | ||
tripStopTimes.put(trip, stopTimes.stopTimes); | ||
stopTimeByNetexId.putAll(stopTimes.stopTimeByNetexId); | ||
|
||
trips.add(trip); | ||
} | ||
|
||
// No trips successfully mapped | ||
if (trips.isEmpty()) { | ||
return result; | ||
return Optional.empty(); | ||
} | ||
|
||
// Create StopPattern from any trip (since they are part of the same JourneyPattern) | ||
StopPattern stopPattern = deduplicator.deduplicateObject( | ||
StopPattern.class, | ||
new StopPattern(result.tripStopTimes.get(trips.get(0))) | ||
new StopPattern(tripStopTimes.get(trips.get(0))) | ||
); | ||
|
||
var tripPatternModes = new HashSet<TransitMode>(); | ||
|
@@ -246,7 +245,7 @@ TripPatternMapperResult mapTripPattern(JourneyPattern_VersionStructure journeyPa | |
); | ||
} | ||
|
||
TripPatternBuilder tripPatternBuilder = TripPattern | ||
var tripPattern = TripPattern | ||
.of(idFactory.createId(journeyPattern.getId())) | ||
.withRoute(lookupRoute(journeyPattern)) | ||
.withStopPattern(stopPattern) | ||
|
@@ -256,30 +255,35 @@ TripPatternMapperResult mapTripPattern(JourneyPattern_VersionStructure journeyPa | |
.withName(journeyPattern.getName() == null ? "" : journeyPattern.getName().getValue()) | ||
.withHopGeometries( | ||
serviceLinkMapper.getGeometriesByJourneyPattern(journeyPattern, stopPattern) | ||
); | ||
|
||
TripPattern tripPattern = tripPatternBuilder.build(); | ||
createTripTimes(trips, tripPattern); | ||
|
||
result.tripPatterns.put(stopPattern, tripPattern); | ||
|
||
return result; | ||
) | ||
.build(); | ||
createTripTimes(trips, tripStopTimes).forEach(tripPattern::add); | ||
|
||
return Optional.of( | ||
new TripPatternMapperResult( | ||
tripPattern, | ||
scheduledStopPointsIndex, | ||
tripStopTimes, | ||
stopTimeByNetexId, | ||
tripOnServiceDates | ||
) | ||
); | ||
} | ||
|
||
private void mapDatedServiceJourney( | ||
private ArrayList<TripOnServiceDate> mapDatedServiceJourney( | ||
JourneyPattern_VersionStructure journeyPattern, | ||
ServiceJourney serviceJourney, | ||
Trip trip | ||
) { | ||
var tripsOnServiceDates = new ArrayList<TripOnServiceDate>(); | ||
if (datedServiceJourneysBySJId.containsKey(serviceJourney.getId())) { | ||
for (DatedServiceJourney datedServiceJourney : datedServiceJourneysBySJId.get( | ||
serviceJourney.getId() | ||
)) { | ||
result.tripOnServiceDates.add( | ||
mapDatedServiceJourney(journeyPattern, trip, datedServiceJourney) | ||
); | ||
tripsOnServiceDates.add(mapDatedServiceJourney(journeyPattern, trip, datedServiceJourney)); | ||
} | ||
} | ||
return tripsOnServiceDates; | ||
} | ||
|
||
private TripOnServiceDate mapDatedServiceJourney( | ||
|
@@ -360,9 +364,13 @@ private org.opentripplanner.transit.model.network.Route lookupRoute( | |
return otpRouteById.get(idFactory.createId(lineId)); | ||
} | ||
|
||
private void createTripTimes(List<Trip> trips, TripPattern tripPattern) { | ||
private List<TripTimes> createTripTimes( | ||
List<Trip> trips, | ||
Map<Trip, List<StopTime>> tripStopTimes | ||
) { | ||
var tripTimesResult = new ArrayList<TripTimes>(); | ||
for (Trip trip : trips) { | ||
List<StopTime> stopTimes = result.tripStopTimes.get(trip); | ||
List<StopTime> stopTimes = tripStopTimes.get(trip); | ||
if (stopTimes.isEmpty()) { | ||
issueStore.add( | ||
"TripWithoutTripTimes", | ||
|
@@ -372,12 +380,13 @@ private void createTripTimes(List<Trip> trips, TripPattern tripPattern) { | |
} else { | ||
try { | ||
TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, deduplicator); | ||
tripPattern.add(tripTimes); | ||
tripTimesResult.add(tripTimes); | ||
} catch (DataValidationException e) { | ||
issueStore.add(e.error()); | ||
} | ||
} | ||
} | ||
return tripTimesResult; | ||
} | ||
|
||
private Trip mapTrip( | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling a none-trivial code block inside a function chain is not clean. Consider making a private method for the code block or using Java language features like
if/for
.