From c83095bb1a9ac225fd37db08766b7974cb97c0b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Wed, 4 Mar 2026 14:20:14 -0500 Subject: [PATCH 1/4] Add "last trip stop" - https://github.com/mtransitapps/commons-java/pull/23 --- .../commons/data/RouteDirectionStop.java | 38 ++++++++++++++----- .../provider/GTFSProviderContract.java | 6 +++ .../provider/gtfs/GTFSPOIProvider.java | 4 ++ .../provider/gtfs/GTFSProviderDbHelper.java | 1 + .../provider/gtfs/GTFSRDSProvider.java | 6 +++ 5 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/mtransit/android/commons/data/RouteDirectionStop.java b/src/main/java/org/mtransit/android/commons/data/RouteDirectionStop.java index a031f512..9ba7752d 100644 --- a/src/main/java/org/mtransit/android/commons/data/RouteDirectionStop.java +++ b/src/main/java/org/mtransit/android/commons/data/RouteDirectionStop.java @@ -15,12 +15,14 @@ import org.json.JSONException; import org.json.JSONObject; import org.mtransit.android.commons.CursorExtKt; +import org.mtransit.android.commons.JSONUtils; import org.mtransit.android.commons.MTLog; import org.mtransit.android.commons.SpanUtils; import org.mtransit.android.commons.SqlUtils; import org.mtransit.android.commons.StringUtils; import org.mtransit.android.commons.data.DataSourceTypeId.DataSourceType; import org.mtransit.android.commons.provider.GTFSProviderContract; +import org.mtransit.commons.FeatureFlags; import org.mtransit.commons.GTFSCommons; import java.util.Arrays; @@ -43,6 +45,8 @@ public String getLogTag() { @NonNull private final Stop stop; private final boolean noPickup; + @Nullable + private final Boolean alwaysLastTripStop; @VisibleForTesting public RouteDirectionStop( @@ -52,7 +56,7 @@ public RouteDirectionStop( @NonNull Direction direction, @NonNull Stop stop, boolean noPickup) { - this(dataSourceTypeId, route, direction, stop, noPickup); + this(dataSourceTypeId, route, direction, stop, noPickup, null); } public RouteDirectionStop( @@ -60,13 +64,15 @@ public RouteDirectionStop( @NonNull Route route, @NonNull Direction direction, @NonNull Stop stop, - boolean noPickup + boolean noPickup, + @Nullable Boolean alwaysLastTripStop ) { super(route.getAuthority(), -1, dataSourceTypeId, POI.ITEM_VIEW_TYPE_ROUTE_DIRECTION_STOP, POI.ITEM_STATUS_TYPE_SCHEDULE, POI.ITEM_ACTION_TYPE_ROUTE_DIRECTION_STOP); this.route = route; this.direction = direction; this.stop = stop; this.noPickup = noPickup; + this.alwaysLastTripStop = alwaysLastTripStop; resetUUID(); } @@ -176,6 +182,7 @@ public String toStringShort() { private static final String JSON_DIRECTION = "trip"; // do not change to avoid breaking compat w/ old modules private static final String JSON_STOP = "stop"; private static final String JSON_NO_PICKUP = "decentOnly"; + private static final String JSON_ALWAYS_LAST_TRIP_STOP = "lastStop"; @Nullable @Override @@ -186,6 +193,9 @@ public JSONObject toJSON() { json.put(JSON_DIRECTION, Direction.toJSON(getDirection())); json.put(JSON_STOP, Stop.toJSON(getStop())); json.put(JSON_NO_PICKUP, isNoPickup()); + if (FeatureFlags.F_EXPORT_DIRECTION_STOP_LAST) { + json.put(JSON_ALWAYS_LAST_TRIP_STOP, isAlwaysLastTripStop()); + } DefaultPOI.toJSON(this, json); return json; } catch (JSONException jsone) { @@ -204,12 +214,13 @@ public POI fromJSON(@NonNull JSONObject json) { public static RouteDirectionStop fromJSONStatic(@NonNull JSONObject json) { try { final String authority = DefaultPOI.getAuthorityFromJSON(json); - final RouteDirectionStop rds = new RouteDirectionStop( // - DefaultPOI.getDSTypeIdFromJSON(json),// - Route.fromJSON(json.getJSONObject(JSON_ROUTE), authority), // - Direction.fromJSON(json.getJSONObject(JSON_DIRECTION), authority), // - Stop.fromJSON(json.getJSONObject(JSON_STOP)), // - json.getBoolean(JSON_NO_PICKUP) // + final RouteDirectionStop rds = new RouteDirectionStop( + DefaultPOI.getDSTypeIdFromJSON(json), + Route.fromJSON(json.getJSONObject(JSON_ROUTE), authority), + Direction.fromJSON(json.getJSONObject(JSON_DIRECTION), authority), + Stop.fromJSON(json.getJSONObject(JSON_STOP)), + json.getBoolean(JSON_NO_PICKUP), + JSONUtils.optBoolean(json, JSON_ALWAYS_LAST_TRIP_STOP) ); DefaultPOI.fromJSON(json, rds); return rds; @@ -244,6 +255,9 @@ public ContentValues toContentValues() { values.put(GTFSProviderContract.RouteDirectionStopColumns.T_STOP_K_ORIGINAL_ID_HASH, getStop().getOriginalIdHash()); // T_DIRECTION_STOPS_K_STOP_SEQUENCE not used in RouteDirectionStop class values.put(GTFSProviderContract.RouteDirectionStopColumns.T_DIRECTION_STOPS_K_NO_PICKUP, SqlUtils.toSQLBoolean(isNoPickup())); + if (FeatureFlags.F_EXPORT_DIRECTION_STOP_LAST) { + values.put(GTFSProviderContract.RouteDirectionStopColumns.T_DIRECTION_STOPS_K_ALWAYS_LAST_TRIP_STOP, SqlUtils.toSQLBoolean(isAlwaysLastTripStop())); + } return values; } @@ -282,7 +296,8 @@ public static RouteDirectionStop fromCursorStatic(@NonNull Cursor c, @NonNull St CursorExtKt.optIntNN(c, GTFSProviderContract.RouteDirectionStopColumns.T_STOP_K_ACCESSIBLE, Accessibility.DEFAULT), CursorExtKt.optInt(c, GTFSProviderContract.RouteDirectionStopColumns.T_STOP_K_ORIGINAL_ID_HASH, GTFSCommons.DEFAULT_ID_HASH) ), - CursorExtKt.getBoolean(c, GTFSProviderContract.RouteDirectionStopColumns.T_DIRECTION_STOPS_K_NO_PICKUP) + CursorExtKt.getBoolean(c, GTFSProviderContract.RouteDirectionStopColumns.T_DIRECTION_STOPS_K_NO_PICKUP), + CursorExtKt.getBoolean(c, GTFSProviderContract.RouteDirectionStopColumns.T_DIRECTION_STOPS_K_ALWAYS_LAST_TRIP_STOP) ); DefaultPOI.fromCursor(c, rds); return rds; @@ -297,6 +312,11 @@ public boolean isNoPickup() { return noPickup; } + public boolean isAlwaysLastTripStop() { + if (!FeatureFlags.F_EXPORT_DIRECTION_STOP_LAST) return false; + return alwaysLastTripStop != null && alwaysLastTripStop; + } + @NonNull @Override public String getAuthority() { diff --git a/src/main/java/org/mtransit/android/commons/provider/GTFSProviderContract.java b/src/main/java/org/mtransit/android/commons/provider/GTFSProviderContract.java index 82688fba..4c092dcd 100644 --- a/src/main/java/org/mtransit/android/commons/provider/GTFSProviderContract.java +++ b/src/main/java/org/mtransit/android/commons/provider/GTFSProviderContract.java @@ -6,6 +6,7 @@ import org.mtransit.android.commons.ArrayUtils; import org.mtransit.android.commons.provider.poi.POIProvider; +import org.mtransit.commons.FeatureFlags; import java.util.ArrayList; @@ -41,6 +42,9 @@ static String[] makePROJECTION_ROUTE_DIRECTION_STOP() { // projection.add(RouteDirectionStopColumns.T_DIRECTION_STOPS_K_STOP_SEQUENCE); projection.add(RouteDirectionStopColumns.T_DIRECTION_STOPS_K_NO_PICKUP); + if (FeatureFlags.F_EXPORT_DIRECTION_STOP_LAST) { + projection.add(RouteDirectionStopColumns.T_DIRECTION_STOPS_K_ALWAYS_LAST_TRIP_STOP); + } // projection.add(RouteDirectionStopColumns.T_STOP_K_ID); projection.add(RouteDirectionStopColumns.T_STOP_K_CODE); @@ -132,6 +136,7 @@ class RouteDirectionStopColumns { private static final String T_DIRECTION_STOPS = "trip_stops"; // do not change to avoid breaking compat w/ old modules public static final String T_DIRECTION_STOPS_K_STOP_SEQUENCE = T_DIRECTION_STOPS + "_" + "stop_sequence"; public static final String T_DIRECTION_STOPS_K_NO_PICKUP = T_DIRECTION_STOPS + "_" + "decent_only"; + public static final String T_DIRECTION_STOPS_K_ALWAYS_LAST_TRIP_STOP = T_DIRECTION_STOPS + "_" + "last_stop"; } class StopColumns { @@ -168,6 +173,7 @@ class DirectionStopColumns { private static final String T_DIRECTION_STOPS = "trip_stops"; // do not change to avoid breaking compat w/ old modules public static final String T_DIRECTION_STOPS_K_STOP_SEQUENCE = T_DIRECTION_STOPS + "_" + "stop_sequence"; public static final String T_DIRECTION_STOPS_K_NO_PICKUP = T_DIRECTION_STOPS + "_" + "decent_only"; + public static final String T_DIRECTION_STOPS_K_ALWAYS_LAST_TRIP_STOP = T_DIRECTION_STOPS + "_" + "last_stop"; } class TripColumns { diff --git a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSPOIProvider.java b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSPOIProvider.java index d759b5bd..17da21af 100644 --- a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSPOIProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSPOIProvider.java @@ -22,6 +22,7 @@ import org.mtransit.android.commons.provider.common.ProviderContract; import org.mtransit.android.commons.provider.poi.POIProvider; import org.mtransit.android.commons.provider.poi.POIProviderContract; +import org.mtransit.commons.FeatureFlags; import java.util.Map; @@ -207,6 +208,9 @@ private static ArrayMap getNewProjectionMap(String authority, in // sb.appendTableColumn(GTFSProviderDbHelper.T_DIRECTION_STOPS, GTFSProviderDbHelper.T_DIRECTION_STOPS_K_STOP_SEQUENCE, GTFSProviderContract.RouteDirectionStopColumns.T_DIRECTION_STOPS_K_STOP_SEQUENCE); sb.appendTableColumn(GTFSProviderDbHelper.T_DIRECTION_STOPS, GTFSProviderDbHelper.T_DIRECTION_STOPS_K_NO_PICKUP, GTFSProviderContract.RouteDirectionStopColumns.T_DIRECTION_STOPS_K_NO_PICKUP); + if (FeatureFlags.F_EXPORT_DIRECTION_STOP_LAST) { + sb.appendTableColumn(GTFSProviderDbHelper.T_DIRECTION_STOPS, GTFSProviderDbHelper.T_DIRECTION_STOPS_K_ALWAYS_LAST_TRIP_STOP, GTFSProviderContract.RouteDirectionStopColumns.T_DIRECTION_STOPS_K_ALWAYS_LAST_TRIP_STOP); + } // sb.appendTableColumn(GTFSProviderDbHelper.T_DIRECTION, GTFSProviderDbHelper.T_DIRECTION_K_ID, GTFSProviderContract.RouteDirectionStopColumns.T_DIRECTION_K_ID); sb.appendTableColumn(GTFSProviderDbHelper.T_DIRECTION, GTFSProviderDbHelper.T_DIRECTION_K_HEADSIGN_TYPE, GTFSProviderContract.RouteDirectionStopColumns.T_DIRECTION_K_HEADSIGN_TYPE); diff --git a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSProviderDbHelper.java b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSProviderDbHelper.java index ec1063b3..3f4c089a 100644 --- a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSProviderDbHelper.java +++ b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSProviderDbHelper.java @@ -89,6 +89,7 @@ public String getLogTag() { static final String T_DIRECTION_STOPS_K_STOP_ID = GTFSCommons.T_DIRECTION_STOPS_K_STOP_ID; static final String T_DIRECTION_STOPS_K_STOP_SEQUENCE = GTFSCommons.T_DIRECTION_STOPS_K_STOP_SEQUENCE; static final String T_DIRECTION_STOPS_K_NO_PICKUP = GTFSCommons.T_DIRECTION_STOPS_K_NO_PICKUP; + static final String T_DIRECTION_STOPS_K_ALWAYS_LAST_TRIP_STOP = GTFSCommons.T_DIRECTION_STOPS_K_ALWAYS_LAST_TRIP_STOP; private static final String T_DIRECTION_STOPS_SQL_CREATE = GTFSCommons.getT_DIRECTION_STOPS_SQL_CREATE(); private static final String T_DIRECTION_STOPS_SQL_INSERT = GTFSCommons.getT_DIRECTION_STOPS_SQL_INSERT(); private static final String T_DIRECTION_STOPS_SQL_DROP = GTFSCommons.getT_DIRECTION_STOPS_SQL_DROP(); diff --git a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRDSProvider.java b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRDSProvider.java index c5e21a8c..36c5487e 100644 --- a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRDSProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRDSProvider.java @@ -107,6 +107,9 @@ public static void append(@NonNull UriMatcher uriMatcher, @NonNull String author // sb.appendTableColumn(GTFSProviderDbHelper.T_DIRECTION_STOPS, GTFSProviderDbHelper.T_DIRECTION_STOPS_K_STOP_SEQUENCE, GTFSProviderContract.RouteDirectionStopColumns.T_DIRECTION_STOPS_K_STOP_SEQUENCE); sb.appendTableColumn(GTFSProviderDbHelper.T_DIRECTION_STOPS, GTFSProviderDbHelper.T_DIRECTION_STOPS_K_NO_PICKUP, GTFSProviderContract.RouteDirectionStopColumns.T_DIRECTION_STOPS_K_NO_PICKUP); + if (FeatureFlags.F_EXPORT_DIRECTION_STOP_LAST) { + sb.appendTableColumn(GTFSProviderDbHelper.T_DIRECTION_STOPS, GTFSProviderDbHelper.T_DIRECTION_STOPS_K_ALWAYS_LAST_TRIP_STOP, GTFSProviderContract.RouteDirectionStopColumns.T_DIRECTION_STOPS_K_ALWAYS_LAST_TRIP_STOP); + } // sb.appendTableColumn(GTFSProviderDbHelper.T_DIRECTION, GTFSProviderDbHelper.T_DIRECTION_K_ID, GTFSProviderContract.RouteDirectionStopColumns.T_DIRECTION_K_ID); sb.appendTableColumn(GTFSProviderDbHelper.T_DIRECTION, GTFSProviderDbHelper.T_DIRECTION_K_HEADSIGN_TYPE, GTFSProviderContract.RouteDirectionStopColumns.T_DIRECTION_K_HEADSIGN_TYPE); @@ -154,6 +157,9 @@ public static void append(@NonNull UriMatcher uriMatcher, @NonNull String author // sb.appendTableColumn(GTFSProviderDbHelper.T_DIRECTION_STOPS, GTFSProviderDbHelper.T_DIRECTION_STOPS_K_STOP_SEQUENCE, GTFSProviderContract.DirectionStopColumns.T_DIRECTION_STOPS_K_STOP_SEQUENCE); sb.appendTableColumn(GTFSProviderDbHelper.T_DIRECTION_STOPS, GTFSProviderDbHelper.T_DIRECTION_STOPS_K_NO_PICKUP, GTFSProviderContract.DirectionStopColumns.T_DIRECTION_STOPS_K_NO_PICKUP); + if (FeatureFlags.F_EXPORT_DIRECTION_STOP_LAST) { + sb.appendTableColumn(GTFSProviderDbHelper.T_DIRECTION_STOPS, GTFSProviderDbHelper.T_DIRECTION_STOPS_K_ALWAYS_LAST_TRIP_STOP, GTFSProviderContract.DirectionStopColumns.T_DIRECTION_STOPS_K_ALWAYS_LAST_TRIP_STOP); + } // sb.appendTableColumn(GTFSProviderDbHelper.T_DIRECTION, GTFSProviderDbHelper.T_DIRECTION_K_ID, GTFSProviderContract.DirectionStopColumns.T_DIRECTION_K_ID); sb.appendTableColumn(GTFSProviderDbHelper.T_DIRECTION, GTFSProviderDbHelper.T_DIRECTION_K_HEADSIGN_TYPE, GTFSProviderContract.DirectionStopColumns.T_DIRECTION_K_HEADSIGN_TYPE); From d75f9d61d89dc65c43c6c38f01a9e71d36ff9043 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Wed, 4 Mar 2026 14:34:28 -0500 Subject: [PATCH 2/4] PR comments --- .../java/org/mtransit/android/commons/CursorExt.kt | 13 +++++++++++++ .../android/commons/data/RouteDirectionStop.java | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mtransit/android/commons/CursorExt.kt b/src/main/java/org/mtransit/android/commons/CursorExt.kt index b10fb88c..a1b0a227 100644 --- a/src/main/java/org/mtransit/android/commons/CursorExt.kt +++ b/src/main/java/org/mtransit/android/commons/CursorExt.kt @@ -9,6 +9,19 @@ fun Cursor.optNotNull(columnIndex: Int) = columnIndex.takeIf { it >= 0 }?.takeIf // region Boolean +@JvmOverloads +fun Cursor.optBoolean(columnIndex: Int, fallback: Boolean? = null) = + optNotNull(columnIndex)?.let { getInt(it) }?.fromSQL() ?: fallback + +@JvmOverloads +fun Cursor.optBoolean(columnName: String, fallback: Boolean? = null) = + this.optBoolean(getColumnIndex(columnName), fallback) + +fun Cursor.optBooleanNN(columnIndex: Int, fallback: Boolean) = + optNotNull(columnIndex)?.let { getInt(it) }?.fromSQL() ?: fallback + +fun Cursor.optBooleanNN(columnName: String, fallback: Boolean) = this.optBooleanNN(getColumnIndex(columnName), fallback) + fun Cursor.getBoolean(columnName: String) = this.getInt(getColumnIndexOrThrow(columnName)).fromSQL() // endregion diff --git a/src/main/java/org/mtransit/android/commons/data/RouteDirectionStop.java b/src/main/java/org/mtransit/android/commons/data/RouteDirectionStop.java index 9ba7752d..e10d55e3 100644 --- a/src/main/java/org/mtransit/android/commons/data/RouteDirectionStop.java +++ b/src/main/java/org/mtransit/android/commons/data/RouteDirectionStop.java @@ -297,7 +297,7 @@ public static RouteDirectionStop fromCursorStatic(@NonNull Cursor c, @NonNull St CursorExtKt.optInt(c, GTFSProviderContract.RouteDirectionStopColumns.T_STOP_K_ORIGINAL_ID_HASH, GTFSCommons.DEFAULT_ID_HASH) ), CursorExtKt.getBoolean(c, GTFSProviderContract.RouteDirectionStopColumns.T_DIRECTION_STOPS_K_NO_PICKUP), - CursorExtKt.getBoolean(c, GTFSProviderContract.RouteDirectionStopColumns.T_DIRECTION_STOPS_K_ALWAYS_LAST_TRIP_STOP) + CursorExtKt.optBoolean(c, GTFSProviderContract.RouteDirectionStopColumns.T_DIRECTION_STOPS_K_ALWAYS_LAST_TRIP_STOP) ); DefaultPOI.fromCursor(c, rds); return rds; @@ -314,7 +314,7 @@ public boolean isNoPickup() { public boolean isAlwaysLastTripStop() { if (!FeatureFlags.F_EXPORT_DIRECTION_STOP_LAST) return false; - return alwaysLastTripStop != null && alwaysLastTripStop; + return Boolean.TRUE.equals(alwaysLastTripStop); } @NonNull From 7f0dfc2c9cce67e6a6214a511d890f988acbe7d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Wed, 4 Mar 2026 14:37:01 -0500 Subject: [PATCH 3/4] fix --- .../android/commons/data/RouteDirectionStop.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/org/mtransit/android/commons/data/RouteDirectionStop.java b/src/main/java/org/mtransit/android/commons/data/RouteDirectionStop.java index e10d55e3..a43a3d52 100644 --- a/src/main/java/org/mtransit/android/commons/data/RouteDirectionStop.java +++ b/src/main/java/org/mtransit/android/commons/data/RouteDirectionStop.java @@ -59,6 +59,17 @@ public RouteDirectionStop( this(dataSourceTypeId, route, direction, stop, noPickup, null); } + @VisibleForTesting + public RouteDirectionStop( + @DataSourceType int dataSourceTypeId, + @NonNull Route route, + @NonNull Direction direction, + @NonNull Stop stop, + boolean noPickup + ) { + this(dataSourceTypeId, route, direction, stop, noPickup, null); + } + public RouteDirectionStop( @DataSourceType int dataSourceTypeId, @NonNull Route route, From 2d83de560c6a6eceec7faae388700a1b028a6da8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Wed, 4 Mar 2026 14:39:50 -0500 Subject: [PATCH 4/4] cleanup --- .../org/mtransit/android/commons/data/RouteDirectionStop.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/mtransit/android/commons/data/RouteDirectionStop.java b/src/main/java/org/mtransit/android/commons/data/RouteDirectionStop.java index a43a3d52..bb0f14e6 100644 --- a/src/main/java/org/mtransit/android/commons/data/RouteDirectionStop.java +++ b/src/main/java/org/mtransit/android/commons/data/RouteDirectionStop.java @@ -56,7 +56,7 @@ public RouteDirectionStop( @NonNull Direction direction, @NonNull Stop stop, boolean noPickup) { - this(dataSourceTypeId, route, direction, stop, noPickup, null); + this(dataSourceTypeId, route, direction, stop, noPickup); } @VisibleForTesting