Skip to content

Conversation

@jessicaKoehnke
Copy link
Contributor

Summary

This is a refactoring to enable the runtime modification of transfers. PathTransfers are pulled out of the TransitRepository and moved into their own repository. Constrained transfers will be moved into the same module in a next step.

Issue

It's the first step for the plan detailed in issue #7074

Unit tests

No new features and no new tests

Documentation

Bumping the serialization version id

yes

@jessicaKoehnke jessicaKoehnke added !Technical Debt Improve code quality, no functional changes. +Bump Serialization Id Add this label if you want the serialization id automatically bumped after merging the PR +Skip Changelog This is not a relevant change for a product owner since last release. labels Dec 3, 2025
@t2gran
Copy link
Member

t2gran commented Dec 3, 2025

This is close, but only the root access points (TransferServiceand TransferRepository) should be visible in the org.opentripplanner.transfer pacage. I prefer creating interfaces for these. Look at org.opentripplanner.service.worldenvelope for how it is done.

@jessicaKoehnke jessicaKoehnke force-pushed the transfer-module-refactoring branch from 0abc62a to e1216dc Compare December 8, 2025 09:21
@codecov
Copy link

codecov bot commented Dec 8, 2025

Codecov Report

❌ Patch coverage is 65.97938% with 33 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.08%. Comparing base (6d59a52) to head (9342dc7).
⚠️ Report is 49 commits behind head on dev-2.x.

Files with missing lines Patch % Lines
...ner/standalone/configure/ConstructApplication.java 12.50% 7 Missing ⚠️
...rg/opentripplanner/ext/flex/FlexTransferIndex.java 77.27% 4 Missing and 1 partial ⚠️
...r/transfer/configure/TransferRepositoryModule.java 0.00% 5 Missing ⚠️
...entripplanner/apis/gtfs/datafetchers/StopImpl.java 0.00% 3 Missing ⚠️
...n/java/org/opentripplanner/standalone/OTPMain.java 0.00% 2 Missing ⚠️
...nner/transfer/configure/TransferServiceModule.java 0.00% 2 Missing ⚠️
...nner/transfer/internal/DefaultTransferService.java 75.00% 2 Missing ⚠️
...entripplanner/transfer/internal/TransferIndex.java 60.00% 2 Missing ⚠️
.../java/org/opentripplanner/ext/flex/FlexRouter.java 66.66% 1 Missing ⚠️
...entripplanner/apis/gtfs/GraphQLRequestContext.java 0.00% 1 Missing ⚠️
... and 3 more
Additional details and impacted files
@@              Coverage Diff              @@
##             dev-2.x    #7115      +/-   ##
=============================================
+ Coverage      72.05%   72.08%   +0.02%     
- Complexity     20679    20709      +30     
=============================================
  Files           2242     2250       +8     
  Lines          83801    83964     +163     
  Branches        8348     8347       -1     
=============================================
+ Hits           60385    60526     +141     
- Misses         20488    20510      +22     
  Partials        2928     2928              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jessicaKoehnke jessicaKoehnke force-pushed the transfer-module-refactoring branch from f4e5ee6 to 4dc2ef3 Compare December 8, 2025 10:46
@jessicaKoehnke jessicaKoehnke marked this pull request as ready for review December 8, 2025 11:01
@jessicaKoehnke jessicaKoehnke requested a review from a team as a code owner December 8, 2025 11:01
Comment on lines 23 to 30
if (OTPFeature.FlexRouting.isOn()) {
// Flex transfers should only use WALK mode transfers.
for (PathTransfer transfer : transferRepository.findTransfers(StreetMode.WALK)) {
transfersToStop.put(transfer.to, transfer);
transfersFromStop.put(transfer.from, transfer);
}
}
LOG.info("Transfer repository index init complete.");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the indices in this class are specific to flex I think that should be indicated in the names of the collections and the accessor methods. Now it looks like these are indices of all transfers to and from a specific stop.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was a bit hesitant on how to refactor this part. I wanted to make sure that the index is only created when flex is on, so that the memory is conserved when it's off. But you do make a good point. I tried doing it with another abstraction layer now. I also moved the flex index code back to the sandbox package, I do get some Class xxx is not exported from module 'org.opentripplanner.otp' warnings from IntelliJ now though. I'm not a hundred percent sure how the dependencies between the sandbox and the core features should be managed best. Is what I did now in line with what we want to aim for in the future?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using inheritance instead of an interface might be better for future proving this, I can change it if you think that is a better idea

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what the cleanest way would be to handle the flex specific indices and I don't really have a strong opinion. We do something similar for the timetable repository with FlexIndex, maybe you could mimic that? Perhaps @t2gran has an opinion?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have now changed it to using inheritance. This way any future additions to the normal TransferIndex will be automatically adopted by the FlexTransferIndex. Additionally I put the flex feature toggle condition into the application construction code. The business logic is now free of any sandbox dependencies. I think this is a good goal to aim for, that way we can create an application construction module, a sandbox module and several business logic modules in the future and we'd have no circular dependencies.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inheritance design looks good for now. It would be nice if we could document why we have this.

I am not sure why an extra index for the Flex is needed. The transit code have a similar index in the Raptor adaptor layer. It might be that the flex index support more stop types - if so this should be documented. Another difference is that the transit uses stopIndex for faster, more compact access. We need to decide what we do with the index in the Raptor adaptor layer as well. So, we can address both of these at the same time. I know there are differences for these indexes, but there is no documentation on "why" - this should be written in the JavaDoc on TransferIndex.

…refactoring

# Conflicts:
#	application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripIntegrationTest.java
#	application/src/ext/java/org/opentripplanner/ext/flex/FlexIndex.java
#	application/src/test/java/org/opentripplanner/transit/model/_data/TransitTestEnvironmentBuilder.java
* stable over the course of a request.
*/
public interface TransferService {
Collection<PathTransfer> findTransfersByStop(StopLocation stop);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method needs JavaDoc. It is not clear from the definition if the stop is the from or to stop, or both.

Comment on lines +32 to +34
if (!indexed) {
throw new IllegalStateException("The transfer index is needed but not initialized");
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The if().. can be extracted into a private method and reused below.

Comment on lines 23 to 30
if (OTPFeature.FlexRouting.isOn()) {
// Flex transfers should only use WALK mode transfers.
for (PathTransfer transfer : transferRepository.findTransfers(StreetMode.WALK)) {
transfersToStop.put(transfer.to, transfer);
transfersFromStop.put(transfer.from, transfer);
}
}
LOG.info("Transfer repository index init complete.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inheritance design looks good for now. It would be nice if we could document why we have this.

I am not sure why an extra index for the Flex is needed. The transit code have a similar index in the Raptor adaptor layer. It might be that the flex index support more stop types - if so this should be documented. Another difference is that the transit uses stopIndex for faster, more compact access. We need to decide what we do with the index in the Raptor adaptor layer as well. So, we can address both of these at the same time. I know there are differences for these indexes, but there is no documentation on "why" - this should be written in the JavaDoc on TransferIndex.

Comment on lines +39 to +45
/**
* @param toStop {@code StopLocation} that is set as a to-stop
* @return all walk mode {@code PathTransfer}s with the specified {@code StopLocation} as a
* to-stop
* @throws UnsupportedOperationException if flex routing is not activated
* @throws IllegalStateException if the index was not initialized
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/**
* @param toStop {@code StopLocation} that is set as a to-stop
* @return all walk mode {@code PathTransfer}s with the specified {@code StopLocation} as a
* to-stop
* @throws UnsupportedOperationException if flex routing is not activated
* @throws IllegalStateException if the index was not initialized
*/

This is not DRY, do not repeat information that is part of the method signature + return type. In this case I do not think there are any information in the doc at all.

In general we do not document "programming error" exceptions. The pre/post conditions are "runtime" documentation on how to use the methods. If you fail to do the index() the exception document the intention.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way, methods used as is by the service should be documented in the service interface, and it is not needed to document them twice - so leaving them without doc in the Repository is fine.

* Initialize the index. This needs to be called before
* {@link #findWalkTransfersFromStop(StopLocation)} and {@link #findWalkTransfersToStop(StopLocation)}
*/
void index();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Witch methods are backed by the the index is a implementation detail - an abstraction leak.

import org.opentripplanner.transfer.TransferRepository;
import org.opentripplanner.transit.model.site.StopLocation;

public class TransferIndex {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to see some doc on this interface:

  • Why do we needed it -> Flex only user
  • Why can we not reuse the Raptor Adaptor layer providing similar functionality?

If you find it hard to answer the above questions, you can just add TODOs instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

+Bump Serialization Id Add this label if you want the serialization id automatically bumped after merging the PR +Skip Changelog This is not a relevant change for a product owner since last release. !Technical Debt Improve code quality, no functional changes.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants