From 293beae66fa5fa0fccdcd2a6d361e83afc04c2a4 Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Fri, 2 Jan 2026 11:00:13 -0600 Subject: [PATCH 01/53] JSpecify --- pom.xml | 8 +++- .../java/com/cta4j/bus/client/BusClient.java | 21 +++++----- .../bus/client/internal/BusClientImpl.java | 40 +++++++------------ .../com/cta4j/train/client/TrainClient.java | 13 +++--- .../client/internal/TrainClientImpl.java | 23 +++++------ 5 files changed, 49 insertions(+), 56 deletions(-) diff --git a/pom.xml b/pom.xml index f3da73e..dc40ac8 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.cta4j cta4j-java-sdk - 3.0.4 + 3.0.5 21 21 @@ -54,6 +54,12 @@ 26.0.2-1 provided + + org.jspecify + jspecify + 1.0.0 + provided + diff --git a/src/main/java/com/cta4j/bus/client/BusClient.java b/src/main/java/com/cta4j/bus/client/BusClient.java index b9214e8..66c1208 100644 --- a/src/main/java/com/cta4j/bus/client/BusClient.java +++ b/src/main/java/com/cta4j/bus/client/BusClient.java @@ -3,6 +3,7 @@ import com.cta4j.bus.client.internal.BusClientImpl; import com.cta4j.bus.model.*; import com.cta4j.exception.Cta4jException; +import org.jspecify.annotations.NonNull; import java.util.List; import java.util.Optional; @@ -17,7 +18,7 @@ public interface BusClient { * @return a {@link List} of all bus routes * @throws Cta4jException if an error occurs while fetching the data */ - List getRoutes(); + @NonNull List getRoutes(); /** * Retrieves a {@link List} of directions for a specific bus route. @@ -27,7 +28,7 @@ public interface BusClient { * @throws NullPointerException if the specified bus route is {@code null} * @throws Cta4jException if an error occurs while fetching the data */ - List getDirections(String routeId); + @NonNull List getDirections(String routeId); /** * Retrieves a {@link List} of stops for a specific bus route and direction. @@ -38,7 +39,7 @@ public interface BusClient { * @throws NullPointerException if the specified bus route or direction is {@code null} * @throws Cta4jException if an error occurs while fetching the data */ - List getStops(String routeId, String direction); + @NonNull List getStops(String routeId, String direction); /** * Retrieves a {@link List} of upcoming arrivals for a specific bus route and stop. @@ -49,7 +50,7 @@ public interface BusClient { * @throws NullPointerException if the specified bus route or stop is {@code null} * @throws Cta4jException if an error occurs while fetching the data */ - List getStopArrivals(String routeId, String stopId); + @NonNull List getStopArrivals(String routeId, String stopId); /** * Retrieves a {@link List} of detours for a specific bus route and direction. @@ -60,7 +61,7 @@ public interface BusClient { * @throws NullPointerException if the specified bus route or direction is {@code null} * @throws Cta4jException if an error occurs while fetching the data */ - List getDetours(String routeId, String direction); + @NonNull List getDetours(String routeId, String direction); /** * Retrieves information about a specific bus by its ID. @@ -70,7 +71,7 @@ public interface BusClient { * @throws NullPointerException if the specified bus ID is {@code null} * @throws Cta4jException if an error occurs while fetching the data */ - Optional getBus(String id); + @NonNull Optional getBus(String id); /** * A builder for configuring and creating {@link BusClient} instances. @@ -85,7 +86,7 @@ interface Builder { * @return this {@link Builder} for method chaining * @throws NullPointerException if {@code host} is {@code null} */ - Builder host(String host); + @NonNull Builder host(String host); /** * Sets the API key used for authentication. @@ -94,14 +95,14 @@ interface Builder { * @return this {@link Builder} for method chaining * @throws NullPointerException if {@code apiKey} is {@code null} */ - Builder apiKey(String apiKey); + @NonNull Builder apiKey(String apiKey); /** * Builds a configured {@link BusClient} instance. * * @return a new {@link BusClient} */ - BusClient build(); + @NonNull BusClient build(); } /** @@ -109,7 +110,7 @@ interface Builder { * * @return a new {@link Builder} instance */ - static Builder builder() { + static @NonNull Builder builder() { return new BusClientImpl.BuilderImpl(); } } diff --git a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java index fd1683e..e0626a8 100644 --- a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java +++ b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java @@ -23,6 +23,7 @@ import com.cta4j.bus.external.vehicle.CtaVehicleBustimeResponse; import com.cta4j.bus.external.vehicle.CtaVehicleResponse; import com.cta4j.util.HttpUtils; +import org.jspecify.annotations.NonNull; import tools.jackson.core.JacksonException; import tools.jackson.databind.ObjectMapper; import org.apache.hc.core5.net.URIBuilder; @@ -34,36 +35,26 @@ @ApiStatus.Internal public final class BusClientImpl implements BusClient { - private final String host; - - private final String apiKey; - - private final ObjectMapper objectMapper; - private static final String DEFAULT_HOST = "ctabustracker.com"; - private static final String ROUTES_ENDPOINT = "/bustime/api/v3/getroutes"; - private static final String DIRECTIONS_ENDPOINT = "/bustime/api/v3/getdirections"; - private static final String STOPS_ENDPOINT = "/bustime/api/v3/getstops"; - private static final String PREDICTIONS_ENDPOINT = "/bustime/api/v3/getpredictions"; - private static final String DETOURS_ENDPOINT = "/bustime/api/v3/getdetours"; - private static final String VEHICLES_ENDPOINT = "/bustime/api/v3/getvehicles"; + private final String host; + private final String apiKey; + private final ObjectMapper objectMapper; + private BusClientImpl(String host, String apiKey) { this.host = Objects.requireNonNull(host); - this.apiKey = Objects.requireNonNull(apiKey); - this.objectMapper = new ObjectMapper(); } @Override - public List getRoutes() { + public @NonNull List getRoutes() { String url = new URIBuilder() .setScheme("https") .setHost(this.host) @@ -102,7 +93,7 @@ public List getRoutes() { } @Override - public List getDirections(String routeId) { + public @NonNull List getDirections(String routeId) { Objects.requireNonNull(routeId); String url = new URIBuilder() @@ -144,9 +135,8 @@ public List getDirections(String routeId) { } @Override - public List getStops(String routeId, String direction) { + public @NonNull List getStops(String routeId, String direction) { Objects.requireNonNull(routeId); - Objects.requireNonNull(direction); String url = new URIBuilder() @@ -189,9 +179,8 @@ public List getStops(String routeId, String direction) { } @Override - public List getStopArrivals(String routeId, String stopId) { + public @NonNull List getStopArrivals(String routeId, String stopId) { Objects.requireNonNull(routeId); - Objects.requireNonNull(stopId); String url = new URIBuilder() @@ -234,9 +223,8 @@ public List getStopArrivals(String routeId, String stopId) { } @Override - public List getDetours(String routeId, String direction) { + public @NonNull List getDetours(String routeId, String direction) { Objects.requireNonNull(routeId); - Objects.requireNonNull(direction); String url = new URIBuilder() @@ -320,7 +308,7 @@ private List getUpcomingBusArrivals(String id) { } @Override - public Optional getBus(String id) { + public @NonNull Optional getBus(String id) { Objects.requireNonNull(id); String url = new URIBuilder() @@ -391,21 +379,21 @@ public BuilderImpl() { } @Override - public Builder host(String host) { + public @NonNull Builder host(String host) { this.host = Objects.requireNonNull(host); return this; } @Override - public Builder apiKey(String apiKey) { + public @NonNull Builder apiKey(String apiKey) { this.apiKey = Objects.requireNonNull(apiKey); return this; } @Override - public BusClient build() { + public @NonNull BusClient build() { String finalHost = (this.host == null) ? DEFAULT_HOST : this.host; if (this.apiKey == null) { diff --git a/src/main/java/com/cta4j/train/client/TrainClient.java b/src/main/java/com/cta4j/train/client/TrainClient.java index 99d069a..260f885 100644 --- a/src/main/java/com/cta4j/train/client/TrainClient.java +++ b/src/main/java/com/cta4j/train/client/TrainClient.java @@ -4,6 +4,7 @@ import com.cta4j.exception.Cta4jException; import com.cta4j.train.model.StationArrival; import com.cta4j.train.model.Train; +import org.jspecify.annotations.NonNull; import java.util.List; import java.util.Optional; @@ -20,7 +21,7 @@ public interface TrainClient { * @throws NullPointerException if the specified station ID is {@code null} * @throws Cta4jException if an error occurs while fetching the data */ - List getStationArrivals(String stationId); + @NonNull List getStationArrivals(String stationId); /** * Retrieves information about a specific train by its run number. @@ -30,7 +31,7 @@ public interface TrainClient { * @throws NullPointerException if the specified run number is {@code null} * @throws Cta4jException if an error occurs while fetching the data */ - Optional getTrain(String run); + @NonNull Optional getTrain(String run); /** * A builder for configuring and creating {@link TrainClient} instances. @@ -45,7 +46,7 @@ interface Builder { * @return this {@link Builder} for method chaining * @throws NullPointerException if {@code host} is {@code null} */ - Builder host(String host); + @NonNull Builder host(String host); /** * Sets the API key used for authentication. @@ -54,14 +55,14 @@ interface Builder { * @return this {@link Builder} for method chaining * @throws NullPointerException if {@code apiKey} is {@code null} */ - Builder apiKey(String apiKey); + @NonNull Builder apiKey(String apiKey); /** * Builds a configured {@link TrainClient} instance. * * @return a new {@link TrainClient} */ - TrainClient build(); + @NonNull TrainClient build(); } /** @@ -69,7 +70,7 @@ interface Builder { * * @return a new {@link Builder} instance */ - static Builder builder() { + static @NonNull Builder builder() { return new TrainClientImpl.BuilderImpl(); } } diff --git a/src/main/java/com/cta4j/train/client/internal/TrainClientImpl.java b/src/main/java/com/cta4j/train/client/internal/TrainClientImpl.java index 6369ddf..22c9301 100644 --- a/src/main/java/com/cta4j/train/client/internal/TrainClientImpl.java +++ b/src/main/java/com/cta4j/train/client/internal/TrainClientImpl.java @@ -17,6 +17,7 @@ import com.cta4j.train.model.UpcomingTrainArrival; import com.cta4j.train.model.StationArrival; import com.cta4j.util.HttpUtils; +import org.jspecify.annotations.NonNull; import tools.jackson.core.JacksonException; import tools.jackson.databind.ObjectMapper; import org.apache.hc.core5.net.URIBuilder; @@ -28,18 +29,14 @@ @ApiStatus.Internal public final class TrainClientImpl implements TrainClient { - private final String host; - - private final String apiKey; - - private final ObjectMapper objectMapper; - private static final String DEFAULT_HOST = "lapi.transitchicago.com"; - private static final String ARRIVALS_ENDPOINT = "/api/1.0/ttarrivals.aspx"; - private static final String FOLLOW_ENDPOINT = "/api/1.0/ttfollow.aspx"; + private final String host; + private final String apiKey; + private final ObjectMapper objectMapper; + private TrainClientImpl(String host, String apiKey) { this.host = Objects.requireNonNull(host); @@ -49,7 +46,7 @@ private TrainClientImpl(String host, String apiKey) { } @Override - public List getStationArrivals(String stationId) { + public @NonNull List getStationArrivals(String stationId) { Objects.requireNonNull(stationId); String url = new URIBuilder() @@ -91,7 +88,7 @@ public List getStationArrivals(String stationId) { } @Override - public Optional getTrain(String run) { + public @NonNull Optional getTrain(String run) { Objects.requireNonNull(run); String url = new URIBuilder() @@ -161,21 +158,21 @@ public BuilderImpl() { } @Override - public Builder host(String host) { + public @NonNull Builder host(String host) { this.host = Objects.requireNonNull(host); return this; } @Override - public Builder apiKey(String apiKey) { + public @NonNull Builder apiKey(String apiKey) { this.apiKey = Objects.requireNonNull(apiKey); return this; } @Override - public TrainClient build() { + public @NonNull TrainClient build() { String finalHost = (this.host == null) ? DEFAULT_HOST : this.host; if (this.apiKey == null) { From eb5e3dcff6c8cd7643f7c66f1d91697c3bedae52 Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Fri, 2 Jan 2026 11:02:18 -0600 Subject: [PATCH 02/53] JSpecify --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad92d73..3faa40d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [3.0.5] - 2026-01-02 + +### Added + +- JSpecify `@NonNull` annotations to public API methods and classes for improved nullability contracts. +- `org.jspecify:jspecify:1.0.0` dependency for nullability annotations. + ## [3.0.4] - 2025-12-30 ### Changed From fdbba52ebba251b1654b9dfc1a0a8c1a2c39231f Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Fri, 2 Jan 2026 11:02:47 -0600 Subject: [PATCH 03/53] JSpecify --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3faa40d..46d9faf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -153,7 +153,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `TrainClient` class with methods to interact with CTA Train API. - `BusClient` class with methods to interact with CTA Bus API. -[Unreleased]: https://github.com/lbkulinski/cta4j-java-sdk/compare/v3.0.4...HEAD +[Unreleased]: https://github.com/lbkulinski/cta4j-java-sdk/compare/v3.0.5...HEAD +[3.0.5]: https://github.com/lbkulinski/cta4j-java-sdk/compare/v3.0.4...v3.0.5 [3.0.4]: https://github.com/lbkulinski/cta4j-java-sdk/compare/v3.0.3...v3.0.4 [3.0.3]: https://github.com/lbkulinski/cta4j-java-sdk/compare/v3.0.2...v3.0.3 [3.0.2]: https://github.com/lbkulinski/cta4j-java-sdk/compare/v3.0.1...v3.0.2 From 73c57a71fd0e5b87f6f43c01a80c07107ad38bf6 Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Fri, 2 Jan 2026 11:19:06 -0600 Subject: [PATCH 04/53] JSpecify --- .../java/com/cta4j/bus/client/BusClient.java | 23 +++++++++-------- .../bus/client/internal/BusClientImpl.java | 25 +++++++++++-------- .../com/cta4j/train/client/TrainClient.java | 15 +++++------ .../client/internal/TrainClientImpl.java | 17 +++++++------ 4 files changed, 44 insertions(+), 36 deletions(-) diff --git a/src/main/java/com/cta4j/bus/client/BusClient.java b/src/main/java/com/cta4j/bus/client/BusClient.java index 66c1208..207db8f 100644 --- a/src/main/java/com/cta4j/bus/client/BusClient.java +++ b/src/main/java/com/cta4j/bus/client/BusClient.java @@ -3,7 +3,7 @@ import com.cta4j.bus.client.internal.BusClientImpl; import com.cta4j.bus.model.*; import com.cta4j.exception.Cta4jException; -import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; import java.util.List; import java.util.Optional; @@ -11,6 +11,7 @@ /** * A client for interacting with the CTA Bus Tracker API. */ +@NullMarked public interface BusClient { /** * Retrieves a {@link List} of all bus routes. @@ -18,7 +19,7 @@ public interface BusClient { * @return a {@link List} of all bus routes * @throws Cta4jException if an error occurs while fetching the data */ - @NonNull List getRoutes(); + List getRoutes(); /** * Retrieves a {@link List} of directions for a specific bus route. @@ -28,7 +29,7 @@ public interface BusClient { * @throws NullPointerException if the specified bus route is {@code null} * @throws Cta4jException if an error occurs while fetching the data */ - @NonNull List getDirections(String routeId); + List getDirections(String routeId); /** * Retrieves a {@link List} of stops for a specific bus route and direction. @@ -39,7 +40,7 @@ public interface BusClient { * @throws NullPointerException if the specified bus route or direction is {@code null} * @throws Cta4jException if an error occurs while fetching the data */ - @NonNull List getStops(String routeId, String direction); + List getStops(String routeId, String direction); /** * Retrieves a {@link List} of upcoming arrivals for a specific bus route and stop. @@ -50,7 +51,7 @@ public interface BusClient { * @throws NullPointerException if the specified bus route or stop is {@code null} * @throws Cta4jException if an error occurs while fetching the data */ - @NonNull List getStopArrivals(String routeId, String stopId); + List getStopArrivals(String routeId, String stopId); /** * Retrieves a {@link List} of detours for a specific bus route and direction. @@ -61,7 +62,7 @@ public interface BusClient { * @throws NullPointerException if the specified bus route or direction is {@code null} * @throws Cta4jException if an error occurs while fetching the data */ - @NonNull List getDetours(String routeId, String direction); + List getDetours(String routeId, String direction); /** * Retrieves information about a specific bus by its ID. @@ -71,7 +72,7 @@ public interface BusClient { * @throws NullPointerException if the specified bus ID is {@code null} * @throws Cta4jException if an error occurs while fetching the data */ - @NonNull Optional getBus(String id); + Optional getBus(String id); /** * A builder for configuring and creating {@link BusClient} instances. @@ -86,7 +87,7 @@ interface Builder { * @return this {@link Builder} for method chaining * @throws NullPointerException if {@code host} is {@code null} */ - @NonNull Builder host(String host); + Builder host(String host); /** * Sets the API key used for authentication. @@ -95,14 +96,14 @@ interface Builder { * @return this {@link Builder} for method chaining * @throws NullPointerException if {@code apiKey} is {@code null} */ - @NonNull Builder apiKey(String apiKey); + Builder apiKey(String apiKey); /** * Builds a configured {@link BusClient} instance. * * @return a new {@link BusClient} */ - @NonNull BusClient build(); + BusClient build(); } /** @@ -110,7 +111,7 @@ interface Builder { * * @return a new {@link Builder} instance */ - static @NonNull Builder builder() { + static Builder builder() { return new BusClientImpl.BuilderImpl(); } } diff --git a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java index e0626a8..6195304 100644 --- a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java +++ b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java @@ -23,7 +23,8 @@ import com.cta4j.bus.external.vehicle.CtaVehicleBustimeResponse; import com.cta4j.bus.external.vehicle.CtaVehicleResponse; import com.cta4j.util.HttpUtils; -import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import tools.jackson.core.JacksonException; import tools.jackson.databind.ObjectMapper; import org.apache.hc.core5.net.URIBuilder; @@ -33,6 +34,7 @@ import java.util.Objects; import java.util.Optional; +@NullMarked @ApiStatus.Internal public final class BusClientImpl implements BusClient { private static final String DEFAULT_HOST = "ctabustracker.com"; @@ -54,7 +56,7 @@ private BusClientImpl(String host, String apiKey) { } @Override - public @NonNull List getRoutes() { + public List getRoutes() { String url = new URIBuilder() .setScheme("https") .setHost(this.host) @@ -93,7 +95,7 @@ private BusClientImpl(String host, String apiKey) { } @Override - public @NonNull List getDirections(String routeId) { + public List getDirections(String routeId) { Objects.requireNonNull(routeId); String url = new URIBuilder() @@ -135,7 +137,7 @@ private BusClientImpl(String host, String apiKey) { } @Override - public @NonNull List getStops(String routeId, String direction) { + public List getStops(String routeId, String direction) { Objects.requireNonNull(routeId); Objects.requireNonNull(direction); @@ -179,7 +181,7 @@ private BusClientImpl(String host, String apiKey) { } @Override - public @NonNull List getStopArrivals(String routeId, String stopId) { + public List getStopArrivals(String routeId, String stopId) { Objects.requireNonNull(routeId); Objects.requireNonNull(stopId); @@ -223,7 +225,7 @@ private BusClientImpl(String host, String apiKey) { } @Override - public @NonNull List getDetours(String routeId, String direction) { + public List getDetours(String routeId, String direction) { Objects.requireNonNull(routeId); Objects.requireNonNull(direction); @@ -308,7 +310,7 @@ private List getUpcomingBusArrivals(String id) { } @Override - public @NonNull Optional getBus(String id) { + public Optional getBus(String id) { Objects.requireNonNull(id); String url = new URIBuilder() @@ -368,32 +370,33 @@ private List getUpcomingBusArrivals(String id) { } public static final class BuilderImpl implements BusClient.Builder { + @Nullable private String host; + @Nullable private String apiKey; public BuilderImpl() { this.host = null; - this.apiKey = null; } @Override - public @NonNull Builder host(String host) { + public Builder host(String host) { this.host = Objects.requireNonNull(host); return this; } @Override - public @NonNull Builder apiKey(String apiKey) { + public Builder apiKey(String apiKey) { this.apiKey = Objects.requireNonNull(apiKey); return this; } @Override - public @NonNull BusClient build() { + public BusClient build() { String finalHost = (this.host == null) ? DEFAULT_HOST : this.host; if (this.apiKey == null) { diff --git a/src/main/java/com/cta4j/train/client/TrainClient.java b/src/main/java/com/cta4j/train/client/TrainClient.java index 260f885..35f1588 100644 --- a/src/main/java/com/cta4j/train/client/TrainClient.java +++ b/src/main/java/com/cta4j/train/client/TrainClient.java @@ -4,7 +4,7 @@ import com.cta4j.exception.Cta4jException; import com.cta4j.train.model.StationArrival; import com.cta4j.train.model.Train; -import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; import java.util.List; import java.util.Optional; @@ -12,6 +12,7 @@ /** * A client for interacting with the CTA Train Tracker API. */ +@NullMarked public interface TrainClient { /** * Retrieves a {@link List} of upcoming arrivals for a specific station. @@ -21,7 +22,7 @@ public interface TrainClient { * @throws NullPointerException if the specified station ID is {@code null} * @throws Cta4jException if an error occurs while fetching the data */ - @NonNull List getStationArrivals(String stationId); + List getStationArrivals(String stationId); /** * Retrieves information about a specific train by its run number. @@ -31,7 +32,7 @@ public interface TrainClient { * @throws NullPointerException if the specified run number is {@code null} * @throws Cta4jException if an error occurs while fetching the data */ - @NonNull Optional getTrain(String run); + Optional getTrain(String run); /** * A builder for configuring and creating {@link TrainClient} instances. @@ -46,7 +47,7 @@ interface Builder { * @return this {@link Builder} for method chaining * @throws NullPointerException if {@code host} is {@code null} */ - @NonNull Builder host(String host); + Builder host(String host); /** * Sets the API key used for authentication. @@ -55,14 +56,14 @@ interface Builder { * @return this {@link Builder} for method chaining * @throws NullPointerException if {@code apiKey} is {@code null} */ - @NonNull Builder apiKey(String apiKey); + Builder apiKey(String apiKey); /** * Builds a configured {@link TrainClient} instance. * * @return a new {@link TrainClient} */ - @NonNull TrainClient build(); + TrainClient build(); } /** @@ -70,7 +71,7 @@ interface Builder { * * @return a new {@link Builder} instance */ - static @NonNull Builder builder() { + static Builder builder() { return new TrainClientImpl.BuilderImpl(); } } diff --git a/src/main/java/com/cta4j/train/client/internal/TrainClientImpl.java b/src/main/java/com/cta4j/train/client/internal/TrainClientImpl.java index 22c9301..d5c2f16 100644 --- a/src/main/java/com/cta4j/train/client/internal/TrainClientImpl.java +++ b/src/main/java/com/cta4j/train/client/internal/TrainClientImpl.java @@ -17,7 +17,8 @@ import com.cta4j.train.model.UpcomingTrainArrival; import com.cta4j.train.model.StationArrival; import com.cta4j.util.HttpUtils; -import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import tools.jackson.core.JacksonException; import tools.jackson.databind.ObjectMapper; import org.apache.hc.core5.net.URIBuilder; @@ -27,6 +28,7 @@ import java.util.Objects; import java.util.Optional; +@NullMarked @ApiStatus.Internal public final class TrainClientImpl implements TrainClient { private static final String DEFAULT_HOST = "lapi.transitchicago.com"; @@ -46,7 +48,7 @@ private TrainClientImpl(String host, String apiKey) { } @Override - public @NonNull List getStationArrivals(String stationId) { + public List getStationArrivals(String stationId) { Objects.requireNonNull(stationId); String url = new URIBuilder() @@ -88,7 +90,7 @@ private TrainClientImpl(String host, String apiKey) { } @Override - public @NonNull Optional getTrain(String run) { + public Optional getTrain(String run) { Objects.requireNonNull(run); String url = new URIBuilder() @@ -147,32 +149,33 @@ private TrainClientImpl(String host, String apiKey) { } public static final class BuilderImpl implements TrainClient.Builder { + @Nullable private String host; + @Nullable private String apiKey; public BuilderImpl() { this.host = null; - this.apiKey = null; } @Override - public @NonNull Builder host(String host) { + public Builder host(String host) { this.host = Objects.requireNonNull(host); return this; } @Override - public @NonNull Builder apiKey(String apiKey) { + public Builder apiKey(String apiKey) { this.apiKey = Objects.requireNonNull(apiKey); return this; } @Override - public @NonNull TrainClient build() { + public TrainClient build() { String finalHost = (this.host == null) ? DEFAULT_HOST : this.host; if (this.apiKey == null) { From 90314fcd0ca95610ed1d9b36ec7f7348ed733a1d Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Fri, 2 Jan 2026 13:03:48 -0600 Subject: [PATCH 05/53] JSpecify + Model updates --- pom.xml | 20 +++- .../java/com/cta4j/bus/client/BusClient.java | 20 ++-- .../bus/client/internal/BusClientImpl.java | 80 ++++++++++++---- .../bus/external/vehicle/CtaVehicle.java | 65 +++++++++++-- .../bus/mapper/BusCoordinatesMapper.java | 93 ++++++++++--------- .../bus/mapper/BusPredictionTypeMapper.java | 6 +- .../com/cta4j/bus/mapper/DetourMapper.java | 5 +- .../com/cta4j/bus/mapper/RouteMapper.java | 6 +- .../cta4j/bus/mapper/StopArrivalMapper.java | 5 +- .../java/com/cta4j/bus/mapper/StopMapper.java | 5 +- .../bus/mapper/UpcomingBusArrivalMapper.java | 5 +- src/main/java/com/cta4j/bus/model/Bus.java | 39 +++++--- .../com/cta4j/bus/model/BusCoordinates.java | 19 +++- .../java/com/cta4j/bus/model/BusMetadata.java | 68 ++++++++++++++ src/main/java/com/cta4j/bus/model/Detour.java | 39 ++++++++ .../com/cta4j/bus/model/PassengerLoad.java | 4 + .../java/com/cta4j/bus/model/TransitMode.java | 4 + .../com/cta4j/train/client/TrainClient.java | 8 +- .../client/internal/TrainClientImpl.java | 32 +++++-- .../com/cta4j/train/mapper/RouteMapper.java | 6 +- .../train/mapper/StationArrivalMapper.java | 5 +- .../train/mapper/TrainCoordinatesMapper.java | 5 +- .../mapper/UpcomingTrainArrivalMapper.java | 26 ++++-- .../com/cta4j/train/model/StationArrival.java | 71 ++++++++++++++ .../java/com/cta4j/train/model/Train.java | 17 ++++ .../cta4j/train/model/TrainCoordinates.java | 15 ++- .../train/model/UpcomingTrainArrival.java | 48 +++++++++- .../java/com/cta4j/util/DateTimeUtils.java | 9 +- 28 files changed, 583 insertions(+), 142 deletions(-) create mode 100644 src/main/java/com/cta4j/bus/model/BusMetadata.java create mode 100644 src/main/java/com/cta4j/bus/model/PassengerLoad.java create mode 100644 src/main/java/com/cta4j/bus/model/TransitMode.java diff --git a/pom.xml b/pom.xml index dc40ac8..256cd3e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.cta4j cta4j-java-sdk - 3.0.5 + 4.0.0 21 21 @@ -60,9 +60,27 @@ 1.0.0 provided + + org.mapstruct + mapstruct + 1.6.3 + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.mapstruct + mapstruct-processor + 1.6.3 + + + + org.apache.maven.plugins maven-source-plugin diff --git a/src/main/java/com/cta4j/bus/client/BusClient.java b/src/main/java/com/cta4j/bus/client/BusClient.java index 207db8f..82eb213 100644 --- a/src/main/java/com/cta4j/bus/client/BusClient.java +++ b/src/main/java/com/cta4j/bus/client/BusClient.java @@ -1,7 +1,11 @@ package com.cta4j.bus.client; import com.cta4j.bus.client.internal.BusClientImpl; -import com.cta4j.bus.model.*; +import com.cta4j.bus.model.Bus; +import com.cta4j.bus.model.Detour; +import com.cta4j.bus.model.Route; +import com.cta4j.bus.model.Stop; +import com.cta4j.bus.model.StopArrival; import com.cta4j.exception.Cta4jException; import org.jspecify.annotations.NullMarked; @@ -26,7 +30,7 @@ public interface BusClient { * * @param routeId the ID of the bus route * @return a {@link List} of directions for the specified bus route - * @throws NullPointerException if the specified bus route is {@code null} + * @throws IllegalArgumentException if the specified bus route is {@code null} * @throws Cta4jException if an error occurs while fetching the data */ List getDirections(String routeId); @@ -37,7 +41,7 @@ public interface BusClient { * @param routeId the ID of the bus route * @param direction the direction of the bus route * @return a {@link List} of stops for the specified bus route and direction - * @throws NullPointerException if the specified bus route or direction is {@code null} + * @throws IllegalArgumentException if the specified bus route or direction is {@code null} * @throws Cta4jException if an error occurs while fetching the data */ List getStops(String routeId, String direction); @@ -48,7 +52,7 @@ public interface BusClient { * @param routeId the ID of the bus route * @param stopId the ID of the bus stop * @return a {@link List} of upcoming arrivals for the specified bus route and stop - * @throws NullPointerException if the specified bus route or stop is {@code null} + * @throws IllegalArgumentException if the specified bus route or stop is {@code null} * @throws Cta4jException if an error occurs while fetching the data */ List getStopArrivals(String routeId, String stopId); @@ -59,7 +63,7 @@ public interface BusClient { * @param routeId the ID of the bus route * @param direction the direction of the bus route * @return a {@link List} of detours for the specified bus route and direction - * @throws NullPointerException if the specified bus route or direction is {@code null} + * @throws IllegalArgumentException if the specified bus route or direction is {@code null} * @throws Cta4jException if an error occurs while fetching the data */ List getDetours(String routeId, String direction); @@ -69,7 +73,7 @@ public interface BusClient { * * @param id the ID of the bus * @return an {@link Optional} containing the bus information if found, or an empty {@link Optional} if not found - * @throws NullPointerException if the specified bus ID is {@code null} + * @throws IllegalArgumentException if the specified bus ID is {@code null} * @throws Cta4jException if an error occurs while fetching the data */ Optional getBus(String id); @@ -85,7 +89,7 @@ interface Builder { * * @param host the host * @return this {@link Builder} for method chaining - * @throws NullPointerException if {@code host} is {@code null} + * @throws IllegalArgumentException if {@code host} is {@code null} */ Builder host(String host); @@ -94,7 +98,7 @@ interface Builder { * * @param apiKey the API key * @return this {@link Builder} for method chaining - * @throws NullPointerException if {@code apiKey} is {@code null} + * @throws IllegalArgumentException if {@code apiKey} is {@code null} */ Builder apiKey(String apiKey); diff --git a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java index 6195304..f3adf54 100644 --- a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java +++ b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java @@ -1,8 +1,19 @@ package com.cta4j.bus.client.internal; import com.cta4j.bus.client.BusClient; -import com.cta4j.bus.mapper.*; -import com.cta4j.bus.model.*; +import com.cta4j.bus.mapper.BusCoordinatesMapper; +import com.cta4j.bus.mapper.DetourMapper; +import com.cta4j.bus.mapper.RouteMapper; +import com.cta4j.bus.mapper.StopArrivalMapper; +import com.cta4j.bus.mapper.StopMapper; +import com.cta4j.bus.mapper.UpcomingBusArrivalMapper; +import com.cta4j.bus.model.Bus; +import com.cta4j.bus.model.BusCoordinates; +import com.cta4j.bus.model.Detour; +import com.cta4j.bus.model.Route; +import com.cta4j.bus.model.Stop; +import com.cta4j.bus.model.StopArrival; +import com.cta4j.bus.model.UpcomingBusArrival; import com.cta4j.exception.Cta4jException; import com.cta4j.bus.external.detour.CtaDetour; import com.cta4j.bus.external.detour.CtaDetoursBustimeResponse; @@ -31,11 +42,11 @@ import org.jetbrains.annotations.ApiStatus; import java.util.List; -import java.util.Objects; import java.util.Optional; @NullMarked @ApiStatus.Internal +@SuppressWarnings("ConstantConditions") public final class BusClientImpl implements BusClient { private static final String DEFAULT_HOST = "ctabustracker.com"; private static final String ROUTES_ENDPOINT = "/bustime/api/v3/getroutes"; @@ -50,8 +61,16 @@ public final class BusClientImpl implements BusClient { private final ObjectMapper objectMapper; private BusClientImpl(String host, String apiKey) { - this.host = Objects.requireNonNull(host); - this.apiKey = Objects.requireNonNull(apiKey); + if (host == null) { + throw new IllegalArgumentException("host must not be null"); + } + + if (apiKey == null) { + throw new IllegalArgumentException("apiKey must not be null"); + } + + this.host = host; + this.apiKey = apiKey; this.objectMapper = new ObjectMapper(); } @@ -96,7 +115,9 @@ public List getRoutes() { @Override public List getDirections(String routeId) { - Objects.requireNonNull(routeId); + if (routeId == null) { + throw new IllegalArgumentException("routeId must not be null"); + } String url = new URIBuilder() .setScheme("https") @@ -138,8 +159,13 @@ public List getDirections(String routeId) { @Override public List getStops(String routeId, String direction) { - Objects.requireNonNull(routeId); - Objects.requireNonNull(direction); + if (routeId == null) { + throw new IllegalArgumentException("routeId must not be null"); + } + + if (direction == null) { + throw new IllegalArgumentException("direction must not be null"); + } String url = new URIBuilder() .setScheme("https") @@ -182,8 +208,13 @@ public List getStops(String routeId, String direction) { @Override public List getStopArrivals(String routeId, String stopId) { - Objects.requireNonNull(routeId); - Objects.requireNonNull(stopId); + if (routeId == null) { + throw new IllegalArgumentException("routeId must not be null"); + } + + if (stopId == null) { + throw new IllegalArgumentException("stopId must not be null"); + } String url = new URIBuilder() .setScheme("https") @@ -226,8 +257,13 @@ public List getStopArrivals(String routeId, String stopId) { @Override public List getDetours(String routeId, String direction) { - Objects.requireNonNull(routeId); - Objects.requireNonNull(direction); + if (routeId == null) { + throw new IllegalArgumentException("routeId must not be null"); + } + + if (direction == null) { + throw new IllegalArgumentException("direction must not be null"); + } String url = new URIBuilder() .setScheme("https") @@ -269,7 +305,9 @@ public List getDetours(String routeId, String direction) { } private List getUpcomingBusArrivals(String id) { - Objects.requireNonNull(id); + if (id == null) { + throw new IllegalArgumentException("id must not be null"); + } String url = new URIBuilder() .setScheme("https") @@ -311,7 +349,9 @@ private List getUpcomingBusArrivals(String id) { @Override public Optional getBus(String id) { - Objects.requireNonNull(id); + if (id == null) { + throw new IllegalArgumentException("id must not be null"); + } String url = new URIBuilder() .setScheme("https") @@ -383,14 +423,22 @@ public BuilderImpl() { @Override public Builder host(String host) { - this.host = Objects.requireNonNull(host); + if (host == null) { + throw new IllegalArgumentException("host must not be null"); + } + + this.host = host; return this; } @Override public Builder apiKey(String apiKey) { - this.apiKey = Objects.requireNonNull(apiKey); + if (apiKey == null) { + throw new IllegalArgumentException("apiKey must not be null"); + } + + this.apiKey = apiKey; return this; } diff --git a/src/main/java/com/cta4j/bus/external/vehicle/CtaVehicle.java b/src/main/java/com/cta4j/bus/external/vehicle/CtaVehicle.java index 79d8947..c1c0f41 100644 --- a/src/main/java/com/cta4j/bus/external/vehicle/CtaVehicle.java +++ b/src/main/java/com/cta4j/bus/external/vehicle/CtaVehicle.java @@ -2,27 +2,74 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked @ApiStatus.Internal @JsonIgnoreProperties(ignoreUnknown = true) public record CtaVehicle( String vid, - String tmstmp, - String lat, - String lon, - String hdg, - String pid, + + @Nullable + String rtpidatafeed, + + String tmpstmp, + + Double lat, + + Double lon, + + Integer hdg, + + Integer pid, + String rt, String des, - String pdist, + + Integer pdist, + + @Nullable + Byte stopstatus, + + @Nullable + Integer timepointid, + + @Nullable + String stopid, + + @Nullable + Integer sequence, + + @Nullable + Integer gtfsseq, + Boolean dly, + + @Nullable + String srvtmstmp, + + Integer spd, + + @Nullable + Integer blk, + + String tablockid, + String tatripid, + String origtatripno, - String tablockid, + String zone, - String mode, + + Byte mode, + String psgld, - String stst, + + @Nullable + Integer stst, + + @Nullable String stsd ) { } diff --git a/src/main/java/com/cta4j/bus/mapper/BusCoordinatesMapper.java b/src/main/java/com/cta4j/bus/mapper/BusCoordinatesMapper.java index 6908f9e..aad4b3b 100644 --- a/src/main/java/com/cta4j/bus/mapper/BusCoordinatesMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/BusCoordinatesMapper.java @@ -3,57 +3,62 @@ import com.cta4j.bus.external.vehicle.CtaVehicle; import com.cta4j.bus.model.BusCoordinates; import org.jetbrains.annotations.ApiStatus; +import org.mapstruct.Mapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.math.BigDecimal; -import java.util.Objects; +@Mapper @ApiStatus.Internal public final class BusCoordinatesMapper { - private static final Logger logger; - static { - logger = LoggerFactory.getLogger(BusCoordinatesMapper.class); - } - private BusCoordinatesMapper() { - throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); - } - - public static BusCoordinates fromExternal(CtaVehicle vehicle) { - Objects.requireNonNull(vehicle); - - BigDecimal latitude = null; - - if (vehicle.lat() != null) { - try { - latitude = new BigDecimal(vehicle.lat()); - } catch (NumberFormatException e) { - logger.warn("Invalid latitude value {}", vehicle.lat()); - } - } - - BigDecimal longitude = null; - - if (vehicle.lon() != null) { - try { - longitude = new BigDecimal(vehicle.lon()); - } catch (NumberFormatException e) { - logger.warn("Invalid longitude value {}", vehicle.lon()); - } - } - - Integer heading = null; - - if (vehicle.hdg() != null) { - try { - heading = Integer.parseInt(vehicle.hdg()); - } catch (NumberFormatException e) { - logger.warn("Invalid heading value {}", vehicle.hdg()); - } - } - - return new BusCoordinates(latitude, longitude, heading); - } +// private static final Logger logger; +// +// static { +// logger = LoggerFactory.getLogger(BusCoordinatesMapper.class); +// } +// +// private BusCoordinatesMapper() { +// throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); +// } +// +// public static BusCoordinates fromExternal(CtaVehicle vehicle) { +// if (vehicle == null) { +// throw new IllegalArgumentException("vehicle must not be null"); +// } +// +// BigDecimal latitude = null; +// +// if (vehicle.lat() != null) { +// try { +// latitude = new BigDecimal(vehicle.lat()); +// } catch (NumberFormatException e) { +// logger.warn("Invalid latitude value {}", vehicle.lat()); +// } +// } +// +// BigDecimal longitude = null; +// +// if (vehicle.lon() != null) { +// try { +// longitude = new BigDecimal(vehicle.lon()); +// } catch (NumberFormatException e) { +// logger.warn("Invalid longitude value {}", vehicle.lon()); +// } +// } +// +// Integer heading = null; +// +// if (vehicle.hdg() != null) { +// try { +// heading = Integer.parseInt(vehicle.hdg()); +// } catch (NumberFormatException e) { +// logger.warn("Invalid heading value {}", vehicle.hdg()); +// } +// } +// +// return new BusCoordinates(latitude, longitude, heading); +// } } diff --git a/src/main/java/com/cta4j/bus/mapper/BusPredictionTypeMapper.java b/src/main/java/com/cta4j/bus/mapper/BusPredictionTypeMapper.java index 994b283..3dd7080 100644 --- a/src/main/java/com/cta4j/bus/mapper/BusPredictionTypeMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/BusPredictionTypeMapper.java @@ -3,8 +3,6 @@ import com.cta4j.bus.model.BusPredictionType; import org.jetbrains.annotations.ApiStatus; -import java.util.Objects; - @ApiStatus.Internal public final class BusPredictionTypeMapper { private BusPredictionTypeMapper() { @@ -12,7 +10,9 @@ private BusPredictionTypeMapper() { } public static BusPredictionType fromExternal(String string) { - Objects.requireNonNull(string); + if (string == null) { + throw new IllegalArgumentException("string must not be null"); + } string = string.toUpperCase(); diff --git a/src/main/java/com/cta4j/bus/mapper/DetourMapper.java b/src/main/java/com/cta4j/bus/mapper/DetourMapper.java index 64bb4e7..7793633 100644 --- a/src/main/java/com/cta4j/bus/mapper/DetourMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/DetourMapper.java @@ -11,7 +11,6 @@ import java.time.Instant; import java.time.format.DateTimeParseException; import java.util.List; -import java.util.Objects; @ApiStatus.Internal public final class DetourMapper { @@ -26,7 +25,9 @@ private DetourMapper() { } public static Detour fromExternal(CtaDetour detour) { - Objects.requireNonNull(detour); + if (detour == null) { + throw new IllegalArgumentException("detour must not be null"); + } Boolean active = null; diff --git a/src/main/java/com/cta4j/bus/mapper/RouteMapper.java b/src/main/java/com/cta4j/bus/mapper/RouteMapper.java index e1901a8..e27aa29 100644 --- a/src/main/java/com/cta4j/bus/mapper/RouteMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/RouteMapper.java @@ -4,8 +4,6 @@ import com.cta4j.bus.model.Route; import org.jetbrains.annotations.ApiStatus; -import java.util.Objects; - @ApiStatus.Internal public final class RouteMapper { private RouteMapper() { @@ -13,7 +11,9 @@ private RouteMapper() { } public static Route fromExternal(CtaRoute route) { - Objects.requireNonNull(route); + if (route == null) { + throw new IllegalArgumentException("route must not be null"); + } return new Route( route.rt(), diff --git a/src/main/java/com/cta4j/bus/mapper/StopArrivalMapper.java b/src/main/java/com/cta4j/bus/mapper/StopArrivalMapper.java index 1fc2204..d149811 100644 --- a/src/main/java/com/cta4j/bus/mapper/StopArrivalMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/StopArrivalMapper.java @@ -11,7 +11,6 @@ import java.math.BigInteger; import java.time.Instant; import java.time.format.DateTimeParseException; -import java.util.Objects; @ApiStatus.Internal public final class StopArrivalMapper { @@ -26,7 +25,9 @@ private StopArrivalMapper() { } public static StopArrival fromExternal(CtaPredictionsPrd prd) { - Objects.requireNonNull(prd); + if (prd == null) { + throw new IllegalArgumentException("prd must not be null"); + } BusPredictionType type = null; diff --git a/src/main/java/com/cta4j/bus/mapper/StopMapper.java b/src/main/java/com/cta4j/bus/mapper/StopMapper.java index 71897be..46d2e6e 100644 --- a/src/main/java/com/cta4j/bus/mapper/StopMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/StopMapper.java @@ -7,7 +7,6 @@ import org.slf4j.LoggerFactory; import java.math.BigDecimal; -import java.util.Objects; @ApiStatus.Internal public final class StopMapper { @@ -22,7 +21,9 @@ private StopMapper() { } public static Stop fromExternal(CtaStop stop) { - Objects.requireNonNull(stop); + if (stop == null) { + throw new IllegalArgumentException("stop must not be null"); + } BigDecimal latitude = null; diff --git a/src/main/java/com/cta4j/bus/mapper/UpcomingBusArrivalMapper.java b/src/main/java/com/cta4j/bus/mapper/UpcomingBusArrivalMapper.java index 0d24a43..c0afe05 100644 --- a/src/main/java/com/cta4j/bus/mapper/UpcomingBusArrivalMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/UpcomingBusArrivalMapper.java @@ -11,7 +11,6 @@ import java.math.BigInteger; import java.time.Instant; import java.time.format.DateTimeParseException; -import java.util.Objects; @ApiStatus.Internal public final class UpcomingBusArrivalMapper { @@ -26,7 +25,9 @@ private UpcomingBusArrivalMapper() { } public static UpcomingBusArrival fromExternal(CtaPredictionsPrd prd) { - Objects.requireNonNull(prd); + if (prd == null) { + throw new IllegalArgumentException("prd must not be null"); + } BusPredictionType type = null; diff --git a/src/main/java/com/cta4j/bus/model/Bus.java b/src/main/java/com/cta4j/bus/model/Bus.java index 534e2b7..1e4c319 100644 --- a/src/main/java/com/cta4j/bus/model/Bus.java +++ b/src/main/java/com/cta4j/bus/model/Bus.java @@ -1,17 +1,10 @@ package com.cta4j.bus.model; -import java.util.List; - -/** - * A bus currently in service. - * - * @param id the unique identifier of the bus - * @param coordinates the coordinates and heading of the bus - * @param arrivals the list of upcoming bus arrivals for the bus - * @param route the route identifier the bus is serving - * @param destination the destination of the bus - * @param delayed whether the bus is currently delayed - */ +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +@SuppressWarnings("ConstantConditions") public record Bus( String id, @@ -21,8 +14,26 @@ public record Bus( BusCoordinates coordinates, - List arrivals, + boolean delayed, - Boolean delayed + @Nullable + BusMetadata metadata ) { + public Bus { + if (id == null) { + throw new IllegalArgumentException("id must not be null"); + } + + if (route == null) { + throw new IllegalArgumentException("route must not be null"); + } + + if (destination == null) { + throw new IllegalArgumentException("destination must not be null"); + } + + if (coordinates == null) { + throw new IllegalArgumentException("coordinates must not be null"); + } + } } diff --git a/src/main/java/com/cta4j/bus/model/BusCoordinates.java b/src/main/java/com/cta4j/bus/model/BusCoordinates.java index f1b3539..a0c56ca 100644 --- a/src/main/java/com/cta4j/bus/model/BusCoordinates.java +++ b/src/main/java/com/cta4j/bus/model/BusCoordinates.java @@ -1,5 +1,7 @@ package com.cta4j.bus.model; +import org.jspecify.annotations.NullMarked; + import java.math.BigDecimal; /** @@ -9,11 +11,26 @@ * @param longitude the longitude of the bus's current location * @param heading the heading of the bus in degrees (0-359) */ +@NullMarked +@SuppressWarnings("ConstantConditions") public record BusCoordinates( BigDecimal latitude, BigDecimal longitude, - Integer heading + int heading ) { + public BusCoordinates { + if (latitude == null) { + throw new IllegalArgumentException("latitude must not be null"); + } + + if (longitude == null) { + throw new IllegalArgumentException("longitude must not be null"); + } + + if ((heading < 0) || (heading > 359)) { + throw new IllegalArgumentException("heading must be between 0 and 359"); + } + } } diff --git a/src/main/java/com/cta4j/bus/model/BusMetadata.java b/src/main/java/com/cta4j/bus/model/BusMetadata.java new file mode 100644 index 0000000..cdaa708 --- /dev/null +++ b/src/main/java/com/cta4j/bus/model/BusMetadata.java @@ -0,0 +1,68 @@ +package com.cta4j.bus.model; + +import org.jspecify.annotations.Nullable; + +import java.time.Instant; +import java.time.LocalDate; + +public record BusMetadata( + @Nullable + String dataFeed, // rtpidatafeed + + @Nullable + Instant serverTimestamp, // srvtmstmp (if you parse it) + + @Nullable + Integer speed, // spd + + @Nullable + Integer patternId, // pid + + @Nullable + Integer distanceToPatternPoint, // pdist (or rename if you prefer) + + @Nullable + Byte stopStatus, // stopstatus + + @Nullable + Integer timepointId, // timepointid + + @Nullable + String stopId, // stopid + + @Nullable + Integer sequence, // sequence + + @Nullable + Integer gtfsSequence, // gtfsseq + + @Nullable + Integer block, // blk + + // CTA ops/schedule identifiers + @Nullable + String taBlockId, // tablockid + + @Nullable + String taTripId, // tatripid + + @Nullable + String originalTaTripNo, // origtatripno + + @Nullable + String zone, // zone + + @Nullable + TransitMode mode, // mode + + @Nullable + PassengerLoad passengerLoad, // psgld + + // schedule start + @Nullable + Integer scheduledStartSeconds, // stst + + @Nullable + LocalDate scheduledStartDate // stsd +) { +} diff --git a/src/main/java/com/cta4j/bus/model/Detour.java b/src/main/java/com/cta4j/bus/model/Detour.java index 81e0319..92fac3e 100644 --- a/src/main/java/com/cta4j/bus/model/Detour.java +++ b/src/main/java/com/cta4j/bus/model/Detour.java @@ -1,5 +1,7 @@ package com.cta4j.bus.model; +import org.jspecify.annotations.NullMarked; + import java.time.Instant; import java.util.List; @@ -14,6 +16,8 @@ * @param startTime the start time of the detour * @param endTime the end time of the detour */ +@NullMarked +@SuppressWarnings("ConstantConditions") public record Detour( String id, @@ -29,4 +33,39 @@ public record Detour( Instant endTime ) { + public Detour { + if (id == null) { + throw new IllegalArgumentException("id must not be null"); + } + + if (version == null) { + throw new IllegalArgumentException("version must not be null"); + } + + if (active == null) { + throw new IllegalArgumentException("active must not be null"); + } + + if (description == null) { + throw new IllegalArgumentException("description must not be null"); + } + + if (routeDirections == null) { + throw new IllegalArgumentException("routeDirections must not be null"); + } + + if (startTime == null) { + throw new IllegalArgumentException("startTime must not be null"); + } + + if (endTime == null) { + throw new IllegalArgumentException("endTime must not be null"); + } + + for (DetourRouteDirection routeDirection : routeDirections) { + if (routeDirection == null) { + throw new IllegalArgumentException("routeDirections must not contain null elements"); + } + } + } } diff --git a/src/main/java/com/cta4j/bus/model/PassengerLoad.java b/src/main/java/com/cta4j/bus/model/PassengerLoad.java new file mode 100644 index 0000000..19636ef --- /dev/null +++ b/src/main/java/com/cta4j/bus/model/PassengerLoad.java @@ -0,0 +1,4 @@ +package com.cta4j.bus.model; + +public enum PassengerLoad { +} diff --git a/src/main/java/com/cta4j/bus/model/TransitMode.java b/src/main/java/com/cta4j/bus/model/TransitMode.java new file mode 100644 index 0000000..06bb695 --- /dev/null +++ b/src/main/java/com/cta4j/bus/model/TransitMode.java @@ -0,0 +1,4 @@ +package com.cta4j.bus.model; + +public enum TransitMode { +} diff --git a/src/main/java/com/cta4j/train/client/TrainClient.java b/src/main/java/com/cta4j/train/client/TrainClient.java index 35f1588..61efe4b 100644 --- a/src/main/java/com/cta4j/train/client/TrainClient.java +++ b/src/main/java/com/cta4j/train/client/TrainClient.java @@ -19,7 +19,7 @@ public interface TrainClient { * * @param stationId the ID of the station * @return a {@link List} of upcoming arrivals for the specified station - * @throws NullPointerException if the specified station ID is {@code null} + * @throws IllegalArgumentException if the specified station ID is {@code null} * @throws Cta4jException if an error occurs while fetching the data */ List getStationArrivals(String stationId); @@ -29,7 +29,7 @@ public interface TrainClient { * * @param run the run number of the train * @return an {@link Optional} containing the train information if found, or an empty {@link Optional} if not found - * @throws NullPointerException if the specified run number is {@code null} + * @throws IllegalArgumentException if the specified run number is {@code null} * @throws Cta4jException if an error occurs while fetching the data */ Optional getTrain(String run); @@ -45,7 +45,7 @@ interface Builder { * * @param host the host * @return this {@link Builder} for method chaining - * @throws NullPointerException if {@code host} is {@code null} + * @throws IllegalArgumentException if {@code host} is {@code null} */ Builder host(String host); @@ -54,7 +54,7 @@ interface Builder { * * @param apiKey the API key * @return this {@link Builder} for method chaining - * @throws NullPointerException if {@code apiKey} is {@code null} + * @throws IllegalArgumentException if {@code apiKey} is {@code null} */ Builder apiKey(String apiKey); diff --git a/src/main/java/com/cta4j/train/client/internal/TrainClientImpl.java b/src/main/java/com/cta4j/train/client/internal/TrainClientImpl.java index d5c2f16..bf780a1 100644 --- a/src/main/java/com/cta4j/train/client/internal/TrainClientImpl.java +++ b/src/main/java/com/cta4j/train/client/internal/TrainClientImpl.java @@ -25,11 +25,11 @@ import org.jetbrains.annotations.ApiStatus; import java.util.List; -import java.util.Objects; import java.util.Optional; @NullMarked @ApiStatus.Internal +@SuppressWarnings("ConstantConditions") public final class TrainClientImpl implements TrainClient { private static final String DEFAULT_HOST = "lapi.transitchicago.com"; private static final String ARRIVALS_ENDPOINT = "/api/1.0/ttarrivals.aspx"; @@ -40,16 +40,24 @@ public final class TrainClientImpl implements TrainClient { private final ObjectMapper objectMapper; private TrainClientImpl(String host, String apiKey) { - this.host = Objects.requireNonNull(host); + if (host == null) { + throw new IllegalArgumentException("host must not be null"); + } - this.apiKey = Objects.requireNonNull(apiKey); + if (apiKey == null) { + throw new IllegalArgumentException("apiKey must not be null"); + } + this.host = host; + this.apiKey = apiKey; this.objectMapper = new ObjectMapper(); } @Override public List getStationArrivals(String stationId) { - Objects.requireNonNull(stationId); + if (stationId == null) { + throw new IllegalArgumentException("stationId must not be null"); + } String url = new URIBuilder() .setScheme("https") @@ -91,7 +99,9 @@ public List getStationArrivals(String stationId) { @Override public Optional getTrain(String run) { - Objects.requireNonNull(run); + if (run == null) { + throw new IllegalArgumentException("run must not be null"); + } String url = new URIBuilder() .setScheme("https") @@ -162,14 +172,22 @@ public BuilderImpl() { @Override public Builder host(String host) { - this.host = Objects.requireNonNull(host); + if (host == null) { + throw new IllegalArgumentException("host must not be null"); + } + + this.host = host; return this; } @Override public Builder apiKey(String apiKey) { - this.apiKey = Objects.requireNonNull(apiKey); + if (apiKey == null) { + throw new IllegalArgumentException("apiKey must not be null"); + } + + this.apiKey = apiKey; return this; } diff --git a/src/main/java/com/cta4j/train/mapper/RouteMapper.java b/src/main/java/com/cta4j/train/mapper/RouteMapper.java index 06f2c08..a70aee1 100644 --- a/src/main/java/com/cta4j/train/mapper/RouteMapper.java +++ b/src/main/java/com/cta4j/train/mapper/RouteMapper.java @@ -3,8 +3,6 @@ import com.cta4j.train.model.Route; import org.jetbrains.annotations.ApiStatus; -import java.util.Objects; - @ApiStatus.Internal public final class RouteMapper { private RouteMapper() { @@ -12,7 +10,9 @@ private RouteMapper() { } public static Route fromExternal(String string) { - Objects.requireNonNull(string); + if (string == null) { + throw new IllegalArgumentException("string must not be null"); + } string = string.toUpperCase(); diff --git a/src/main/java/com/cta4j/train/mapper/StationArrivalMapper.java b/src/main/java/com/cta4j/train/mapper/StationArrivalMapper.java index bf684b7..6b4e332 100644 --- a/src/main/java/com/cta4j/train/mapper/StationArrivalMapper.java +++ b/src/main/java/com/cta4j/train/mapper/StationArrivalMapper.java @@ -10,7 +10,6 @@ import java.math.BigDecimal; import java.time.Instant; -import java.util.Objects; @ApiStatus.Internal public final class StationArrivalMapper { @@ -25,7 +24,9 @@ private StationArrivalMapper() { } public static StationArrival fromExternal(CtaArrivalsEta eta) { - Objects.requireNonNull(eta); + if (eta == null) { + throw new IllegalArgumentException("eta must not be null"); + } Route route = null; diff --git a/src/main/java/com/cta4j/train/mapper/TrainCoordinatesMapper.java b/src/main/java/com/cta4j/train/mapper/TrainCoordinatesMapper.java index 9249b19..23b34db 100644 --- a/src/main/java/com/cta4j/train/mapper/TrainCoordinatesMapper.java +++ b/src/main/java/com/cta4j/train/mapper/TrainCoordinatesMapper.java @@ -7,7 +7,6 @@ import org.slf4j.LoggerFactory; import java.math.BigDecimal; -import java.util.Objects; @ApiStatus.Internal public final class TrainCoordinatesMapper { @@ -22,7 +21,9 @@ private TrainCoordinatesMapper() { } public static TrainCoordinates fromExternal(CtaFollowPosition position) { - Objects.requireNonNull(position); + if (position == null) { + throw new IllegalArgumentException("position must not be null"); + } BigDecimal latitude = null; diff --git a/src/main/java/com/cta4j/train/mapper/UpcomingTrainArrivalMapper.java b/src/main/java/com/cta4j/train/mapper/UpcomingTrainArrivalMapper.java index f01ba5b..d03b0a6 100644 --- a/src/main/java/com/cta4j/train/mapper/UpcomingTrainArrivalMapper.java +++ b/src/main/java/com/cta4j/train/mapper/UpcomingTrainArrivalMapper.java @@ -1,5 +1,6 @@ package com.cta4j.train.mapper; +import com.cta4j.exception.Cta4jException; import com.cta4j.train.external.follow.CtaFollowEta; import com.cta4j.train.model.Route; import com.cta4j.train.model.UpcomingTrainArrival; @@ -9,7 +10,6 @@ import org.slf4j.LoggerFactory; import java.time.Instant; -import java.util.Objects; @ApiStatus.Internal public final class UpcomingTrainArrivalMapper { @@ -24,16 +24,24 @@ private UpcomingTrainArrivalMapper() { } public static UpcomingTrainArrival fromExternal(CtaFollowEta eta) { - Objects.requireNonNull(eta); + if (eta == null) { + throw new IllegalArgumentException("eta must not be null"); + } - Route route = null; + if (eta.rt() == null) { + throw new Cta4jException("ETA route is missing"); + } - if (eta.rt() != null) { - try { - route = RouteMapper.fromExternal(eta.rt()); - } catch (IllegalArgumentException e) { - logger.warn("Invalid route {}", eta.rt()); - } + + + Route route; + + try { + route = RouteMapper.fromExternal(eta.rt()); + } catch (IllegalArgumentException e) { + String message = String.format("Invalid route value %s", eta.rt()); + + throw new Cta4jException(message, e); } Integer direction = null; diff --git a/src/main/java/com/cta4j/train/model/StationArrival.java b/src/main/java/com/cta4j/train/model/StationArrival.java index 6f1764d..2879a93 100644 --- a/src/main/java/com/cta4j/train/model/StationArrival.java +++ b/src/main/java/com/cta4j/train/model/StationArrival.java @@ -1,5 +1,8 @@ package com.cta4j.train.model; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + import java.math.BigDecimal; import java.time.Duration; import java.time.Instant; @@ -27,6 +30,8 @@ * @param longitude the longitude of the train's current location * @param heading the heading of the train in degrees (0-359) */ +@NullMarked +@SuppressWarnings("ConstantConditions") public record StationArrival( String stationId, @@ -58,14 +63,80 @@ public record StationArrival( Boolean faulted, + @Nullable String flags, + @Nullable BigDecimal latitude, + @Nullable BigDecimal longitude, + @Nullable Integer heading ) { + public StationArrival { + if (stationId == null) { + throw new IllegalArgumentException("stationId must not be null"); + } + + if (stopId == null) { + throw new IllegalArgumentException("stopId must not be null"); + } + + if (stationName == null) { + throw new IllegalArgumentException("stationName must not be null"); + } + + if (stopDescription == null) { + throw new IllegalArgumentException("stopDescription must not be null"); + } + + if (run == null) { + throw new IllegalArgumentException("run must not be null"); + } + + if (route == null) { + throw new IllegalArgumentException("route must not be null"); + } + + if (destinationStopId == null) { + throw new IllegalArgumentException("destinationStopId must not be null"); + } + + if (destinationName == null) { + throw new IllegalArgumentException("destinationName must not be null"); + } + + if (direction == null) { + throw new IllegalArgumentException("direction must not be null"); + } + + if (predictionTime == null) { + throw new IllegalArgumentException("predictionTime must not be null"); + } + + if (arrivalTime == null) { + throw new IllegalArgumentException("arrivalTime must not be null"); + } + + if (approaching == null) { + throw new IllegalArgumentException("approaching must not be null"); + } + + if (scheduled == null) { + throw new IllegalArgumentException("scheduled must not be null"); + } + + if (delayed == null) { + throw new IllegalArgumentException("delayed must not be null"); + } + + if (faulted == null) { + throw new IllegalArgumentException("faulted must not be null"); + } + } + /** * Calculates the estimated time of arrival (ETA) in minutes from the prediction time to the arrival time. If the * arrival time is before the prediction time, it returns 0. diff --git a/src/main/java/com/cta4j/train/model/Train.java b/src/main/java/com/cta4j/train/model/Train.java index fc23a58..2d24451 100644 --- a/src/main/java/com/cta4j/train/model/Train.java +++ b/src/main/java/com/cta4j/train/model/Train.java @@ -1,5 +1,8 @@ package com.cta4j.train.model; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + import java.util.List; /** @@ -8,9 +11,23 @@ * @param coordinates the coordinates and heading of the train * @param arrivals the list of upcoming train arrivals for the train */ +@NullMarked +@SuppressWarnings("ConstantConditions") public record Train( + @Nullable TrainCoordinates coordinates, List arrivals ) { + public Train { + if (arrivals == null) { + throw new IllegalArgumentException("arrivals must not be null"); + } + + for (UpcomingTrainArrival arrival : arrivals) { + if (arrival == null) { + throw new IllegalArgumentException("arrivals must not contain null elements"); + } + } + } } diff --git a/src/main/java/com/cta4j/train/model/TrainCoordinates.java b/src/main/java/com/cta4j/train/model/TrainCoordinates.java index 6bf63e2..e2be5d8 100644 --- a/src/main/java/com/cta4j/train/model/TrainCoordinates.java +++ b/src/main/java/com/cta4j/train/model/TrainCoordinates.java @@ -1,5 +1,7 @@ package com.cta4j.train.model; +import org.jspecify.annotations.NullMarked; + import java.math.BigDecimal; /** @@ -9,11 +11,22 @@ * @param longitude the longitude of the train's current location * @param heading the heading of the train in degrees (0-359) */ +@NullMarked +@SuppressWarnings("ConstantConditions") public record TrainCoordinates( BigDecimal latitude, BigDecimal longitude, - Integer heading + int heading ) { + public TrainCoordinates { + if (latitude == null) { + throw new IllegalArgumentException("latitude must not be null"); + } + + if (longitude == null) { + throw new IllegalArgumentException("longitude must not be null"); + } + } } diff --git a/src/main/java/com/cta4j/train/model/UpcomingTrainArrival.java b/src/main/java/com/cta4j/train/model/UpcomingTrainArrival.java index d86c516..08bfa5a 100644 --- a/src/main/java/com/cta4j/train/model/UpcomingTrainArrival.java +++ b/src/main/java/com/cta4j/train/model/UpcomingTrainArrival.java @@ -1,5 +1,8 @@ package com.cta4j.train.model; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + import java.time.Duration; import java.time.Instant; @@ -23,6 +26,8 @@ * @param faulted whether there is a fault affecting the train * @param flags additional flags associated with the prediction */ +@NullMarked +@SuppressWarnings("ConstantConditions") public record UpcomingTrainArrival( String stationId, @@ -46,16 +51,51 @@ public record UpcomingTrainArrival( Instant arrivalTime, - Boolean approaching, + boolean approaching, - Boolean scheduled, + boolean scheduled, - Boolean delayed, + boolean delayed, - Boolean faulted, + boolean faulted, + @Nullable String flags ) { + public UpcomingTrainArrival { + if (stationId == null) { + throw new IllegalArgumentException("stationId must not be null"); + } + + if (stopId == null) { + throw new IllegalArgumentException("stopId must not be null"); + } + + if (stationName == null) { + throw new IllegalArgumentException("stationName must not be null"); + } + + if (stopDescription == null) { + throw new IllegalArgumentException("stopDescription must not be null"); + } + + if (run == null) { + throw new IllegalArgumentException("run must not be null"); + } + + if (route == null) { + throw new IllegalArgumentException("route must not be null"); + } + + if (predictionTime == null) { + throw new IllegalArgumentException("predictionTime must not be null"); + } + + if (arrivalTime == null) { + throw new IllegalArgumentException("arrivalTime must not be null"); + } + } + /** * Calculates the estimated time of arrival (ETA) in minutes from the prediction time to the arrival time. If the * arrival time is before the prediction time, it returns 0. diff --git a/src/main/java/com/cta4j/util/DateTimeUtils.java b/src/main/java/com/cta4j/util/DateTimeUtils.java index 45715af..6b10fe7 100644 --- a/src/main/java/com/cta4j/util/DateTimeUtils.java +++ b/src/main/java/com/cta4j/util/DateTimeUtils.java @@ -6,7 +6,6 @@ import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; -import java.util.Objects; @ApiStatus.Internal public final class DateTimeUtils { @@ -15,7 +14,9 @@ private DateTimeUtils() { } public static Instant parseTrainTimestamp(String timestamp) { - Objects.requireNonNull(timestamp); + if (timestamp == null) { + throw new IllegalArgumentException("timestamp must not be null"); + } ZoneId chicagoId = ZoneId.of("America/Chicago"); @@ -25,7 +26,9 @@ public static Instant parseTrainTimestamp(String timestamp) { } public static Instant parseBusTimestamp(String timestamp) { - Objects.requireNonNull(timestamp); + if (timestamp == null) { + throw new IllegalArgumentException("timestamp must not be null"); + } DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm"); From a5c1da096670af72a81b9bc124e2390fa71473ff Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Fri, 2 Jan 2026 13:46:55 -0600 Subject: [PATCH 06/53] JSpecify + Model updates --- CHANGELOG.md | 6 +-- .../bus/external/vehicle/CtaVehicle.java | 3 +- .../java/com/cta4j/bus/mapper/BusMapper.java | 40 +++++++++++++++++++ .../com/cta4j/bus/model/PassengerLoad.java | 15 +++++++ .../java/com/cta4j/bus/model/TransitMode.java | 25 ++++++++++++ 5 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/cta4j/bus/mapper/BusMapper.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 46d9faf..8780663 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [3.0.5] - 2026-01-02 +## [4.0.0] - 2026-01-02 ### Added @@ -153,8 +153,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `TrainClient` class with methods to interact with CTA Train API. - `BusClient` class with methods to interact with CTA Bus API. -[Unreleased]: https://github.com/lbkulinski/cta4j-java-sdk/compare/v3.0.5...HEAD -[3.0.5]: https://github.com/lbkulinski/cta4j-java-sdk/compare/v3.0.4...v3.0.5 +[Unreleased]: https://github.com/lbkulinski/cta4j-java-sdk/compare/v4.0.0...HEAD +[4.0.0]: https://github.com/lbkulinski/cta4j-java-sdk/compare/v3.0.4...v4.0.0 [3.0.4]: https://github.com/lbkulinski/cta4j-java-sdk/compare/v3.0.3...v3.0.4 [3.0.3]: https://github.com/lbkulinski/cta4j-java-sdk/compare/v3.0.2...v3.0.3 [3.0.2]: https://github.com/lbkulinski/cta4j-java-sdk/compare/v3.0.1...v3.0.2 diff --git a/src/main/java/com/cta4j/bus/external/vehicle/CtaVehicle.java b/src/main/java/com/cta4j/bus/external/vehicle/CtaVehicle.java index c1c0f41..49a6bb8 100644 --- a/src/main/java/com/cta4j/bus/external/vehicle/CtaVehicle.java +++ b/src/main/java/com/cta4j/bus/external/vehicle/CtaVehicle.java @@ -25,6 +25,7 @@ public record CtaVehicle( Integer pid, String rt, + String des, Integer pdist, @@ -62,7 +63,7 @@ public record CtaVehicle( String zone, - Byte mode, + Integer mode, String psgld, diff --git a/src/main/java/com/cta4j/bus/mapper/BusMapper.java b/src/main/java/com/cta4j/bus/mapper/BusMapper.java new file mode 100644 index 0000000..e014e1b --- /dev/null +++ b/src/main/java/com/cta4j/bus/mapper/BusMapper.java @@ -0,0 +1,40 @@ +package com.cta4j.bus.mapper; + +import com.cta4j.bus.external.vehicle.CtaVehicle; +import com.cta4j.bus.model.Bus; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper +public interface BusMapper { + @Mapping(source = "vid", target = "id") + @Mapping(source = "rt", target = "route") + @Mapping(source = "des", target = "destination") + + @Mapping(source = "lat", target = "coordinates.latitude") + @Mapping(source = "lon", target = "coordinates.longitude") + @Mapping(source = "hdg", target = "coordinates.heading") + + @Mapping(source = "dly", target = "delayed") + + @Mapping(source = "rtpidatafeed", target = "metadata.dataFeed") + @Mapping(source = "srvtmstmp", target = "metadata.serverTimestamp") + @Mapping(source = "spd", target = "metadata.speed") + @Mapping(source = "pid", target = "metadata.patternId") + @Mapping(source = "pdist", target = "metadata.distanceToPatternPoint") + @Mapping(source = "stopstatus", target = "metadata.stopStatus") + @Mapping(source = "timepointid", target = "metadata.timepointId") + @Mapping(source = "stopid", target = "metadata.stopId") + @Mapping(source = "sequence", target = "metadata.sequence") + @Mapping(source = "gtfsseq", target = "metadata.gtfsSequence") + @Mapping(source = "blk", target = "metadata.block") + @Mapping(source = "tablockid", target = "metadata.taBlockId") + @Mapping(source = "tatripid", target = "metadata.taTripId") + @Mapping(source = "origtatripno", target = "metadata.originalTaTripNo") + @Mapping(source = "zone", target = "metadata.zone") + @Mapping(source = "mode", target = "metadata.mode") + @Mapping(source = "psgld", target = "metadata.passengerLoad") + @Mapping(source = "stst", target = "metadata.scheduledStartSeconds") + @Mapping(source = "stsd", target = "metadata.scheduledStartDate") + Bus toDomain(CtaVehicle vehicle); +} diff --git a/src/main/java/com/cta4j/bus/model/PassengerLoad.java b/src/main/java/com/cta4j/bus/model/PassengerLoad.java index 19636ef..a117c16 100644 --- a/src/main/java/com/cta4j/bus/model/PassengerLoad.java +++ b/src/main/java/com/cta4j/bus/model/PassengerLoad.java @@ -1,4 +1,19 @@ package com.cta4j.bus.model; public enum PassengerLoad { + FULL, + HALF_EMPTY, + EMPTY, + UNKNOWN; + + public static PassengerLoad fromString(String value) { + String upperCaseValue = value.toUpperCase(); + + return switch (upperCaseValue) { + case "FULL" -> FULL; + case "HALF_EMPTY" -> HALF_EMPTY; + case "EMPTY" -> EMPTY; + default -> UNKNOWN; + }; + } } diff --git a/src/main/java/com/cta4j/bus/model/TransitMode.java b/src/main/java/com/cta4j/bus/model/TransitMode.java index 06bb695..5a479bb 100644 --- a/src/main/java/com/cta4j/bus/model/TransitMode.java +++ b/src/main/java/com/cta4j/bus/model/TransitMode.java @@ -1,4 +1,29 @@ package com.cta4j.bus.model; public enum TransitMode { + NONE(0), + + BUS(1), + + FERRY(2), + + RAIL(3), + + PEOPLE_MOVER(4); + + private final int code; + + TransitMode(int code) { + this.code = code; + } + + public static TransitMode fromCode(int code) { + for (TransitMode transitMode : TransitMode.values()) { + if (transitMode.code == code) { + return transitMode; + } + } + + throw new IllegalArgumentException("Invalid transit mode: " + code); + } } From 521574f87b0ce9bbb3f26d0d44244378b53334c3 Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sun, 4 Jan 2026 20:44:15 -0600 Subject: [PATCH 07/53] JSpecify + Model updates --- .../bus/client/internal/BusClientImpl.java | 63 ++---------------- .../bus/mapper/BusCoordinatesMapper.java | 64 ------------------- .../java/com/cta4j/bus/mapper/BusMapper.java | 3 - .../java/com/cta4j/bus/model/BusMetadata.java | 40 ++++++------ 4 files changed, 24 insertions(+), 146 deletions(-) delete mode 100644 src/main/java/com/cta4j/bus/mapper/BusCoordinatesMapper.java diff --git a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java index f3adf54..1adfc6c 100644 --- a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java +++ b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java @@ -1,19 +1,16 @@ package com.cta4j.bus.client.internal; import com.cta4j.bus.client.BusClient; -import com.cta4j.bus.mapper.BusCoordinatesMapper; +import com.cta4j.bus.mapper.BusMapper; import com.cta4j.bus.mapper.DetourMapper; import com.cta4j.bus.mapper.RouteMapper; import com.cta4j.bus.mapper.StopArrivalMapper; import com.cta4j.bus.mapper.StopMapper; -import com.cta4j.bus.mapper.UpcomingBusArrivalMapper; import com.cta4j.bus.model.Bus; -import com.cta4j.bus.model.BusCoordinates; import com.cta4j.bus.model.Detour; import com.cta4j.bus.model.Route; import com.cta4j.bus.model.Stop; import com.cta4j.bus.model.StopArrival; -import com.cta4j.bus.model.UpcomingBusArrival; import com.cta4j.exception.Cta4jException; import com.cta4j.bus.external.detour.CtaDetour; import com.cta4j.bus.external.detour.CtaDetoursBustimeResponse; @@ -36,6 +33,7 @@ import com.cta4j.util.HttpUtils; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; +import org.mapstruct.factory.Mappers; import tools.jackson.core.JacksonException; import tools.jackson.databind.ObjectMapper; import org.apache.hc.core5.net.URIBuilder; @@ -59,6 +57,7 @@ public final class BusClientImpl implements BusClient { private final String host; private final String apiKey; private final ObjectMapper objectMapper; + private final BusMapper busMapper; private BusClientImpl(String host, String apiKey) { if (host == null) { @@ -72,6 +71,7 @@ private BusClientImpl(String host, String apiKey) { this.host = host; this.apiKey = apiKey; this.objectMapper = new ObjectMapper(); + this.busMapper = Mappers.getMapper(BusMapper.class); } @Override @@ -304,49 +304,6 @@ public List getDetours(String routeId, String direction) { .toList(); } - private List getUpcomingBusArrivals(String id) { - if (id == null) { - throw new IllegalArgumentException("id must not be null"); - } - - String url = new URIBuilder() - .setScheme("https") - .setHost(this.host) - .setPath(PREDICTIONS_ENDPOINT) - .addParameter("vid", id) - .addParameter("key", this.apiKey) - .addParameter("format", "json") - .toString(); - - String response = HttpUtils.get(url); - - CtaPredictionsResponse predictionsResponse; - - try { - predictionsResponse = this.objectMapper.readValue(response, CtaPredictionsResponse.class); - } catch (JacksonException e) { - String message = "Failed to parse response from %s".formatted(PREDICTIONS_ENDPOINT); - - throw new Cta4jException(message, e); - } - - CtaPredictionsBustimeResponse bustimeResponse = predictionsResponse.bustimeResponse(); - - if (bustimeResponse == null) { - throw new Cta4jException("Invalid response from %s".formatted(PREDICTIONS_ENDPOINT)); - } - - List prd = bustimeResponse.prd(); - - if ((prd == null) || prd.isEmpty()) { - return List.of(); - } - - return prd.stream() - .map(UpcomingBusArrivalMapper::fromExternal) - .toList(); - } - @Override public Optional getBus(String id) { if (id == null) { @@ -394,17 +351,7 @@ public Optional getBus(String id) { CtaVehicle vehicle = vehicles.getFirst(); - String route = vehicle.rt(); - - String destination = vehicle.des(); - - BusCoordinates coordinates = BusCoordinatesMapper.fromExternal(vehicle); - - List upcomingArrivals = this.getUpcomingBusArrivals(id); - - Boolean delayed = vehicle.dly(); - - Bus bus = new Bus(id, route, destination, coordinates, upcomingArrivals, delayed); + Bus bus = this.busMapper.toDomain(vehicle); return Optional.of(bus); } diff --git a/src/main/java/com/cta4j/bus/mapper/BusCoordinatesMapper.java b/src/main/java/com/cta4j/bus/mapper/BusCoordinatesMapper.java deleted file mode 100644 index aad4b3b..0000000 --- a/src/main/java/com/cta4j/bus/mapper/BusCoordinatesMapper.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.cta4j.bus.mapper; - -import com.cta4j.bus.external.vehicle.CtaVehicle; -import com.cta4j.bus.model.BusCoordinates; -import org.jetbrains.annotations.ApiStatus; -import org.mapstruct.Mapper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.math.BigDecimal; - -@Mapper -@ApiStatus.Internal -public final class BusCoordinatesMapper { - - -// private static final Logger logger; -// -// static { -// logger = LoggerFactory.getLogger(BusCoordinatesMapper.class); -// } -// -// private BusCoordinatesMapper() { -// throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); -// } -// -// public static BusCoordinates fromExternal(CtaVehicle vehicle) { -// if (vehicle == null) { -// throw new IllegalArgumentException("vehicle must not be null"); -// } -// -// BigDecimal latitude = null; -// -// if (vehicle.lat() != null) { -// try { -// latitude = new BigDecimal(vehicle.lat()); -// } catch (NumberFormatException e) { -// logger.warn("Invalid latitude value {}", vehicle.lat()); -// } -// } -// -// BigDecimal longitude = null; -// -// if (vehicle.lon() != null) { -// try { -// longitude = new BigDecimal(vehicle.lon()); -// } catch (NumberFormatException e) { -// logger.warn("Invalid longitude value {}", vehicle.lon()); -// } -// } -// -// Integer heading = null; -// -// if (vehicle.hdg() != null) { -// try { -// heading = Integer.parseInt(vehicle.hdg()); -// } catch (NumberFormatException e) { -// logger.warn("Invalid heading value {}", vehicle.hdg()); -// } -// } -// -// return new BusCoordinates(latitude, longitude, heading); -// } -} diff --git a/src/main/java/com/cta4j/bus/mapper/BusMapper.java b/src/main/java/com/cta4j/bus/mapper/BusMapper.java index e014e1b..46edd46 100644 --- a/src/main/java/com/cta4j/bus/mapper/BusMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/BusMapper.java @@ -10,13 +10,10 @@ public interface BusMapper { @Mapping(source = "vid", target = "id") @Mapping(source = "rt", target = "route") @Mapping(source = "des", target = "destination") - @Mapping(source = "lat", target = "coordinates.latitude") @Mapping(source = "lon", target = "coordinates.longitude") @Mapping(source = "hdg", target = "coordinates.heading") - @Mapping(source = "dly", target = "delayed") - @Mapping(source = "rtpidatafeed", target = "metadata.dataFeed") @Mapping(source = "srvtmstmp", target = "metadata.serverTimestamp") @Mapping(source = "spd", target = "metadata.speed") diff --git a/src/main/java/com/cta4j/bus/model/BusMetadata.java b/src/main/java/com/cta4j/bus/model/BusMetadata.java index cdaa708..c55c28d 100644 --- a/src/main/java/com/cta4j/bus/model/BusMetadata.java +++ b/src/main/java/com/cta4j/bus/model/BusMetadata.java @@ -7,62 +7,60 @@ public record BusMetadata( @Nullable - String dataFeed, // rtpidatafeed + String dataFeed, @Nullable - Instant serverTimestamp, // srvtmstmp (if you parse it) + Instant serverTimestamp, @Nullable - Integer speed, // spd + Integer speed, @Nullable - Integer patternId, // pid + Integer patternId, @Nullable - Integer distanceToPatternPoint, // pdist (or rename if you prefer) + Integer distanceToPatternPoint, @Nullable - Byte stopStatus, // stopstatus + Byte stopStatus, @Nullable - Integer timepointId, // timepointid + Integer timepointId, @Nullable - String stopId, // stopid + String stopId, @Nullable - Integer sequence, // sequence + Integer sequence, @Nullable - Integer gtfsSequence, // gtfsseq + Integer gtfsSequence, @Nullable - Integer block, // blk + Integer block, - // CTA ops/schedule identifiers @Nullable - String taBlockId, // tablockid + String taBlockId, @Nullable - String taTripId, // tatripid + String taTripId, @Nullable - String originalTaTripNo, // origtatripno + String originalTaTripNo, @Nullable - String zone, // zone + String zone, @Nullable - TransitMode mode, // mode + TransitMode mode, @Nullable - PassengerLoad passengerLoad, // psgld + PassengerLoad passengerLoad, - // schedule start @Nullable - Integer scheduledStartSeconds, // stst + Integer scheduledStartSeconds, @Nullable - LocalDate scheduledStartDate // stsd + LocalDate scheduledStartDate ) { } From b797ec7d5f9ff8e356a630a25fd59f2de4f5344d Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sat, 10 Jan 2026 12:52:11 -0600 Subject: [PATCH 08/53] JSpecify + Model updates --- README.md | 6 +- .../java/com/cta4j/bus/client/BusClient.java | 14 +++- .../bus/client/internal/BusClientImpl.java | 62 ++++++++++++-- .../prediction/CtaPredictionsPrd.java | 17 ++-- .../com/cta4j/bus/external/stop/CtaStop.java | 10 ++- .../com/cta4j/bus/mapper/ArrivalMapper.java | 36 ++++++++ .../java/com/cta4j/bus/mapper/BusMapper.java | 2 + .../bus/mapper/BusPredictionTypeMapper.java | 29 ------- .../com/cta4j/bus/mapper/RouteMapper.java | 24 ++---- .../cta4j/bus/mapper/StopArrivalMapper.java | 82 ------------------- .../java/com/cta4j/bus/mapper/StopMapper.java | 60 +++----------- .../bus/mapper/UpcomingBusArrivalMapper.java | 82 ------------------- .../java/com/cta4j/bus/model/Arrival.java | 40 +++++++++ .../com/cta4j/bus/model/ArrivalMetadata.java | 28 +++++++ ...redictionType.java => PredictionType.java} | 2 +- src/main/java/com/cta4j/bus/model/Route.java | 8 +- src/main/java/com/cta4j/bus/model/Stop.java | 15 +++- .../java/com/cta4j/bus/model/StopArrival.java | 59 ------------- .../cta4j/bus/model/UpcomingBusArrival.java | 59 ------------- 19 files changed, 238 insertions(+), 397 deletions(-) create mode 100644 src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java delete mode 100644 src/main/java/com/cta4j/bus/mapper/BusPredictionTypeMapper.java delete mode 100644 src/main/java/com/cta4j/bus/mapper/StopArrivalMapper.java delete mode 100644 src/main/java/com/cta4j/bus/mapper/UpcomingBusArrivalMapper.java create mode 100644 src/main/java/com/cta4j/bus/model/Arrival.java create mode 100644 src/main/java/com/cta4j/bus/model/ArrivalMetadata.java rename src/main/java/com/cta4j/bus/model/{BusPredictionType.java => PredictionType.java} (88%) delete mode 100644 src/main/java/com/cta4j/bus/model/StopArrival.java delete mode 100644 src/main/java/com/cta4j/bus/model/UpcomingBusArrival.java diff --git a/README.md b/README.md index 6796ab8..6f1dc00 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ implementation("com.cta4j:cta4j-java-sdk:3.0.4") import com.cta4j.train.client.TrainClient; public final class Application { - public static void main(String[] args) { + static void main(String[] args) { TrainClient trainClient = TrainClient.builder() .apiKey("TRAIN_API_KEY") .build(); @@ -85,12 +85,12 @@ public final class Application { import com.cta4j.bus.client.BusClient; public final class Application { - public static void main(String[] args) { + static void main(String[] args) { BusClient busClient = BusClient.builder() .apiKey("BUS_API_KEY") .build(); - busClient.getStopArrivals("22", "1828") + busClient.getArrivalsByStop("22", "1828") .stream() .map(arrival -> String.format( "%s-bound bus is arriving at %s in %d minutes", diff --git a/src/main/java/com/cta4j/bus/client/BusClient.java b/src/main/java/com/cta4j/bus/client/BusClient.java index 82eb213..6a02105 100644 --- a/src/main/java/com/cta4j/bus/client/BusClient.java +++ b/src/main/java/com/cta4j/bus/client/BusClient.java @@ -1,11 +1,11 @@ package com.cta4j.bus.client; import com.cta4j.bus.client.internal.BusClientImpl; +import com.cta4j.bus.model.Arrival; import com.cta4j.bus.model.Bus; import com.cta4j.bus.model.Detour; import com.cta4j.bus.model.Route; import com.cta4j.bus.model.Stop; -import com.cta4j.bus.model.StopArrival; import com.cta4j.exception.Cta4jException; import org.jspecify.annotations.NullMarked; @@ -55,7 +55,17 @@ public interface BusClient { * @throws IllegalArgumentException if the specified bus route or stop is {@code null} * @throws Cta4jException if an error occurs while fetching the data */ - List getStopArrivals(String routeId, String stopId); + List getArrivalsByStop(String routeId, String stopId); + + /** + * Retrieves a {@link List} of upcoming arrivals for a specific bus by its ID. + * + * @param busId the ID of the bus + * @return a {@link List} of upcoming arrivals for the specified bus + * @throws IllegalArgumentException if the specified bus ID is {@code null} + * @throws Cta4jException if an error occurs while fetching the data + */ + List getArrivalsByBus(String busId); /** * Retrieves a {@link List} of detours for a specific bus route and direction. diff --git a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java index 1adfc6c..8824f39 100644 --- a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java +++ b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java @@ -1,16 +1,16 @@ package com.cta4j.bus.client.internal; import com.cta4j.bus.client.BusClient; +import com.cta4j.bus.mapper.ArrivalMapper; import com.cta4j.bus.mapper.BusMapper; import com.cta4j.bus.mapper.DetourMapper; import com.cta4j.bus.mapper.RouteMapper; -import com.cta4j.bus.mapper.StopArrivalMapper; import com.cta4j.bus.mapper.StopMapper; +import com.cta4j.bus.model.Arrival; import com.cta4j.bus.model.Bus; import com.cta4j.bus.model.Detour; import com.cta4j.bus.model.Route; import com.cta4j.bus.model.Stop; -import com.cta4j.bus.model.StopArrival; import com.cta4j.exception.Cta4jException; import com.cta4j.bus.external.detour.CtaDetour; import com.cta4j.bus.external.detour.CtaDetoursBustimeResponse; @@ -57,6 +57,9 @@ public final class BusClientImpl implements BusClient { private final String host; private final String apiKey; private final ObjectMapper objectMapper; + private final ArrivalMapper arrivalMapper; + private final RouteMapper routeMapper; + private final StopMapper stopMapper; private final BusMapper busMapper; private BusClientImpl(String host, String apiKey) { @@ -71,6 +74,9 @@ private BusClientImpl(String host, String apiKey) { this.host = host; this.apiKey = apiKey; this.objectMapper = new ObjectMapper(); + this.arrivalMapper = Mappers.getMapper(ArrivalMapper.class); + this.routeMapper = Mappers.getMapper(RouteMapper.class); + this.stopMapper = Mappers.getMapper(StopMapper.class); this.busMapper = Mappers.getMapper(BusMapper.class); } @@ -109,7 +115,7 @@ public List getRoutes() { } return routes.stream() - .map(RouteMapper::fromExternal) + .map(this.routeMapper::toDomain) .toList(); } @@ -202,12 +208,12 @@ public List getStops(String routeId, String direction) { } return stops.stream() - .map(StopMapper::fromExternal) + .map(this.stopMapper::toDomain) .toList(); } @Override - public List getStopArrivals(String routeId, String stopId) { + public List getArrivalsByStop(String routeId, String stopId) { if (routeId == null) { throw new IllegalArgumentException("routeId must not be null"); } @@ -251,7 +257,51 @@ public List getStopArrivals(String routeId, String stopId) { } return prd.stream() - .map(StopArrivalMapper::fromExternal) + .map(this.arrivalMapper::toDomain) + .toList(); + } + + @Override + public List getArrivalsByBus(String busId) { + if (busId == null) { + throw new IllegalArgumentException("busId must not be null"); + } + + String url = new URIBuilder() + .setScheme("https") + .setHost(this.host) + .setPath(PREDICTIONS_ENDPOINT) + .addParameter("vid", busId) + .addParameter("key", this.apiKey) + .addParameter("format", "json") + .toString(); + + String response = HttpUtils.get(url); + + CtaPredictionsResponse predictionsResponse; + + try { + predictionsResponse = this.objectMapper.readValue(response, CtaPredictionsResponse.class); + } catch (JacksonException e) { + String message = "Failed to parse response from %s".formatted(PREDICTIONS_ENDPOINT); + + throw new Cta4jException(message, e); + } + + CtaPredictionsBustimeResponse bustimeResponse = predictionsResponse.bustimeResponse(); + + if (bustimeResponse == null) { + throw new Cta4jException("Invalid response from %s".formatted(PREDICTIONS_ENDPOINT)); + } + + List prd = bustimeResponse.prd(); + + if ((prd == null) || prd.isEmpty()) { + return List.of(); + } + + return prd.stream() + .map(this.arrivalMapper::toDomain) .toList(); } diff --git a/src/main/java/com/cta4j/bus/external/prediction/CtaPredictionsPrd.java b/src/main/java/com/cta4j/bus/external/prediction/CtaPredictionsPrd.java index 0333a4e..1cd22b1 100644 --- a/src/main/java/com/cta4j/bus/external/prediction/CtaPredictionsPrd.java +++ b/src/main/java/com/cta4j/bus/external/prediction/CtaPredictionsPrd.java @@ -8,25 +8,26 @@ public record CtaPredictionsPrd( String tmstmp, String typ, - String stpnm, String stpid, - String vid, - String dstp, + String stpnm, + int vid, + int dstp, String rt, String rtdd, String rtdir, String des, String prdtm, - String dly, - String dyn, + Boolean dly, + int dyn, String tablockid, String tatripid, String origtatripno, - String prdctdn, String zone, String psgld, - String stst, + int gtfsseq, + String nbus, + Integer stst, String stsd, - String flagstop + int flagstop ) { } diff --git a/src/main/java/com/cta4j/bus/external/stop/CtaStop.java b/src/main/java/com/cta4j/bus/external/stop/CtaStop.java index 2d0fec9..8a8d345 100644 --- a/src/main/java/com/cta4j/bus/external/stop/CtaStop.java +++ b/src/main/java/com/cta4j/bus/external/stop/CtaStop.java @@ -3,12 +3,18 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.jetbrains.annotations.ApiStatus; +import java.util.List; + @ApiStatus.Internal @JsonIgnoreProperties(ignoreUnknown = true) public record CtaStop( String stpid, String stpnm, - String lat, - String lon + double lat, + double lon, + List dtradd, + List dtrrem, + Integer gtfsseq, + Boolean ada ) { } diff --git a/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java b/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java new file mode 100644 index 0000000..1b6bd32 --- /dev/null +++ b/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java @@ -0,0 +1,36 @@ +package com.cta4j.bus.mapper; + +import com.cta4j.bus.external.prediction.CtaPredictionsPrd; +import com.cta4j.bus.model.Arrival; +import org.jetbrains.annotations.ApiStatus; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper +@ApiStatus.Internal +public interface ArrivalMapper { + @Mapping(source = "typ", target = "predictionType") + @Mapping(source = "stpid", target = "stopId") + @Mapping(source = "stpnm", target = "stopName") + @Mapping(source = "vid", target = "vehicleId") + @Mapping(source = "dstp", target = "distanceToStop") + @Mapping(source = "rt", target = "route") + @Mapping(source = "rtdd", target = "routeDesignator") + @Mapping(source = "rtdir", target = "routeDirection") + @Mapping(source = "des", target = "destination") + @Mapping(source = "prdtm", target = "arrivalTime") + @Mapping(source = "dly", target = "delayed") + @Mapping(source = "tmstmp", target = "metadata.timestamp") + @Mapping(source = "dyn", target = "metadata.dynamic") + @Mapping(source = "tablockid", target = "metadata.blockId") + @Mapping(source = "tatripid", target = "metadata.tripId") + @Mapping(source = "origtatripno", target = "metadata.originalTripNumber") + @Mapping(source = "zone", target = "metadata.zone") + @Mapping(source = "psgld", target = "metadata.passengerLoad") + @Mapping(source = "gtfsseq", target = "metadata.gtfsSequence") + @Mapping(source = "nbus", target = "metadata.nextBus") + @Mapping(source = "stst", target = "metadata.stopStatus") + @Mapping(source = "stsd", target = "metadata.stopStatusDescription") + @Mapping(source = "flagstop", target = "metadata.flagStop") + Arrival toDomain(CtaPredictionsPrd prediction); +} diff --git a/src/main/java/com/cta4j/bus/mapper/BusMapper.java b/src/main/java/com/cta4j/bus/mapper/BusMapper.java index 46edd46..e1cc5fa 100644 --- a/src/main/java/com/cta4j/bus/mapper/BusMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/BusMapper.java @@ -2,10 +2,12 @@ import com.cta4j.bus.external.vehicle.CtaVehicle; import com.cta4j.bus.model.Bus; +import org.jetbrains.annotations.ApiStatus; import org.mapstruct.Mapper; import org.mapstruct.Mapping; @Mapper +@ApiStatus.Internal public interface BusMapper { @Mapping(source = "vid", target = "id") @Mapping(source = "rt", target = "route") diff --git a/src/main/java/com/cta4j/bus/mapper/BusPredictionTypeMapper.java b/src/main/java/com/cta4j/bus/mapper/BusPredictionTypeMapper.java deleted file mode 100644 index 3dd7080..0000000 --- a/src/main/java/com/cta4j/bus/mapper/BusPredictionTypeMapper.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.cta4j.bus.mapper; - -import com.cta4j.bus.model.BusPredictionType; -import org.jetbrains.annotations.ApiStatus; - -@ApiStatus.Internal -public final class BusPredictionTypeMapper { - private BusPredictionTypeMapper() { - throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); - } - - public static BusPredictionType fromExternal(String string) { - if (string == null) { - throw new IllegalArgumentException("string must not be null"); - } - - string = string.toUpperCase(); - - return switch (string) { - case "A" -> BusPredictionType.ARRIVAL; - case "D" -> BusPredictionType.DEPARTURE; - default -> { - String message = "A bus prediction type with the name \"%s\" does not exist".formatted(string); - - throw new IllegalArgumentException(message); - } - }; - } -} diff --git a/src/main/java/com/cta4j/bus/mapper/RouteMapper.java b/src/main/java/com/cta4j/bus/mapper/RouteMapper.java index e27aa29..9e59149 100644 --- a/src/main/java/com/cta4j/bus/mapper/RouteMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/RouteMapper.java @@ -3,21 +3,15 @@ import com.cta4j.bus.external.route.CtaRoute; import com.cta4j.bus.model.Route; import org.jetbrains.annotations.ApiStatus; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +@Mapper @ApiStatus.Internal -public final class RouteMapper { - private RouteMapper() { - throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); - } - - public static Route fromExternal(CtaRoute route) { - if (route == null) { - throw new IllegalArgumentException("route must not be null"); - } - - return new Route( - route.rt(), - route.rtnm() - ); - } +public interface RouteMapper { + @Mapping(source = "rt", target = "id") + @Mapping(source = "rtnm", target = "name") + @Mapping(source = "rtdd", target = "description") + @Mapping(source = "rtclr", target = "color") + Route toDomain(CtaRoute route); } diff --git a/src/main/java/com/cta4j/bus/mapper/StopArrivalMapper.java b/src/main/java/com/cta4j/bus/mapper/StopArrivalMapper.java deleted file mode 100644 index d149811..0000000 --- a/src/main/java/com/cta4j/bus/mapper/StopArrivalMapper.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.cta4j.bus.mapper; - -import com.cta4j.bus.external.prediction.CtaPredictionsPrd; -import com.cta4j.bus.model.BusPredictionType; -import com.cta4j.bus.model.StopArrival; -import com.cta4j.util.DateTimeUtils; -import org.jetbrains.annotations.ApiStatus; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.math.BigInteger; -import java.time.Instant; -import java.time.format.DateTimeParseException; - -@ApiStatus.Internal -public final class StopArrivalMapper { - private static final Logger logger; - - static { - logger = LoggerFactory.getLogger(StopArrivalMapper.class); - } - - private StopArrivalMapper() { - throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); - } - - public static StopArrival fromExternal(CtaPredictionsPrd prd) { - if (prd == null) { - throw new IllegalArgumentException("prd must not be null"); - } - - BusPredictionType type = null; - - if (prd.typ() != null) { - try { - type = BusPredictionTypeMapper.fromExternal(prd.typ()); - } catch (IllegalArgumentException e) { - logger.warn("Invalid bus prediction type {}", prd.typ()); - } - } - - BigInteger distanceToStop = null; - - if (prd.dstp() != null) { - try { - distanceToStop = new BigInteger(prd.dstp()); - } catch (NumberFormatException e) { - logger.warn("Invalid distance to stop value {}", prd.dstp()); - } - } - - Instant arrivalTime = null; - - if (prd.prdtm() != null) { - try { - arrivalTime = DateTimeUtils.parseBusTimestamp(prd.prdtm()); - } catch (DateTimeParseException e) { - logger.warn("Invalid arrival time value {}", prd.prdtm()); - } - } - - Boolean delayed = null; - - if (prd.dly() != null) { - delayed = Boolean.parseBoolean(prd.dly()); - } - - return new StopArrival( - type, - prd.stpnm(), - prd.stpid(), - prd.vid(), - distanceToStop, - prd.rt(), - prd.rtdd(), - prd.rtdir(), - prd.des(), - arrivalTime, - delayed - ); - } -} diff --git a/src/main/java/com/cta4j/bus/mapper/StopMapper.java b/src/main/java/com/cta4j/bus/mapper/StopMapper.java index 46d2e6e..a34b2a3 100644 --- a/src/main/java/com/cta4j/bus/mapper/StopMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/StopMapper.java @@ -3,53 +3,19 @@ import com.cta4j.bus.external.stop.CtaStop; import com.cta4j.bus.model.Stop; import org.jetbrains.annotations.ApiStatus; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.math.BigDecimal; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +@Mapper @ApiStatus.Internal -public final class StopMapper { - private static final Logger logger; - - static { - logger = LoggerFactory.getLogger(StopMapper.class); - } - - private StopMapper() { - throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); - } - - public static Stop fromExternal(CtaStop stop) { - if (stop == null) { - throw new IllegalArgumentException("stop must not be null"); - } - - BigDecimal latitude = null; - - if (stop.lat() != null) { - try { - latitude = new BigDecimal(stop.lat()); - } catch (NumberFormatException e) { - logger.warn("Invalid latitude value {}", stop.lat()); - } - } - - BigDecimal longitude = null; - - if (stop.lon() != null) { - try { - longitude = new BigDecimal(stop.lon()); - } catch (NumberFormatException e) { - logger.warn("Invalid longitude value {}", stop.lon()); - } - } - - return new Stop( - stop.stpid(), - stop.stpnm(), - latitude, - longitude - ); - } +public interface StopMapper { + @Mapping(source = "stpid", target = "id") + @Mapping(source = "stpnm", target = "name") + @Mapping(source = "lat", target = "latitude") + @Mapping(source = "lon", target = "longitude") + @Mapping(source = "dtradd", target = "detoursAdded") + @Mapping(source = "dtrrem", target = "detoursRemoved") + @Mapping(source = "gtfsseq", target = "gtfsSequence") + @Mapping(source = "ada", target = "adaAccessible") + Stop toDomain(CtaStop stop); } diff --git a/src/main/java/com/cta4j/bus/mapper/UpcomingBusArrivalMapper.java b/src/main/java/com/cta4j/bus/mapper/UpcomingBusArrivalMapper.java deleted file mode 100644 index c0afe05..0000000 --- a/src/main/java/com/cta4j/bus/mapper/UpcomingBusArrivalMapper.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.cta4j.bus.mapper; - -import com.cta4j.bus.external.prediction.CtaPredictionsPrd; -import com.cta4j.bus.model.BusPredictionType; -import com.cta4j.bus.model.UpcomingBusArrival; -import com.cta4j.util.DateTimeUtils; -import org.jetbrains.annotations.ApiStatus; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.math.BigInteger; -import java.time.Instant; -import java.time.format.DateTimeParseException; - -@ApiStatus.Internal -public final class UpcomingBusArrivalMapper { - private static final Logger logger; - - static { - logger = LoggerFactory.getLogger(UpcomingBusArrivalMapper.class); - } - - private UpcomingBusArrivalMapper() { - throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); - } - - public static UpcomingBusArrival fromExternal(CtaPredictionsPrd prd) { - if (prd == null) { - throw new IllegalArgumentException("prd must not be null"); - } - - BusPredictionType type = null; - - if (prd.typ() != null) { - try { - type = BusPredictionTypeMapper.fromExternal(prd.typ()); - } catch (IllegalArgumentException e) { - logger.warn("Invalid bus prediction type {}", prd.typ()); - } - } - - BigInteger distanceToStop = null; - - if (prd.dstp() != null) { - try { - distanceToStop = new BigInteger(prd.dstp()); - } catch (NumberFormatException e) { - logger.warn("Invalid distance to stop value {}", prd.dstp()); - } - } - - Instant arrivalTime = null; - - if (prd.prdtm() != null) { - try { - arrivalTime = DateTimeUtils.parseBusTimestamp(prd.prdtm()); - } catch (DateTimeParseException e) { - logger.warn("Invalid arrival time value {}", prd.prdtm()); - } - } - - Boolean delayed = null; - - if (prd.dly() != null) { - delayed = Boolean.parseBoolean(prd.dly()); - } - - return new UpcomingBusArrival( - type, - prd.stpnm(), - prd.stpid(), - prd.vid(), - distanceToStop, - prd.rt(), - prd.rtdd(), - prd.rtdir(), - prd.des(), - arrivalTime, - delayed - ); - } -} diff --git a/src/main/java/com/cta4j/bus/model/Arrival.java b/src/main/java/com/cta4j/bus/model/Arrival.java new file mode 100644 index 0000000..d0a90d4 --- /dev/null +++ b/src/main/java/com/cta4j/bus/model/Arrival.java @@ -0,0 +1,40 @@ +package com.cta4j.bus.model; + +import java.math.BigInteger; +import java.time.Duration; +import java.time.Instant; + +public record Arrival( + PredictionType predictionType, + + String stopId, + + String stopName, + + String vehicleId, + + BigInteger distanceToStop, + + String route, + + String routeDesignator, + + String routeDirection, + + String destination, + + Instant arrivalTime, + + Boolean delayed, + + ArrivalMetadata metadata +) { + public Long etaMinutes() { + Instant now = Instant.now(); + + long minutes = Duration.between(now, this.arrivalTime) + .toMinutes(); + + return Math.max(minutes, 0L); + } +} diff --git a/src/main/java/com/cta4j/bus/model/ArrivalMetadata.java b/src/main/java/com/cta4j/bus/model/ArrivalMetadata.java new file mode 100644 index 0000000..3e248e8 --- /dev/null +++ b/src/main/java/com/cta4j/bus/model/ArrivalMetadata.java @@ -0,0 +1,28 @@ +package com.cta4j.bus.model; + +public record ArrivalMetadata( + String timestamp, + + int dynamic, + + String blockId, + + String tripId, + + String originalTripNumber, + + String zone, + + PassengerLoad passengerLoad, + + int gtfsSequence, + + String nextBus, + + Integer stopStatus, + + String stopStatusDescription, + + int flagStop +) { +} diff --git a/src/main/java/com/cta4j/bus/model/BusPredictionType.java b/src/main/java/com/cta4j/bus/model/PredictionType.java similarity index 88% rename from src/main/java/com/cta4j/bus/model/BusPredictionType.java rename to src/main/java/com/cta4j/bus/model/PredictionType.java index 5578b6f..d281a57 100644 --- a/src/main/java/com/cta4j/bus/model/BusPredictionType.java +++ b/src/main/java/com/cta4j/bus/model/PredictionType.java @@ -3,7 +3,7 @@ /** * A type of bus prediction, either an arrival or a departure. */ -public enum BusPredictionType { +public enum PredictionType { /** * Indicates an arrival prediction. */ diff --git a/src/main/java/com/cta4j/bus/model/Route.java b/src/main/java/com/cta4j/bus/model/Route.java index 885f561..788ea73 100644 --- a/src/main/java/com/cta4j/bus/model/Route.java +++ b/src/main/java/com/cta4j/bus/model/Route.java @@ -5,10 +5,16 @@ * * @param id the unique identifier of the bus route * @param name the name of the bus route + * @param description the description of the bus route + * @param color the color associated with the bus route */ public record Route( String id, - String name + String name, + + String description, + + String color ) { } diff --git a/src/main/java/com/cta4j/bus/model/Stop.java b/src/main/java/com/cta4j/bus/model/Stop.java index 965e97e..dd7961e 100644 --- a/src/main/java/com/cta4j/bus/model/Stop.java +++ b/src/main/java/com/cta4j/bus/model/Stop.java @@ -1,6 +1,7 @@ package com.cta4j.bus.model; import java.math.BigDecimal; +import java.util.List; /** * A bus stop. @@ -9,6 +10,10 @@ * @param name the name of the bus stop * @param latitude the latitude coordinate of the bus stop * @param longitude the longitude coordinate of the bus stop + * @param detoursAdded a list of detour identifiers that have been added to this stop + * @param detoursRemoved a list of detour identifiers that have been removed from this stop + * @param gtfsSequence the GTFS sequence number of the bus stop + * @param adaAccessible indicates whether the bus stop is ADA accessible */ public record Stop( String id, @@ -17,6 +22,14 @@ public record Stop( BigDecimal latitude, - BigDecimal longitude + BigDecimal longitude, + + List detoursAdded, + + List detoursRemoved, + + Integer gtfsSequence, + + Boolean adaAccessible ) { } diff --git a/src/main/java/com/cta4j/bus/model/StopArrival.java b/src/main/java/com/cta4j/bus/model/StopArrival.java deleted file mode 100644 index 3efa955..0000000 --- a/src/main/java/com/cta4j/bus/model/StopArrival.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.cta4j.bus.model; - -import java.math.BigInteger; -import java.time.Duration; -import java.time.Instant; - -/** - * An arrival prediction for a bus at a specific stop. - * - * @param predictionType the type of prediction (arrival or departure) - * @param stopName the name of the bus stop - * @param stopId the unique identifier of the bus stop - * @param vehicleId the unique identifier of the bus vehicle - * @param distanceToStop the distance from the bus to the stop in feet - * @param route the bus route identifier - * @param routeDesignator additional designator for the route, if any - * @param routeDirection the direction of the bus route (e.g., Northbound, Southbound) - * @param destination the final destination of the bus - * @param arrivalTime the predicted arrival time at the stop - * @param delayed indicates whether the bus is delayed - */ -public record StopArrival( - BusPredictionType predictionType, - - String stopName, - - String stopId, - - String vehicleId, - - BigInteger distanceToStop, - - String route, - - String routeDesignator, - - String routeDirection, - - String destination, - - Instant arrivalTime, - - Boolean delayed -) { - /** - * Calculates the estimated time of arrival (ETA) in minutes from the current time to the predicted arrival time. - * If the arrival time is in the past, it returns 0. - * - * @return the ETA in minutes, or 0 if the arrival time is in the past - */ - public Long etaMinutes() { - Instant now = Instant.now(); - - long minutes = Duration.between(now, this.arrivalTime) - .toMinutes(); - - return Math.max(minutes, 0L); - } -} diff --git a/src/main/java/com/cta4j/bus/model/UpcomingBusArrival.java b/src/main/java/com/cta4j/bus/model/UpcomingBusArrival.java deleted file mode 100644 index a18e09d..0000000 --- a/src/main/java/com/cta4j/bus/model/UpcomingBusArrival.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.cta4j.bus.model; - -import java.math.BigInteger; -import java.time.Duration; -import java.time.Instant; - -/** - * An upcoming arrival prediction for a bus at a stop. - * - * @param predictionType the type of prediction (arrival or departure) - * @param stopName the name of the bus stop - * @param stopId the unique identifier of the bus stop - * @param vehicleId the unique identifier of the bus vehicle - * @param distanceToStop the distance from the bus to the stop in feet - * @param route the bus route identifier - * @param routeDesignator additional designator for the route, if any - * @param routeDirection the direction of the bus route (e.g., Northbound, Southbound) - * @param destination the final destination of the bus - * @param arrivalTime the predicted arrival time at the stop - * @param delayed indicates whether the bus is delayed - */ -public record UpcomingBusArrival( - BusPredictionType predictionType, - - String stopName, - - String stopId, - - String vehicleId, - - BigInteger distanceToStop, - - String route, - - String routeDesignator, - - String routeDirection, - - String destination, - - Instant arrivalTime, - - Boolean delayed -) { - /** - * Calculates the estimated time of arrival (ETA) in minutes from the current time to the predicted arrival time. - * If the arrival time is in the past, it returns 0. - * - * @return the ETA in minutes, or 0 if the arrival time is in the past - */ - public Long etaMinutes() { - Instant now = Instant.now(); - - long minutes = Duration.between(now, this.arrivalTime) - .toMinutes(); - - return Math.max(minutes, 0L); - } -} From efe4365065d7407afd6c7ceab0d3c52ab9665d68 Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sat, 10 Jan 2026 13:37:07 -0600 Subject: [PATCH 09/53] JSpecify + Model updates --- .../prediction/CtaPredictionsPrd.java | 22 +++++++ .../bus/external/vehicle/CtaVehicle.java | 13 ----- .../java/com/cta4j/bus/mapper/BusMapper.java | 19 +++--- .../java/com/cta4j/bus/model/Arrival.java | 50 +++++++++++++++- .../com/cta4j/bus/model/ArrivalMetadata.java | 33 +++++++++++ src/main/java/com/cta4j/bus/model/Bus.java | 6 +- .../java/com/cta4j/bus/model/BusMetadata.java | 58 +++++++++++++------ 7 files changed, 158 insertions(+), 43 deletions(-) diff --git a/src/main/java/com/cta4j/bus/external/prediction/CtaPredictionsPrd.java b/src/main/java/com/cta4j/bus/external/prediction/CtaPredictionsPrd.java index 1cd22b1..720fa15 100644 --- a/src/main/java/com/cta4j/bus/external/prediction/CtaPredictionsPrd.java +++ b/src/main/java/com/cta4j/bus/external/prediction/CtaPredictionsPrd.java @@ -7,27 +7,49 @@ @JsonIgnoreProperties(ignoreUnknown = true) public record CtaPredictionsPrd( String tmstmp, + String typ, + String stpid, + String stpnm, + int vid, + int dstp, + String rt, + String rtdd, + String rtdir, + String des, + String prdtm, + Boolean dly, + int dyn, + String tablockid, + String tatripid, + String origtatripno, + String zone, + String psgld, + int gtfsseq, + String nbus, + Integer stst, + String stsd, + int flagstop ) { } diff --git a/src/main/java/com/cta4j/bus/external/vehicle/CtaVehicle.java b/src/main/java/com/cta4j/bus/external/vehicle/CtaVehicle.java index 49a6bb8..92ad266 100644 --- a/src/main/java/com/cta4j/bus/external/vehicle/CtaVehicle.java +++ b/src/main/java/com/cta4j/bus/external/vehicle/CtaVehicle.java @@ -2,16 +2,12 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.jetbrains.annotations.ApiStatus; -import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; -@NullMarked @ApiStatus.Internal @JsonIgnoreProperties(ignoreUnknown = true) public record CtaVehicle( String vid, - @Nullable String rtpidatafeed, String tmpstmp, @@ -30,29 +26,22 @@ public record CtaVehicle( Integer pdist, - @Nullable Byte stopstatus, - @Nullable Integer timepointid, - @Nullable String stopid, - @Nullable Integer sequence, - @Nullable Integer gtfsseq, Boolean dly, - @Nullable String srvtmstmp, Integer spd, - @Nullable Integer blk, String tablockid, @@ -67,10 +56,8 @@ public record CtaVehicle( String psgld, - @Nullable Integer stst, - @Nullable String stsd ) { } diff --git a/src/main/java/com/cta4j/bus/mapper/BusMapper.java b/src/main/java/com/cta4j/bus/mapper/BusMapper.java index e1cc5fa..ae1758d 100644 --- a/src/main/java/com/cta4j/bus/mapper/BusMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/BusMapper.java @@ -10,26 +10,27 @@ @ApiStatus.Internal public interface BusMapper { @Mapping(source = "vid", target = "id") - @Mapping(source = "rt", target = "route") - @Mapping(source = "des", target = "destination") + @Mapping(source = "rtpidatafeed", target = "metadata.dataFeed") + @Mapping(source = "tmpstmp", target = "metadata.lastUpdated") @Mapping(source = "lat", target = "coordinates.latitude") @Mapping(source = "lon", target = "coordinates.longitude") @Mapping(source = "hdg", target = "coordinates.heading") - @Mapping(source = "dly", target = "delayed") - @Mapping(source = "rtpidatafeed", target = "metadata.dataFeed") - @Mapping(source = "srvtmstmp", target = "metadata.serverTimestamp") - @Mapping(source = "spd", target = "metadata.speed") @Mapping(source = "pid", target = "metadata.patternId") + @Mapping(source = "rt", target = "route") + @Mapping(source = "des", target = "destination") @Mapping(source = "pdist", target = "metadata.distanceToPatternPoint") @Mapping(source = "stopstatus", target = "metadata.stopStatus") @Mapping(source = "timepointid", target = "metadata.timepointId") @Mapping(source = "stopid", target = "metadata.stopId") @Mapping(source = "sequence", target = "metadata.sequence") @Mapping(source = "gtfsseq", target = "metadata.gtfsSequence") + @Mapping(source = "dly", target = "delayed") + @Mapping(source = "srvtmstmp", target = "metadata.serverTimestamp") + @Mapping(source = "spd", target = "metadata.speed") @Mapping(source = "blk", target = "metadata.block") - @Mapping(source = "tablockid", target = "metadata.taBlockId") - @Mapping(source = "tatripid", target = "metadata.taTripId") - @Mapping(source = "origtatripno", target = "metadata.originalTaTripNo") + @Mapping(source = "tablockid", target = "metadata.blockId") + @Mapping(source = "tatripid", target = "metadata.tripId") + @Mapping(source = "origtatripno", target = "metadata.originalTripNumber") @Mapping(source = "zone", target = "metadata.zone") @Mapping(source = "mode", target = "metadata.mode") @Mapping(source = "psgld", target = "metadata.passengerLoad") diff --git a/src/main/java/com/cta4j/bus/model/Arrival.java b/src/main/java/com/cta4j/bus/model/Arrival.java index d0a90d4..e8f4947 100644 --- a/src/main/java/com/cta4j/bus/model/Arrival.java +++ b/src/main/java/com/cta4j/bus/model/Arrival.java @@ -1,9 +1,14 @@ package com.cta4j.bus.model; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + import java.math.BigInteger; import java.time.Duration; import java.time.Instant; +@NullMarked +@SuppressWarnings("ConstantConditions") public record Arrival( PredictionType predictionType, @@ -25,11 +30,54 @@ public record Arrival( Instant arrivalTime, + @Nullable Boolean delayed, ArrivalMetadata metadata ) { - public Long etaMinutes() { + public Arrival { + if (stopId == null) { + throw new IllegalArgumentException("stopId must not be null"); + } + + if (stopName == null) { + throw new IllegalArgumentException("stopName must not be null"); + } + + if (vehicleId == null) { + throw new IllegalArgumentException("vehicleId must not be null"); + } + + if (distanceToStop == null) { + throw new IllegalArgumentException("distanceToStop must not be null"); + } + + if (route == null) { + throw new IllegalArgumentException("route must not be null"); + } + + if (routeDesignator == null) { + throw new IllegalArgumentException("routeDesignator must not be null"); + } + + if (routeDirection == null) { + throw new IllegalArgumentException("routeDirection must not be null"); + } + + if (destination == null) { + throw new IllegalArgumentException("destination must not be null"); + } + + if (arrivalTime == null) { + throw new IllegalArgumentException("arrivalTime must not be null"); + } + + if (metadata == null) { + throw new IllegalArgumentException("metadata must not be null"); + } + } + + public long etaMinutes() { Instant now = Instant.now(); long minutes = Duration.between(now, this.arrivalTime) diff --git a/src/main/java/com/cta4j/bus/model/ArrivalMetadata.java b/src/main/java/com/cta4j/bus/model/ArrivalMetadata.java index 3e248e8..d979732 100644 --- a/src/main/java/com/cta4j/bus/model/ArrivalMetadata.java +++ b/src/main/java/com/cta4j/bus/model/ArrivalMetadata.java @@ -1,5 +1,10 @@ package com.cta4j.bus.model; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +@SuppressWarnings("ConstantConditions") public record ArrivalMetadata( String timestamp, @@ -17,12 +22,40 @@ public record ArrivalMetadata( int gtfsSequence, + @Nullable String nextBus, + @Nullable Integer stopStatus, + @Nullable String stopStatusDescription, int flagStop ) { + public ArrivalMetadata { + if (timestamp == null) { + throw new IllegalArgumentException("timestamp must not be null"); + } + + if (blockId == null) { + throw new IllegalArgumentException("blockId must not be null"); + } + + if (tripId == null) { + throw new IllegalArgumentException("tripId must not be null"); + } + + if (originalTripNumber == null) { + throw new IllegalArgumentException("originalTripNumber must not be null"); + } + + if (zone == null) { + throw new IllegalArgumentException("zone must not be null"); + } + + if (passengerLoad == null) { + throw new IllegalArgumentException("passengerLoad must not be null"); + } + } } diff --git a/src/main/java/com/cta4j/bus/model/Bus.java b/src/main/java/com/cta4j/bus/model/Bus.java index 1e4c319..4aa7675 100644 --- a/src/main/java/com/cta4j/bus/model/Bus.java +++ b/src/main/java/com/cta4j/bus/model/Bus.java @@ -1,7 +1,6 @@ package com.cta4j.bus.model; import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; @NullMarked @SuppressWarnings("ConstantConditions") @@ -16,7 +15,6 @@ public record Bus( boolean delayed, - @Nullable BusMetadata metadata ) { public Bus { @@ -35,5 +33,9 @@ public record Bus( if (coordinates == null) { throw new IllegalArgumentException("coordinates must not be null"); } + + if (metadata == null) { + throw new IllegalArgumentException("metadata must not be null"); + } } } diff --git a/src/main/java/com/cta4j/bus/model/BusMetadata.java b/src/main/java/com/cta4j/bus/model/BusMetadata.java index c55c28d..7629520 100644 --- a/src/main/java/com/cta4j/bus/model/BusMetadata.java +++ b/src/main/java/com/cta4j/bus/model/BusMetadata.java @@ -1,28 +1,26 @@ package com.cta4j.bus.model; +import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import java.time.Instant; import java.time.LocalDate; +@NullMarked +@SuppressWarnings("ConstantConditions") public record BusMetadata( @Nullable String dataFeed, @Nullable - Instant serverTimestamp, - - @Nullable - Integer speed, + Instant lastUpdated, - @Nullable - Integer patternId, + int patternId, - @Nullable - Integer distanceToPatternPoint, + int distanceToPatternPoint, @Nullable - Byte stopStatus, + Integer stopStatus, @Nullable Integer timepointId, @@ -37,24 +35,23 @@ public record BusMetadata( Integer gtfsSequence, @Nullable - Integer block, + Instant serverTimestamp, - @Nullable - String taBlockId, + int speed, @Nullable - String taTripId, + Integer block, - @Nullable - String originalTaTripNo, + String blockId, + + String tripId, + + String originalTripNumber, - @Nullable String zone, - @Nullable TransitMode mode, - @Nullable PassengerLoad passengerLoad, @Nullable @@ -63,4 +60,29 @@ public record BusMetadata( @Nullable LocalDate scheduledStartDate ) { + public BusMetadata { + if (blockId == null) { + throw new IllegalArgumentException("blockId must not be null"); + } + + if (tripId == null) { + throw new IllegalArgumentException("tripId must not be null"); + } + + if (originalTripNumber == null) { + throw new IllegalArgumentException("originalTripNumber must not be null"); + } + + if (zone == null) { + throw new IllegalArgumentException("zone must not be null"); + } + + if (mode == null) { + throw new IllegalArgumentException("mode must not be null"); + } + + if (passengerLoad == null) { + throw new IllegalArgumentException("passengerLoad must not be null"); + } + } } From 369c1c0b06830bf2616e13cac948eeb8e558c90e Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sat, 10 Jan 2026 13:40:07 -0600 Subject: [PATCH 10/53] JSpecify + Model updates --- .../java/com/cta4j/bus/external/route/CtaRoute.java | 3 +++ .../java/com/cta4j/bus/external/stop/CtaStop.java | 11 +++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/cta4j/bus/external/route/CtaRoute.java b/src/main/java/com/cta4j/bus/external/route/CtaRoute.java index 06635b1..c32a363 100644 --- a/src/main/java/com/cta4j/bus/external/route/CtaRoute.java +++ b/src/main/java/com/cta4j/bus/external/route/CtaRoute.java @@ -7,8 +7,11 @@ @JsonIgnoreProperties(ignoreUnknown = true) public record CtaRoute( String rt, + String rtnm, + String rtdd, + String rtclr ) { } diff --git a/src/main/java/com/cta4j/bus/external/stop/CtaStop.java b/src/main/java/com/cta4j/bus/external/stop/CtaStop.java index 8a8d345..b3040f0 100644 --- a/src/main/java/com/cta4j/bus/external/stop/CtaStop.java +++ b/src/main/java/com/cta4j/bus/external/stop/CtaStop.java @@ -9,12 +9,19 @@ @JsonIgnoreProperties(ignoreUnknown = true) public record CtaStop( String stpid, + String stpnm, - double lat, - double lon, + + Double lat, + + Double lon, + List dtradd, + List dtrrem, + Integer gtfsseq, + Boolean ada ) { } From bb4c27b9aecfa2a80493c2ac986905fbf1f04d4d Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sat, 10 Jan 2026 13:48:18 -0600 Subject: [PATCH 11/53] JSpecify + Model updates --- .../com/cta4j/bus/mapper/ArrivalMapper.java | 2 +- .../com/cta4j/bus/model/ArrivalMetadata.java | 2 +- .../com/cta4j/bus/model/DynamicAction.java | 57 +++++++++++++++++++ 3 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/cta4j/bus/model/DynamicAction.java diff --git a/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java b/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java index 1b6bd32..470c74a 100644 --- a/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java @@ -21,7 +21,7 @@ public interface ArrivalMapper { @Mapping(source = "prdtm", target = "arrivalTime") @Mapping(source = "dly", target = "delayed") @Mapping(source = "tmstmp", target = "metadata.timestamp") - @Mapping(source = "dyn", target = "metadata.dynamic") + @Mapping(source = "dyn", target = "metadata.dynamicAction") @Mapping(source = "tablockid", target = "metadata.blockId") @Mapping(source = "tatripid", target = "metadata.tripId") @Mapping(source = "origtatripno", target = "metadata.originalTripNumber") diff --git a/src/main/java/com/cta4j/bus/model/ArrivalMetadata.java b/src/main/java/com/cta4j/bus/model/ArrivalMetadata.java index d979732..dc2140c 100644 --- a/src/main/java/com/cta4j/bus/model/ArrivalMetadata.java +++ b/src/main/java/com/cta4j/bus/model/ArrivalMetadata.java @@ -8,7 +8,7 @@ public record ArrivalMetadata( String timestamp, - int dynamic, + DynamicAction dynamicAction, String blockId, diff --git a/src/main/java/com/cta4j/bus/model/DynamicAction.java b/src/main/java/com/cta4j/bus/model/DynamicAction.java new file mode 100644 index 0000000..ccda3ce --- /dev/null +++ b/src/main/java/com/cta4j/bus/model/DynamicAction.java @@ -0,0 +1,57 @@ +package com.cta4j.bus.model; + +public enum DynamicAction { + NONE(0), + + CANCELLED(1), + + REASSIGNED(2), + + SHIFTED(3), + + EXPRESSED(4), + + STOPS_AFFECTED(6), + + NEW_TRIP(8), + + PARTIAL_TRIP(9), + + PARTIAL_TRIP_NEW(10), + + DELAYED_CANCEL(12), + + ADDED_STOP(13), + + UNKNOWN_DELAY(14), + + UNKNOWN_DELAY_NEW(15), + + INVALIDATED_TRIP(16), + + INVALIDATED_TRIP_NEW(17), + + CANCELLED_TRIP_NEW(18), + + STOPS_AFFECTED_NEW(19); + + private final int id; + + DynamicAction(int id) { + this.id = id; + } + + public int getId() { + return this.id; + } + + public static DynamicAction fromId(int id) { + for (DynamicAction action : DynamicAction.values()) { + if (action.id == id) { + return action; + } + } + + throw new IllegalArgumentException("Invalid dynamic action id: " + id); + } +} From 47b046625145108aa280153ba425d82d2df1548c Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sat, 10 Jan 2026 14:08:50 -0600 Subject: [PATCH 12/53] JSpecify + Model updates --- .../bus/client/internal/BusClientImpl.java | 90 ++++++++----------- .../cta4j/bus/external/detour/CtaDetour.java | 4 +- .../com/cta4j/bus/mapper/DetourMapper.java | 81 +++-------------- src/main/java/com/cta4j/bus/model/Detour.java | 23 ++--- .../cta4j/bus/model/DetourRouteDirection.java | 6 -- .../com/cta4j/bus/model/DynamicAction.java | 10 --- .../com/cta4j/bus/model/PassengerLoad.java | 14 +-- .../com/cta4j/bus/model/PredictionType.java | 9 -- src/main/java/com/cta4j/bus/model/Route.java | 8 -- src/main/java/com/cta4j/bus/model/Stop.java | 12 --- .../java/com/cta4j/bus/model/TransitMode.java | 10 --- 11 files changed, 59 insertions(+), 208 deletions(-) diff --git a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java index 8824f39..9874176 100644 --- a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java +++ b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java @@ -60,6 +60,7 @@ public final class BusClientImpl implements BusClient { private final ArrivalMapper arrivalMapper; private final RouteMapper routeMapper; private final StopMapper stopMapper; + private final DetourMapper detourMapper; private final BusMapper busMapper; private BusClientImpl(String host, String apiKey) { @@ -77,6 +78,7 @@ private BusClientImpl(String host, String apiKey) { this.arrivalMapper = Mappers.getMapper(ArrivalMapper.class); this.routeMapper = Mappers.getMapper(RouteMapper.class); this.stopMapper = Mappers.getMapper(StopMapper.class); + this.detourMapper = Mappers.getMapper(DetourMapper.class); this.busMapper = Mappers.getMapper(BusMapper.class); } @@ -232,33 +234,7 @@ public List getArrivalsByStop(String routeId, String stopId) { .addParameter("format", "json") .toString(); - String response = HttpUtils.get(url); - - CtaPredictionsResponse predictionsResponse; - - try { - predictionsResponse = this.objectMapper.readValue(response, CtaPredictionsResponse.class); - } catch (JacksonException e) { - String message = "Failed to parse response from %s".formatted(PREDICTIONS_ENDPOINT); - - throw new Cta4jException(message, e); - } - - CtaPredictionsBustimeResponse bustimeResponse = predictionsResponse.bustimeResponse(); - - if (bustimeResponse == null) { - throw new Cta4jException("Invalid response from %s".formatted(PREDICTIONS_ENDPOINT)); - } - - List prd = bustimeResponse.prd(); - - if ((prd == null) || prd.isEmpty()) { - return List.of(); - } - - return prd.stream() - .map(this.arrivalMapper::toDomain) - .toList(); + return this.getArrivals(url); } @Override @@ -276,33 +252,7 @@ public List getArrivalsByBus(String busId) { .addParameter("format", "json") .toString(); - String response = HttpUtils.get(url); - - CtaPredictionsResponse predictionsResponse; - - try { - predictionsResponse = this.objectMapper.readValue(response, CtaPredictionsResponse.class); - } catch (JacksonException e) { - String message = "Failed to parse response from %s".formatted(PREDICTIONS_ENDPOINT); - - throw new Cta4jException(message, e); - } - - CtaPredictionsBustimeResponse bustimeResponse = predictionsResponse.bustimeResponse(); - - if (bustimeResponse == null) { - throw new Cta4jException("Invalid response from %s".formatted(PREDICTIONS_ENDPOINT)); - } - - List prd = bustimeResponse.prd(); - - if ((prd == null) || prd.isEmpty()) { - return List.of(); - } - - return prd.stream() - .map(this.arrivalMapper::toDomain) - .toList(); + return this.getArrivals(url); } @Override @@ -350,7 +300,7 @@ public List getDetours(String routeId, String direction) { } return dtrs.stream() - .map(DetourMapper::fromExternal) + .map(this.detourMapper::toDomain) .toList(); } @@ -406,6 +356,36 @@ public Optional getBus(String id) { return Optional.of(bus); } + private List getArrivals(String url) { + String response = HttpUtils.get(url); + + CtaPredictionsResponse predictionsResponse; + + try { + predictionsResponse = this.objectMapper.readValue(response, CtaPredictionsResponse.class); + } catch (JacksonException e) { + String message = "Failed to parse response from %s".formatted(PREDICTIONS_ENDPOINT); + + throw new Cta4jException(message, e); + } + + CtaPredictionsBustimeResponse bustimeResponse = predictionsResponse.bustimeResponse(); + + if (bustimeResponse == null) { + throw new Cta4jException("Invalid response from %s".formatted(PREDICTIONS_ENDPOINT)); + } + + List prd = bustimeResponse.prd(); + + if ((prd == null) || prd.isEmpty()) { + return List.of(); + } + + return prd.stream() + .map(this.arrivalMapper::toDomain) + .toList(); + } + public static final class BuilderImpl implements BusClient.Builder { @Nullable private String host; diff --git a/src/main/java/com/cta4j/bus/external/detour/CtaDetour.java b/src/main/java/com/cta4j/bus/external/detour/CtaDetour.java index 53d2651..d11fabc 100644 --- a/src/main/java/com/cta4j/bus/external/detour/CtaDetour.java +++ b/src/main/java/com/cta4j/bus/external/detour/CtaDetour.java @@ -20,6 +20,8 @@ public record CtaDetour( String startdt, - String enddt + String enddt, + + String rtpidatafeed ) { } diff --git a/src/main/java/com/cta4j/bus/mapper/DetourMapper.java b/src/main/java/com/cta4j/bus/mapper/DetourMapper.java index 7793633..8be7a76 100644 --- a/src/main/java/com/cta4j/bus/mapper/DetourMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/DetourMapper.java @@ -2,76 +2,19 @@ import com.cta4j.bus.external.detour.CtaDetour; import com.cta4j.bus.model.Detour; -import com.cta4j.bus.model.DetourRouteDirection; -import com.cta4j.util.DateTimeUtils; import org.jetbrains.annotations.ApiStatus; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.time.Instant; -import java.time.format.DateTimeParseException; -import java.util.List; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +@Mapper @ApiStatus.Internal -public final class DetourMapper { - private static final Logger logger; - - static { - logger = LoggerFactory.getLogger(DetourMapper.class); - } - - private DetourMapper() { - throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); - } - - public static Detour fromExternal(CtaDetour detour) { - if (detour == null) { - throw new IllegalArgumentException("detour must not be null"); - } - - Boolean active = null; - - if (detour.st() != null) { - active = "1".equals(detour.st()); - } - - List routeDirections = null; - - if (detour.rtdirs() != null) { - routeDirections = detour.rtdirs() - .stream() - .map(rd -> new DetourRouteDirection(rd.rt(), rd.dir())) - .toList(); - } - - Instant startTime = null; - - if (detour.startdt() != null) { - try { - startTime = DateTimeUtils.parseBusTimestamp(detour.startdt()); - } catch (DateTimeParseException e) { - logger.warn("Invalid start time value {}", detour.startdt()); - } - } - - Instant endTime = null; - - if (detour.enddt() != null) { - try { - endTime = DateTimeUtils.parseBusTimestamp(detour.enddt()); - } catch (DateTimeParseException e) { - logger.warn("Invalid end time value {}", detour.enddt()); - } - } - - return new Detour( - detour.id(), - detour.ver(), - active, - detour.desc(), - routeDirections, - startTime, - endTime - ); - } +public interface DetourMapper { + @Mapping(source = "ver", target = "version") + @Mapping(source = "st", target = "active", qualifiedByName = "stToActive") + @Mapping(source = "desc", target = "description") + @Mapping(source = "rtdirs", target = "routeDirections") + @Mapping(source = "startdt", target = "startTime", qualifiedByName = "toInstant") + @Mapping(source = "enddt", target = "endTime", qualifiedByName = "toInstant") + @Mapping(source = "rtpidatafeed", target = "dataFeed") + Detour toDomain(CtaDetour dto); } diff --git a/src/main/java/com/cta4j/bus/model/Detour.java b/src/main/java/com/cta4j/bus/model/Detour.java index 92fac3e..1544660 100644 --- a/src/main/java/com/cta4j/bus/model/Detour.java +++ b/src/main/java/com/cta4j/bus/model/Detour.java @@ -1,21 +1,11 @@ package com.cta4j.bus.model; import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.time.Instant; import java.util.List; -/** - * A detour affecting one or more bus routes. - * - * @param id the unique identifier of the detour - * @param version the version number of the detour - * @param active whether the detour is currently active - * @param description a description of the detour - * @param routeDirections the list of route directions affected by the detour - * @param startTime the start time of the detour - * @param endTime the end time of the detour - */ @NullMarked @SuppressWarnings("ConstantConditions") public record Detour( @@ -23,7 +13,7 @@ public record Detour( String version, - Boolean active, + boolean active, String description, @@ -31,7 +21,10 @@ public record Detour( Instant startTime, - Instant endTime + Instant endTime, + + @Nullable + String dataFeed ) { public Detour { if (id == null) { @@ -42,10 +35,6 @@ public record Detour( throw new IllegalArgumentException("version must not be null"); } - if (active == null) { - throw new IllegalArgumentException("active must not be null"); - } - if (description == null) { throw new IllegalArgumentException("description must not be null"); } diff --git a/src/main/java/com/cta4j/bus/model/DetourRouteDirection.java b/src/main/java/com/cta4j/bus/model/DetourRouteDirection.java index 01dbd5f..692bc21 100644 --- a/src/main/java/com/cta4j/bus/model/DetourRouteDirection.java +++ b/src/main/java/com/cta4j/bus/model/DetourRouteDirection.java @@ -1,11 +1,5 @@ package com.cta4j.bus.model; -/** - * A bus route and its direction affected by a detour. - * - * @param route the bus route identifier - * @param direction the direction of the bus route (e.g., "Northbound", "Southbound") - */ public record DetourRouteDirection( String route, diff --git a/src/main/java/com/cta4j/bus/model/DynamicAction.java b/src/main/java/com/cta4j/bus/model/DynamicAction.java index ccda3ce..aca6292 100644 --- a/src/main/java/com/cta4j/bus/model/DynamicAction.java +++ b/src/main/java/com/cta4j/bus/model/DynamicAction.java @@ -44,14 +44,4 @@ public enum DynamicAction { public int getId() { return this.id; } - - public static DynamicAction fromId(int id) { - for (DynamicAction action : DynamicAction.values()) { - if (action.id == id) { - return action; - } - } - - throw new IllegalArgumentException("Invalid dynamic action id: " + id); - } } diff --git a/src/main/java/com/cta4j/bus/model/PassengerLoad.java b/src/main/java/com/cta4j/bus/model/PassengerLoad.java index a117c16..4893a64 100644 --- a/src/main/java/com/cta4j/bus/model/PassengerLoad.java +++ b/src/main/java/com/cta4j/bus/model/PassengerLoad.java @@ -2,18 +2,10 @@ public enum PassengerLoad { FULL, + HALF_EMPTY, - EMPTY, - UNKNOWN; - public static PassengerLoad fromString(String value) { - String upperCaseValue = value.toUpperCase(); + EMPTY, - return switch (upperCaseValue) { - case "FULL" -> FULL; - case "HALF_EMPTY" -> HALF_EMPTY; - case "EMPTY" -> EMPTY; - default -> UNKNOWN; - }; - } + UNKNOWN } diff --git a/src/main/java/com/cta4j/bus/model/PredictionType.java b/src/main/java/com/cta4j/bus/model/PredictionType.java index d281a57..b99d6df 100644 --- a/src/main/java/com/cta4j/bus/model/PredictionType.java +++ b/src/main/java/com/cta4j/bus/model/PredictionType.java @@ -1,16 +1,7 @@ package com.cta4j.bus.model; -/** - * A type of bus prediction, either an arrival or a departure. - */ public enum PredictionType { - /** - * Indicates an arrival prediction. - */ ARRIVAL, - /** - * Indicates a departure prediction. - */ DEPARTURE } diff --git a/src/main/java/com/cta4j/bus/model/Route.java b/src/main/java/com/cta4j/bus/model/Route.java index 788ea73..e7632e0 100644 --- a/src/main/java/com/cta4j/bus/model/Route.java +++ b/src/main/java/com/cta4j/bus/model/Route.java @@ -1,13 +1,5 @@ package com.cta4j.bus.model; -/** - * A bus route. - * - * @param id the unique identifier of the bus route - * @param name the name of the bus route - * @param description the description of the bus route - * @param color the color associated with the bus route - */ public record Route( String id, diff --git a/src/main/java/com/cta4j/bus/model/Stop.java b/src/main/java/com/cta4j/bus/model/Stop.java index dd7961e..930e54f 100644 --- a/src/main/java/com/cta4j/bus/model/Stop.java +++ b/src/main/java/com/cta4j/bus/model/Stop.java @@ -3,18 +3,6 @@ import java.math.BigDecimal; import java.util.List; -/** - * A bus stop. - * - * @param id the unique identifier of the bus stop - * @param name the name of the bus stop - * @param latitude the latitude coordinate of the bus stop - * @param longitude the longitude coordinate of the bus stop - * @param detoursAdded a list of detour identifiers that have been added to this stop - * @param detoursRemoved a list of detour identifiers that have been removed from this stop - * @param gtfsSequence the GTFS sequence number of the bus stop - * @param adaAccessible indicates whether the bus stop is ADA accessible - */ public record Stop( String id, diff --git a/src/main/java/com/cta4j/bus/model/TransitMode.java b/src/main/java/com/cta4j/bus/model/TransitMode.java index 5a479bb..413f8a0 100644 --- a/src/main/java/com/cta4j/bus/model/TransitMode.java +++ b/src/main/java/com/cta4j/bus/model/TransitMode.java @@ -16,14 +16,4 @@ public enum TransitMode { TransitMode(int code) { this.code = code; } - - public static TransitMode fromCode(int code) { - for (TransitMode transitMode : TransitMode.values()) { - if (transitMode.code == code) { - return transitMode; - } - } - - throw new IllegalArgumentException("Invalid transit mode: " + code); - } } From 8d5dd4c29508b445d3794f6c5f1feda91ef0bcb4 Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sat, 10 Jan 2026 14:23:12 -0600 Subject: [PATCH 13/53] JSpecify + Model updates --- .../cta4j/bus/external/route/CtaRoute.java | 4 ++- .../com/cta4j/bus/mapper/DetourMapper.java | 12 ++++++-- .../com/cta4j/bus/mapper/RouteMapper.java | 3 +- src/main/java/com/cta4j/bus/model/Detour.java | 2 +- src/main/java/com/cta4j/bus/model/Route.java | 29 +++++++++++++++++-- 5 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/cta4j/bus/external/route/CtaRoute.java b/src/main/java/com/cta4j/bus/external/route/CtaRoute.java index c32a363..0274009 100644 --- a/src/main/java/com/cta4j/bus/external/route/CtaRoute.java +++ b/src/main/java/com/cta4j/bus/external/route/CtaRoute.java @@ -10,8 +10,10 @@ public record CtaRoute( String rtnm, + String rtclr, + String rtdd, - String rtclr + String rtpidatafeed ) { } diff --git a/src/main/java/com/cta4j/bus/mapper/DetourMapper.java b/src/main/java/com/cta4j/bus/mapper/DetourMapper.java index 8be7a76..67984f4 100644 --- a/src/main/java/com/cta4j/bus/mapper/DetourMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/DetourMapper.java @@ -1,7 +1,9 @@ package com.cta4j.bus.mapper; import com.cta4j.bus.external.detour.CtaDetour; +import com.cta4j.bus.external.detour.CtaDetoursRouteDirection; import com.cta4j.bus.model.Detour; +import com.cta4j.bus.model.DetourRouteDirection; import org.jetbrains.annotations.ApiStatus; import org.mapstruct.Mapper; import org.mapstruct.Mapping; @@ -10,11 +12,15 @@ @ApiStatus.Internal public interface DetourMapper { @Mapping(source = "ver", target = "version") - @Mapping(source = "st", target = "active", qualifiedByName = "stToActive") + @Mapping(source = "st", target = "active") @Mapping(source = "desc", target = "description") @Mapping(source = "rtdirs", target = "routeDirections") - @Mapping(source = "startdt", target = "startTime", qualifiedByName = "toInstant") - @Mapping(source = "enddt", target = "endTime", qualifiedByName = "toInstant") + @Mapping(source = "startdt", target = "startTime") + @Mapping(source = "enddt", target = "endTime") @Mapping(source = "rtpidatafeed", target = "dataFeed") Detour toDomain(CtaDetour dto); + + @Mapping(source = "rt", target = "route") + @Mapping(source = "dir", target = "direction") + DetourRouteDirection toDomain(CtaDetoursRouteDirection dto); } diff --git a/src/main/java/com/cta4j/bus/mapper/RouteMapper.java b/src/main/java/com/cta4j/bus/mapper/RouteMapper.java index 9e59149..e000b63 100644 --- a/src/main/java/com/cta4j/bus/mapper/RouteMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/RouteMapper.java @@ -11,7 +11,8 @@ public interface RouteMapper { @Mapping(source = "rt", target = "id") @Mapping(source = "rtnm", target = "name") - @Mapping(source = "rtdd", target = "description") @Mapping(source = "rtclr", target = "color") + @Mapping(source = "rtdd", target = "designator") + @Mapping(source = "rtpidatafeed", target = "dataFeed") Route toDomain(CtaRoute route); } diff --git a/src/main/java/com/cta4j/bus/model/Detour.java b/src/main/java/com/cta4j/bus/model/Detour.java index 1544660..a062900 100644 --- a/src/main/java/com/cta4j/bus/model/Detour.java +++ b/src/main/java/com/cta4j/bus/model/Detour.java @@ -36,7 +36,7 @@ public record Detour( } if (description == null) { - throw new IllegalArgumentException("description must not be null"); + throw new IllegalArgumentException("designator must not be null"); } if (routeDirections == null) { diff --git a/src/main/java/com/cta4j/bus/model/Route.java b/src/main/java/com/cta4j/bus/model/Route.java index e7632e0..b7cb187 100644 --- a/src/main/java/com/cta4j/bus/model/Route.java +++ b/src/main/java/com/cta4j/bus/model/Route.java @@ -1,12 +1,37 @@ package com.cta4j.bus.model; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +@SuppressWarnings("ConstantConditions") public record Route( String id, String name, - String description, + String color, + + String designator, - String color + @Nullable + String dataFeed ) { + public Route { + if (id == null) { + throw new IllegalArgumentException("id must not be null"); + } + + if (name == null) { + throw new IllegalArgumentException("name must not be null"); + } + + if (designator == null) { + throw new IllegalArgumentException("designator must not be null"); + } + + if (color == null) { + throw new IllegalArgumentException("color must not be null"); + } + } } From 14ecfcdc3aef19f9aa561b49f37ef7607ae5fe01 Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sat, 10 Jan 2026 14:32:11 -0600 Subject: [PATCH 14/53] JSpecify + Model updates --- src/main/java/com/cta4j/bus/model/Stop.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/com/cta4j/bus/model/Stop.java b/src/main/java/com/cta4j/bus/model/Stop.java index 930e54f..7916ae5 100644 --- a/src/main/java/com/cta4j/bus/model/Stop.java +++ b/src/main/java/com/cta4j/bus/model/Stop.java @@ -1,8 +1,13 @@ package com.cta4j.bus.model; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + import java.math.BigDecimal; import java.util.List; +@NullMarked +@SuppressWarnings("ConstantConditions") public record Stop( String id, @@ -12,12 +17,16 @@ public record Stop( BigDecimal longitude, + @Nullable List detoursAdded, + @Nullable List detoursRemoved, + @Nullable Integer gtfsSequence, + @Nullable Boolean adaAccessible ) { } From e77f2eda8c365bad22f37012910f7fb96a6066c5 Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sat, 10 Jan 2026 15:15:03 -0600 Subject: [PATCH 15/53] JSpecify + Model updates --- .../bus/client/internal/BusClientImpl.java | 79 +++++++++++++------ .../bus/external/CtaBustimeResponse.java | 22 ++++++ .../com/cta4j/bus/external/CtaDirection.java | 25 ++++++ .../java/com/cta4j/bus/external/CtaError.java | 27 +++++++ .../com/cta4j/bus/external/CtaResponse.java | 19 +++++ .../java/com/cta4j/bus/external/CtaRoute.java | 41 ++++++++++ .../bus/external/{stop => }/CtaStop.java | 10 ++- .../bus/external/direction/CtaDirection.java | 13 --- .../CtaDirectionsBustimeResponse.java | 13 --- .../direction/CtaDirectionsResponse.java | 13 --- .../cta4j/bus/external/route/CtaRoute.java | 19 ----- .../route/CtaRoutesBustimeResponse.java | 13 --- .../bus/external/route/CtaRoutesResponse.java | 13 --- .../stop/CtaStopsBustimeResponse.java | 13 --- .../bus/external/stop/CtaStopsResponse.java | 13 --- .../com/cta4j/bus/mapper/RouteMapper.java | 2 +- .../java/com/cta4j/bus/mapper/StopMapper.java | 2 +- 17 files changed, 200 insertions(+), 137 deletions(-) create mode 100644 src/main/java/com/cta4j/bus/external/CtaBustimeResponse.java create mode 100644 src/main/java/com/cta4j/bus/external/CtaDirection.java create mode 100644 src/main/java/com/cta4j/bus/external/CtaError.java create mode 100644 src/main/java/com/cta4j/bus/external/CtaResponse.java create mode 100644 src/main/java/com/cta4j/bus/external/CtaRoute.java rename src/main/java/com/cta4j/bus/external/{stop => }/CtaStop.java (63%) delete mode 100644 src/main/java/com/cta4j/bus/external/direction/CtaDirection.java delete mode 100644 src/main/java/com/cta4j/bus/external/direction/CtaDirectionsBustimeResponse.java delete mode 100644 src/main/java/com/cta4j/bus/external/direction/CtaDirectionsResponse.java delete mode 100644 src/main/java/com/cta4j/bus/external/route/CtaRoute.java delete mode 100644 src/main/java/com/cta4j/bus/external/route/CtaRoutesBustimeResponse.java delete mode 100644 src/main/java/com/cta4j/bus/external/route/CtaRoutesResponse.java delete mode 100644 src/main/java/com/cta4j/bus/external/stop/CtaStopsBustimeResponse.java delete mode 100644 src/main/java/com/cta4j/bus/external/stop/CtaStopsResponse.java diff --git a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java index 9874176..0cc64dc 100644 --- a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java +++ b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java @@ -1,6 +1,9 @@ package com.cta4j.bus.client.internal; import com.cta4j.bus.client.BusClient; +import com.cta4j.bus.external.CtaBustimeResponse; +import com.cta4j.bus.external.CtaError; +import com.cta4j.bus.external.CtaResponse; import com.cta4j.bus.mapper.ArrivalMapper; import com.cta4j.bus.mapper.BusMapper; import com.cta4j.bus.mapper.DetourMapper; @@ -15,18 +18,12 @@ import com.cta4j.bus.external.detour.CtaDetour; import com.cta4j.bus.external.detour.CtaDetoursBustimeResponse; import com.cta4j.bus.external.detour.CtaDetoursResponse; -import com.cta4j.bus.external.direction.CtaDirection; -import com.cta4j.bus.external.direction.CtaDirectionsBustimeResponse; -import com.cta4j.bus.external.direction.CtaDirectionsResponse; +import com.cta4j.bus.external.CtaDirection; import com.cta4j.bus.external.prediction.CtaPredictionsBustimeResponse; import com.cta4j.bus.external.prediction.CtaPredictionsPrd; import com.cta4j.bus.external.prediction.CtaPredictionsResponse; -import com.cta4j.bus.external.route.CtaRoute; -import com.cta4j.bus.external.route.CtaRoutesBustimeResponse; -import com.cta4j.bus.external.route.CtaRoutesResponse; -import com.cta4j.bus.external.stop.CtaStop; -import com.cta4j.bus.external.stop.CtaStopsBustimeResponse; -import com.cta4j.bus.external.stop.CtaStopsResponse; +import com.cta4j.bus.external.CtaRoute; +import com.cta4j.bus.external.CtaStop; import com.cta4j.bus.external.vehicle.CtaVehicle; import com.cta4j.bus.external.vehicle.CtaVehicleBustimeResponse; import com.cta4j.bus.external.vehicle.CtaVehicleResponse; @@ -35,6 +32,7 @@ import org.jspecify.annotations.Nullable; import org.mapstruct.factory.Mappers; import tools.jackson.core.JacksonException; +import tools.jackson.core.type.TypeReference; import tools.jackson.databind.ObjectMapper; import org.apache.hc.core5.net.URIBuilder; import org.jetbrains.annotations.ApiStatus; @@ -94,23 +92,31 @@ public List getRoutes() { String response = HttpUtils.get(url); - CtaRoutesResponse routesResponse; + TypeReference> typeReference = new TypeReference<>() {}; + CtaResponse routesResponse; try { - routesResponse = this.objectMapper.readValue(response, CtaRoutesResponse.class); + routesResponse = this.objectMapper.readValue(response, typeReference); } catch (JacksonException e) { String message = "Failed to parse response from %s".formatted(ROUTES_ENDPOINT); throw new Cta4jException(message, e); } - CtaRoutesBustimeResponse bustimeResponse = routesResponse.bustimeResponse(); + CtaBustimeResponse bustimeResponse = routesResponse.bustimeResponse(); - if (bustimeResponse == null) { + List errors = bustimeResponse.error(); + List routes = bustimeResponse.data(); + + if ((errors == null) && (routes == null)) { throw new Cta4jException("Invalid response from %s".formatted(ROUTES_ENDPOINT)); } - List routes = bustimeResponse.routes(); + if ((errors != null) && !errors.isEmpty()) { + String message = this.buildErrorMessage(ROUTES_ENDPOINT, errors); + + throw new Cta4jException("Error response from %s: %s".formatted(ROUTES_ENDPOINT, message)); + } if ((routes == null) || routes.isEmpty()) { return List.of(); @@ -138,23 +144,31 @@ public List getDirections(String routeId) { String response = HttpUtils.get(url); - CtaDirectionsResponse directionsResponse; + TypeReference> typeReference = new TypeReference<>() {}; + CtaResponse directionsResponse; try { - directionsResponse = this.objectMapper.readValue(response, CtaDirectionsResponse.class); + directionsResponse = this.objectMapper.readValue(response, typeReference); } catch (JacksonException e) { String message = "Failed to parse response from %s".formatted(DIRECTIONS_ENDPOINT); throw new Cta4jException(message, e); } - CtaDirectionsBustimeResponse bustimeResponse = directionsResponse.bustimeResponse(); + CtaBustimeResponse bustimeResponse = directionsResponse.bustimeResponse(); - if (bustimeResponse == null) { + List errors = bustimeResponse.error(); + List directions = bustimeResponse.data(); + + if ((errors == null) && (directions == null)) { throw new Cta4jException("Invalid response from %s".formatted(DIRECTIONS_ENDPOINT)); } - List directions = bustimeResponse.directions(); + if ((errors != null) && !errors.isEmpty()) { + String message = this.buildErrorMessage(DIRECTIONS_ENDPOINT, errors); + + throw new Cta4jException("Error response from %s: %s".formatted(DIRECTIONS_ENDPOINT, message)); + } if ((directions == null) || directions.isEmpty()) { return List.of(); @@ -187,23 +201,31 @@ public List getStops(String routeId, String direction) { String response = HttpUtils.get(url); - CtaStopsResponse stopsResponse; + TypeReference> typeReference = new TypeReference<>() {}; + CtaResponse stopsResponse; try { - stopsResponse = this.objectMapper.readValue(response, CtaStopsResponse.class); + stopsResponse = this.objectMapper.readValue(response, typeReference); } catch (JacksonException e) { String message = "Failed to parse response from %s".formatted(STOPS_ENDPOINT); throw new Cta4jException(message, e); } - CtaStopsBustimeResponse bustimeResponse = stopsResponse.bustimeResponse(); + CtaBustimeResponse bustimeResponse = stopsResponse.bustimeResponse(); - if (bustimeResponse == null) { + List errors = bustimeResponse.error(); + List stops = bustimeResponse.data(); + + if ((errors == null) && (stops == null)) { throw new Cta4jException("Invalid response from %s".formatted(STOPS_ENDPOINT)); } - List stops = bustimeResponse.stops(); + if ((errors != null) && !errors.isEmpty()) { + String message = this.buildErrorMessage(STOPS_ENDPOINT, errors); + + throw new Cta4jException("Error response from %s: %s".formatted(STOPS_ENDPOINT, message)); + } if ((stops == null) || stops.isEmpty()) { return List.of(); @@ -356,6 +378,15 @@ public Optional getBus(String id) { return Optional.of(bus); } + private String buildErrorMessage(String endpoint, List errors) { + String message = errors.stream() + .map(CtaError::msg) + .reduce("%s; %s"::formatted) + .orElse("Unknown error"); + + return "Error response from %s: %s".formatted(endpoint, message); + } + private List getArrivals(String url) { String response = HttpUtils.get(url); diff --git a/src/main/java/com/cta4j/bus/external/CtaBustimeResponse.java b/src/main/java/com/cta4j/bus/external/CtaBustimeResponse.java new file mode 100644 index 0000000..bddd0e6 --- /dev/null +++ b/src/main/java/com/cta4j/bus/external/CtaBustimeResponse.java @@ -0,0 +1,22 @@ +package com.cta4j.bus.external; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.util.List; + +@NullMarked +@JsonIgnoreProperties(ignoreUnknown = true) +public record CtaBustimeResponse( + @Nullable + List error, + + @Nullable + @JsonAlias({ + "routes" + }) + List data +) { +} diff --git a/src/main/java/com/cta4j/bus/external/CtaDirection.java b/src/main/java/com/cta4j/bus/external/CtaDirection.java new file mode 100644 index 0000000..05d0729 --- /dev/null +++ b/src/main/java/com/cta4j/bus/external/CtaDirection.java @@ -0,0 +1,25 @@ +package com.cta4j.bus.external; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; + +@NullMarked +@ApiStatus.Internal +@SuppressWarnings("ConstantConditions") +@JsonIgnoreProperties(ignoreUnknown = true) +public record CtaDirection( + String id, + + String name +) { + public CtaDirection { + if (id == null) { + throw new IllegalArgumentException("id must not be null"); + } + + if (name == null) { + throw new IllegalArgumentException("name must not be null"); + } + } +} diff --git a/src/main/java/com/cta4j/bus/external/CtaError.java b/src/main/java/com/cta4j/bus/external/CtaError.java new file mode 100644 index 0000000..01ce54e --- /dev/null +++ b/src/main/java/com/cta4j/bus/external/CtaError.java @@ -0,0 +1,27 @@ +package com.cta4j.bus.external; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +@SuppressWarnings("ConstantConditions") +@JsonIgnoreProperties(ignoreUnknown = true) +public record CtaError( + @Nullable + String rtpidatafeed, + + @Nullable + String rt, + + @Nullable + String dir, + + String msg +) { + public CtaError { + if (msg == null) { + throw new IllegalArgumentException("msg must not be null"); + } + } +} diff --git a/src/main/java/com/cta4j/bus/external/CtaResponse.java b/src/main/java/com/cta4j/bus/external/CtaResponse.java new file mode 100644 index 0000000..7ccb649 --- /dev/null +++ b/src/main/java/com/cta4j/bus/external/CtaResponse.java @@ -0,0 +1,19 @@ +package com.cta4j.bus.external; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.jspecify.annotations.NullMarked; + +@NullMarked +@SuppressWarnings("ConstantConditions") +@JsonIgnoreProperties(ignoreUnknown = true) +public record CtaResponse( + @JsonProperty("bustime-response") + CtaBustimeResponse bustimeResponse +) { + public CtaResponse { + if (bustimeResponse == null) { + throw new IllegalArgumentException("bustimeResponse must not be null"); + } + } +} diff --git a/src/main/java/com/cta4j/bus/external/CtaRoute.java b/src/main/java/com/cta4j/bus/external/CtaRoute.java new file mode 100644 index 0000000..b80a77b --- /dev/null +++ b/src/main/java/com/cta4j/bus/external/CtaRoute.java @@ -0,0 +1,41 @@ +package com.cta4j.bus.external; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +@ApiStatus.Internal +@SuppressWarnings("ConstantConditions") +@JsonIgnoreProperties(ignoreUnknown = true) +public record CtaRoute( + String rt, + + String rtnm, + + String rtclr, + + String rtdd, + + @Nullable + String rtpidatafeed +) { + public CtaRoute { + if (rt == null) { + throw new IllegalArgumentException("rt must not be null"); + } + + if (rtnm == null) { + throw new IllegalArgumentException("rtnm must not be null"); + } + + if (rtclr == null) { + throw new IllegalArgumentException("rtclr must not be null"); + } + + if (rtdd == null) { + throw new IllegalArgumentException("rtdd must not be null"); + } + } +} diff --git a/src/main/java/com/cta4j/bus/external/stop/CtaStop.java b/src/main/java/com/cta4j/bus/external/CtaStop.java similarity index 63% rename from src/main/java/com/cta4j/bus/external/stop/CtaStop.java rename to src/main/java/com/cta4j/bus/external/CtaStop.java index b3040f0..14724fa 100644 --- a/src/main/java/com/cta4j/bus/external/stop/CtaStop.java +++ b/src/main/java/com/cta4j/bus/external/CtaStop.java @@ -1,11 +1,15 @@ -package com.cta4j.bus.external.stop; +package com.cta4j.bus.external; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.List; +@NullMarked @ApiStatus.Internal +@SuppressWarnings("ConstantConditions") @JsonIgnoreProperties(ignoreUnknown = true) public record CtaStop( String stpid, @@ -16,12 +20,16 @@ public record CtaStop( Double lon, + @Nullable List dtradd, + @Nullable List dtrrem, + @Nullable Integer gtfsseq, + @Nullable Boolean ada ) { } diff --git a/src/main/java/com/cta4j/bus/external/direction/CtaDirection.java b/src/main/java/com/cta4j/bus/external/direction/CtaDirection.java deleted file mode 100644 index c107782..0000000 --- a/src/main/java/com/cta4j/bus/external/direction/CtaDirection.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.cta4j.bus.external.direction; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import org.jetbrains.annotations.ApiStatus; - -@ApiStatus.Internal -@JsonIgnoreProperties(ignoreUnknown = true) -public record CtaDirection( - String id, - - String name -) { -} diff --git a/src/main/java/com/cta4j/bus/external/direction/CtaDirectionsBustimeResponse.java b/src/main/java/com/cta4j/bus/external/direction/CtaDirectionsBustimeResponse.java deleted file mode 100644 index 06507c7..0000000 --- a/src/main/java/com/cta4j/bus/external/direction/CtaDirectionsBustimeResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.cta4j.bus.external.direction; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import org.jetbrains.annotations.ApiStatus; - -import java.util.List; - -@ApiStatus.Internal -@JsonIgnoreProperties(ignoreUnknown = true) -public record CtaDirectionsBustimeResponse( - List directions -) { -} diff --git a/src/main/java/com/cta4j/bus/external/direction/CtaDirectionsResponse.java b/src/main/java/com/cta4j/bus/external/direction/CtaDirectionsResponse.java deleted file mode 100644 index a8236c6..0000000 --- a/src/main/java/com/cta4j/bus/external/direction/CtaDirectionsResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.cta4j.bus.external.direction; - -import com.fasterxml.jackson.annotation.JsonAlias; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import org.jetbrains.annotations.ApiStatus; - -@ApiStatus.Internal -@JsonIgnoreProperties(ignoreUnknown = true) -public record CtaDirectionsResponse( - @JsonAlias("bustime-response") - CtaDirectionsBustimeResponse bustimeResponse -) { -} diff --git a/src/main/java/com/cta4j/bus/external/route/CtaRoute.java b/src/main/java/com/cta4j/bus/external/route/CtaRoute.java deleted file mode 100644 index 0274009..0000000 --- a/src/main/java/com/cta4j/bus/external/route/CtaRoute.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.cta4j.bus.external.route; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import org.jetbrains.annotations.ApiStatus; - -@ApiStatus.Internal -@JsonIgnoreProperties(ignoreUnknown = true) -public record CtaRoute( - String rt, - - String rtnm, - - String rtclr, - - String rtdd, - - String rtpidatafeed -) { -} diff --git a/src/main/java/com/cta4j/bus/external/route/CtaRoutesBustimeResponse.java b/src/main/java/com/cta4j/bus/external/route/CtaRoutesBustimeResponse.java deleted file mode 100644 index 1d88dfb..0000000 --- a/src/main/java/com/cta4j/bus/external/route/CtaRoutesBustimeResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.cta4j.bus.external.route; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import org.jetbrains.annotations.ApiStatus; - -import java.util.List; - -@ApiStatus.Internal -@JsonIgnoreProperties(ignoreUnknown = true) -public record CtaRoutesBustimeResponse( - List routes -) { -} diff --git a/src/main/java/com/cta4j/bus/external/route/CtaRoutesResponse.java b/src/main/java/com/cta4j/bus/external/route/CtaRoutesResponse.java deleted file mode 100644 index 3e9e743..0000000 --- a/src/main/java/com/cta4j/bus/external/route/CtaRoutesResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.cta4j.bus.external.route; - -import com.fasterxml.jackson.annotation.JsonAlias; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import org.jetbrains.annotations.ApiStatus; - -@ApiStatus.Internal -@JsonIgnoreProperties(ignoreUnknown = true) -public record CtaRoutesResponse( - @JsonAlias("bustime-response") - CtaRoutesBustimeResponse bustimeResponse -) { -} diff --git a/src/main/java/com/cta4j/bus/external/stop/CtaStopsBustimeResponse.java b/src/main/java/com/cta4j/bus/external/stop/CtaStopsBustimeResponse.java deleted file mode 100644 index 0ae0707..0000000 --- a/src/main/java/com/cta4j/bus/external/stop/CtaStopsBustimeResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.cta4j.bus.external.stop; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import org.jetbrains.annotations.ApiStatus; - -import java.util.List; - -@ApiStatus.Internal -@JsonIgnoreProperties(ignoreUnknown = true) -public record CtaStopsBustimeResponse( - List stops -) { -} diff --git a/src/main/java/com/cta4j/bus/external/stop/CtaStopsResponse.java b/src/main/java/com/cta4j/bus/external/stop/CtaStopsResponse.java deleted file mode 100644 index e561262..0000000 --- a/src/main/java/com/cta4j/bus/external/stop/CtaStopsResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.cta4j.bus.external.stop; - -import com.fasterxml.jackson.annotation.JsonAlias; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import org.jetbrains.annotations.ApiStatus; - -@ApiStatus.Internal -@JsonIgnoreProperties(ignoreUnknown = true) -public record CtaStopsResponse( - @JsonAlias("bustime-response") - CtaStopsBustimeResponse bustimeResponse -) { -} diff --git a/src/main/java/com/cta4j/bus/mapper/RouteMapper.java b/src/main/java/com/cta4j/bus/mapper/RouteMapper.java index e000b63..2fe2434 100644 --- a/src/main/java/com/cta4j/bus/mapper/RouteMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/RouteMapper.java @@ -1,6 +1,6 @@ package com.cta4j.bus.mapper; -import com.cta4j.bus.external.route.CtaRoute; +import com.cta4j.bus.external.CtaRoute; import com.cta4j.bus.model.Route; import org.jetbrains.annotations.ApiStatus; import org.mapstruct.Mapper; diff --git a/src/main/java/com/cta4j/bus/mapper/StopMapper.java b/src/main/java/com/cta4j/bus/mapper/StopMapper.java index a34b2a3..251e1f1 100644 --- a/src/main/java/com/cta4j/bus/mapper/StopMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/StopMapper.java @@ -1,6 +1,6 @@ package com.cta4j.bus.mapper; -import com.cta4j.bus.external.stop.CtaStop; +import com.cta4j.bus.external.CtaStop; import com.cta4j.bus.model.Stop; import org.jetbrains.annotations.ApiStatus; import org.mapstruct.Mapper; From 5b75b99ea9a023fa4614c16f45ce9660b211c62a Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sat, 10 Jan 2026 15:25:10 -0600 Subject: [PATCH 16/53] JSpecify + Model updates --- .../bus/client/internal/BusClientImpl.java | 30 +++-- .../com/cta4j/bus/external/CtaPrediction.java | 120 ++++++++++++++++++ .../CtaPredictionsBustimeResponse.java | 13 -- .../prediction/CtaPredictionsPrd.java | 55 -------- .../prediction/CtaPredictionsResponse.java | 13 -- .../com/cta4j/bus/mapper/ArrivalMapper.java | 4 +- 6 files changed, 140 insertions(+), 95 deletions(-) create mode 100644 src/main/java/com/cta4j/bus/external/CtaPrediction.java delete mode 100644 src/main/java/com/cta4j/bus/external/prediction/CtaPredictionsBustimeResponse.java delete mode 100644 src/main/java/com/cta4j/bus/external/prediction/CtaPredictionsPrd.java delete mode 100644 src/main/java/com/cta4j/bus/external/prediction/CtaPredictionsResponse.java diff --git a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java index 0cc64dc..191e28c 100644 --- a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java +++ b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java @@ -4,6 +4,7 @@ import com.cta4j.bus.external.CtaBustimeResponse; import com.cta4j.bus.external.CtaError; import com.cta4j.bus.external.CtaResponse; +import com.cta4j.bus.external.CtaPrediction; import com.cta4j.bus.mapper.ArrivalMapper; import com.cta4j.bus.mapper.BusMapper; import com.cta4j.bus.mapper.DetourMapper; @@ -19,9 +20,6 @@ import com.cta4j.bus.external.detour.CtaDetoursBustimeResponse; import com.cta4j.bus.external.detour.CtaDetoursResponse; import com.cta4j.bus.external.CtaDirection; -import com.cta4j.bus.external.prediction.CtaPredictionsBustimeResponse; -import com.cta4j.bus.external.prediction.CtaPredictionsPrd; -import com.cta4j.bus.external.prediction.CtaPredictionsResponse; import com.cta4j.bus.external.CtaRoute; import com.cta4j.bus.external.CtaStop; import com.cta4j.bus.external.vehicle.CtaVehicle; @@ -390,31 +388,39 @@ private String buildErrorMessage(String endpoint, List errors) { private List getArrivals(String url) { String response = HttpUtils.get(url); - CtaPredictionsResponse predictionsResponse; + TypeReference> typeReference = new TypeReference<>() {}; + CtaResponse predictionsResponse; try { - predictionsResponse = this.objectMapper.readValue(response, CtaPredictionsResponse.class); + predictionsResponse = this.objectMapper.readValue(response, typeReference); } catch (JacksonException e) { String message = "Failed to parse response from %s".formatted(PREDICTIONS_ENDPOINT); throw new Cta4jException(message, e); } - CtaPredictionsBustimeResponse bustimeResponse = predictionsResponse.bustimeResponse(); + CtaBustimeResponse bustimeResponse = predictionsResponse.bustimeResponse(); - if (bustimeResponse == null) { + List errors = bustimeResponse.error(); + List predictions = bustimeResponse.data(); + + if ((errors == null) && (predictions == null)) { throw new Cta4jException("Invalid response from %s".formatted(PREDICTIONS_ENDPOINT)); } - List prd = bustimeResponse.prd(); + if ((errors != null) && !errors.isEmpty()) { + String message = this.buildErrorMessage(PREDICTIONS_ENDPOINT, errors); + + throw new Cta4jException("Error response from %s: %s".formatted(PREDICTIONS_ENDPOINT, message)); + } - if ((prd == null) || prd.isEmpty()) { + if ((predictions == null) || predictions.isEmpty()) { return List.of(); } - return prd.stream() - .map(this.arrivalMapper::toDomain) - .toList(); + return predictions.stream() + .map(this.arrivalMapper::toDomain) + .toList(); } public static final class BuilderImpl implements BusClient.Builder { diff --git a/src/main/java/com/cta4j/bus/external/CtaPrediction.java b/src/main/java/com/cta4j/bus/external/CtaPrediction.java new file mode 100644 index 0000000..3fe6049 --- /dev/null +++ b/src/main/java/com/cta4j/bus/external/CtaPrediction.java @@ -0,0 +1,120 @@ +package com.cta4j.bus.external; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +@ApiStatus.Internal +@SuppressWarnings("ConstantConditions") +@JsonIgnoreProperties(ignoreUnknown = true) +public record CtaPrediction( + String tmstmp, + + String typ, + + String stpid, + + String stpnm, + + int vid, + + int dstp, + + String rt, + + String rtdd, + + String rtdir, + + String des, + + String prdtm, + + @Nullable + Boolean dly, + + int dyn, + + String tablockid, + + String tatripid, + + String origtatripno, + + String zone, + + String psgld, + + int gtfsseq, + + @Nullable + String nbus, + + @Nullable + Integer stst, + + @Nullable + String stsd, + + int flagstop +) { + public CtaPrediction { + if (tmstmp == null) { + throw new IllegalArgumentException("tmstmp must not be null"); + } + + if (typ == null) { + throw new IllegalArgumentException("typ must not be null"); + } + + if (stpid == null) { + throw new IllegalArgumentException("stpid must not be null"); + } + + if (stpnm == null) { + throw new IllegalArgumentException("stpnm must not be null"); + } + + if (rt == null) { + throw new IllegalArgumentException("rt must not be null"); + } + + if (rtdd == null) { + throw new IllegalArgumentException("rtdd must not be null"); + } + + if (rtdir == null) { + throw new IllegalArgumentException("rtdir must not be null"); + } + + if (des == null) { + throw new IllegalArgumentException("des must not be null"); + } + + if (prdtm == null) { + throw new IllegalArgumentException("prdtm must not be null"); + } + + if (tablockid == null) { + throw new IllegalArgumentException("tablockid must not be null"); + } + + if (tatripid == null) { + throw new IllegalArgumentException("tatripid must not be null"); + } + + if (origtatripno == null) { + throw new IllegalArgumentException("origtatripno must not be null"); + } + + if (zone == null) { + throw new IllegalArgumentException("zone must not be null"); + } + + if (psgld == null) { + throw new IllegalArgumentException("psgld must not be null"); + } + } +} diff --git a/src/main/java/com/cta4j/bus/external/prediction/CtaPredictionsBustimeResponse.java b/src/main/java/com/cta4j/bus/external/prediction/CtaPredictionsBustimeResponse.java deleted file mode 100644 index 973e787..0000000 --- a/src/main/java/com/cta4j/bus/external/prediction/CtaPredictionsBustimeResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.cta4j.bus.external.prediction; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import org.jetbrains.annotations.ApiStatus; - -import java.util.List; - -@ApiStatus.Internal -@JsonIgnoreProperties(ignoreUnknown = true) -public record CtaPredictionsBustimeResponse( - List prd -) { -} diff --git a/src/main/java/com/cta4j/bus/external/prediction/CtaPredictionsPrd.java b/src/main/java/com/cta4j/bus/external/prediction/CtaPredictionsPrd.java deleted file mode 100644 index 720fa15..0000000 --- a/src/main/java/com/cta4j/bus/external/prediction/CtaPredictionsPrd.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.cta4j.bus.external.prediction; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import org.jetbrains.annotations.ApiStatus; - -@ApiStatus.Internal -@JsonIgnoreProperties(ignoreUnknown = true) -public record CtaPredictionsPrd( - String tmstmp, - - String typ, - - String stpid, - - String stpnm, - - int vid, - - int dstp, - - String rt, - - String rtdd, - - String rtdir, - - String des, - - String prdtm, - - Boolean dly, - - int dyn, - - String tablockid, - - String tatripid, - - String origtatripno, - - String zone, - - String psgld, - - int gtfsseq, - - String nbus, - - Integer stst, - - String stsd, - - int flagstop -) { -} diff --git a/src/main/java/com/cta4j/bus/external/prediction/CtaPredictionsResponse.java b/src/main/java/com/cta4j/bus/external/prediction/CtaPredictionsResponse.java deleted file mode 100644 index 9dcdadb..0000000 --- a/src/main/java/com/cta4j/bus/external/prediction/CtaPredictionsResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.cta4j.bus.external.prediction; - -import com.fasterxml.jackson.annotation.JsonAlias; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import org.jetbrains.annotations.ApiStatus; - -@ApiStatus.Internal -@JsonIgnoreProperties(ignoreUnknown = true) -public record CtaPredictionsResponse( - @JsonAlias("bustime-response") - CtaPredictionsBustimeResponse bustimeResponse -) { -} diff --git a/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java b/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java index 470c74a..b0ff5b1 100644 --- a/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java @@ -1,6 +1,6 @@ package com.cta4j.bus.mapper; -import com.cta4j.bus.external.prediction.CtaPredictionsPrd; +import com.cta4j.bus.external.CtaPrediction; import com.cta4j.bus.model.Arrival; import org.jetbrains.annotations.ApiStatus; import org.mapstruct.Mapper; @@ -32,5 +32,5 @@ public interface ArrivalMapper { @Mapping(source = "stst", target = "metadata.stopStatus") @Mapping(source = "stsd", target = "metadata.stopStatusDescription") @Mapping(source = "flagstop", target = "metadata.flagStop") - Arrival toDomain(CtaPredictionsPrd prediction); + Arrival toDomain(CtaPrediction prediction); } From 08dc1c58b70e87b98fd8ef1ad88954aec66b5e42 Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sat, 10 Jan 2026 15:40:46 -0600 Subject: [PATCH 17/53] JSpecify + Model updates --- .../bus/client/internal/BusClientImpl.java | 48 +++++--- .../bus/external/CtaBustimeResponse.java | 7 +- .../com/cta4j/bus/external/CtaDetour.java | 59 +++++++++ .../external/CtaDetoursRouteDirection.java | 25 ++++ .../com/cta4j/bus/external/CtaVehicle.java | 114 ++++++++++++++++++ .../cta4j/bus/external/detour/CtaDetour.java | 27 ----- .../detour/CtaDetoursBustimeResponse.java | 13 -- .../external/detour/CtaDetoursResponse.java | 13 -- .../detour/CtaDetoursRouteDirection.java | 13 -- .../bus/external/vehicle/CtaVehicle.java | 63 ---------- .../vehicle/CtaVehicleBustimeResponse.java | 13 -- .../external/vehicle/CtaVehicleResponse.java | 13 -- .../java/com/cta4j/bus/mapper/BusMapper.java | 2 +- .../com/cta4j/bus/mapper/DetourMapper.java | 4 +- 14 files changed, 237 insertions(+), 177 deletions(-) create mode 100644 src/main/java/com/cta4j/bus/external/CtaDetour.java create mode 100644 src/main/java/com/cta4j/bus/external/CtaDetoursRouteDirection.java create mode 100644 src/main/java/com/cta4j/bus/external/CtaVehicle.java delete mode 100644 src/main/java/com/cta4j/bus/external/detour/CtaDetour.java delete mode 100644 src/main/java/com/cta4j/bus/external/detour/CtaDetoursBustimeResponse.java delete mode 100644 src/main/java/com/cta4j/bus/external/detour/CtaDetoursResponse.java delete mode 100644 src/main/java/com/cta4j/bus/external/detour/CtaDetoursRouteDirection.java delete mode 100644 src/main/java/com/cta4j/bus/external/vehicle/CtaVehicle.java delete mode 100644 src/main/java/com/cta4j/bus/external/vehicle/CtaVehicleBustimeResponse.java delete mode 100644 src/main/java/com/cta4j/bus/external/vehicle/CtaVehicleResponse.java diff --git a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java index 191e28c..b84b29e 100644 --- a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java +++ b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java @@ -16,15 +16,11 @@ import com.cta4j.bus.model.Route; import com.cta4j.bus.model.Stop; import com.cta4j.exception.Cta4jException; -import com.cta4j.bus.external.detour.CtaDetour; -import com.cta4j.bus.external.detour.CtaDetoursBustimeResponse; -import com.cta4j.bus.external.detour.CtaDetoursResponse; +import com.cta4j.bus.external.CtaDetour; import com.cta4j.bus.external.CtaDirection; import com.cta4j.bus.external.CtaRoute; import com.cta4j.bus.external.CtaStop; -import com.cta4j.bus.external.vehicle.CtaVehicle; -import com.cta4j.bus.external.vehicle.CtaVehicleBustimeResponse; -import com.cta4j.bus.external.vehicle.CtaVehicleResponse; +import com.cta4j.bus.external.CtaVehicle; import com.cta4j.util.HttpUtils; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -297,29 +293,37 @@ public List getDetours(String routeId, String direction) { String response = HttpUtils.get(url); - CtaDetoursResponse detoursResponse; + TypeReference> typeReference = new TypeReference<>() {}; + CtaResponse detoursResponse; try { - detoursResponse = this.objectMapper.readValue(response, CtaDetoursResponse.class); + detoursResponse = this.objectMapper.readValue(response, typeReference); } catch (JacksonException e) { String message = "Failed to parse response from %s".formatted(DETOURS_ENDPOINT); throw new Cta4jException(message, e); } - CtaDetoursBustimeResponse bustimeResponse = detoursResponse.bustimeResponse(); + CtaBustimeResponse bustimeResponse = detoursResponse.bustimeResponse(); - if (bustimeResponse == null) { + List errors = bustimeResponse.error(); + List detours = bustimeResponse.data(); + + if ((errors == null) && (detours == null)) { throw new Cta4jException("Invalid response from %s".formatted(DETOURS_ENDPOINT)); } - List dtrs = bustimeResponse.dtrs(); + if ((errors != null) && !errors.isEmpty()) { + String message = this.buildErrorMessage(DETOURS_ENDPOINT, errors); + + throw new Cta4jException("Error response from %s: %s".formatted(DETOURS_ENDPOINT, message)); + } - if ((dtrs == null) || dtrs.isEmpty()) { + if ((detours == null) || detours.isEmpty()) { return List.of(); } - return dtrs.stream() + return detours.stream() .map(this.detourMapper::toDomain) .toList(); } @@ -341,23 +345,31 @@ public Optional getBus(String id) { String response = HttpUtils.get(url); - CtaVehicleResponse vehicleResponse; + TypeReference> typeReference = new TypeReference<>() {}; + CtaResponse vehicleResponse; try { - vehicleResponse = this.objectMapper.readValue(response, CtaVehicleResponse.class); + vehicleResponse = this.objectMapper.readValue(response, typeReference); } catch (JacksonException e) { String message = "Failed to parse response from %s".formatted(VEHICLES_ENDPOINT); throw new Cta4jException(message, e); } - CtaVehicleBustimeResponse bustimeResponse = vehicleResponse.bustimeResponse(); + CtaBustimeResponse bustimeResponse = vehicleResponse.bustimeResponse(); - if (bustimeResponse == null) { + List errors = bustimeResponse.error(); + List vehicles = bustimeResponse.data(); + + if ((errors == null) && (vehicleResponse == null)) { throw new Cta4jException("Invalid response from %s".formatted(VEHICLES_ENDPOINT)); } - List vehicles = bustimeResponse.vehicle(); + if ((errors != null) && !errors.isEmpty()) { + String message = this.buildErrorMessage(VEHICLES_ENDPOINT, errors); + + throw new Cta4jException("Error response from %s: %s".formatted(VEHICLES_ENDPOINT, message)); + } if ((vehicles == null) || vehicles.isEmpty()) { return Optional.empty(); diff --git a/src/main/java/com/cta4j/bus/external/CtaBustimeResponse.java b/src/main/java/com/cta4j/bus/external/CtaBustimeResponse.java index bddd0e6..4362230 100644 --- a/src/main/java/com/cta4j/bus/external/CtaBustimeResponse.java +++ b/src/main/java/com/cta4j/bus/external/CtaBustimeResponse.java @@ -15,7 +15,12 @@ public record CtaBustimeResponse( @Nullable @JsonAlias({ - "routes" + "routes", + "directions", + "stops", + "prd", + "dtrs", + "vehicle", }) List data ) { diff --git a/src/main/java/com/cta4j/bus/external/CtaDetour.java b/src/main/java/com/cta4j/bus/external/CtaDetour.java new file mode 100644 index 0000000..7af39d8 --- /dev/null +++ b/src/main/java/com/cta4j/bus/external/CtaDetour.java @@ -0,0 +1,59 @@ +package com.cta4j.bus.external; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.util.List; + +@NullMarked +@ApiStatus.Internal +@SuppressWarnings("ConstantConditions") +@JsonIgnoreProperties(ignoreUnknown = true) +public record CtaDetour( + String id, + + int ver, + + int st, + + String desc, + + List rtdirs, + + String startdt, + + String enddt, + + @Nullable + String rtpidatafeed +) { + public CtaDetour { + if (id == null) { + throw new IllegalArgumentException("id must not be null"); + } + + if (desc == null) { + throw new IllegalArgumentException("desc must not be null"); + } + + if (rtdirs == null) { + throw new IllegalArgumentException("rtdirs must not be null"); + } + + if (startdt == null) { + throw new IllegalArgumentException("startdt must not be null"); + } + + if (enddt == null) { + throw new IllegalArgumentException("enddt must not be null"); + } + + for (CtaDetoursRouteDirection rtdir : rtdirs) { + if (rtdir == null) { + throw new IllegalArgumentException("rtdirs must not contain null elements"); + } + } + } +} diff --git a/src/main/java/com/cta4j/bus/external/CtaDetoursRouteDirection.java b/src/main/java/com/cta4j/bus/external/CtaDetoursRouteDirection.java new file mode 100644 index 0000000..2c6a3c5 --- /dev/null +++ b/src/main/java/com/cta4j/bus/external/CtaDetoursRouteDirection.java @@ -0,0 +1,25 @@ +package com.cta4j.bus.external; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; + +@NullMarked +@ApiStatus.Internal +@SuppressWarnings("ConstantConditions") +@JsonIgnoreProperties(ignoreUnknown = true) +public record CtaDetoursRouteDirection( + String rt, + + String dir +) { + public CtaDetoursRouteDirection { + if (rt == null) { + throw new IllegalArgumentException("rt must not be null"); + } + + if (dir == null) { + throw new IllegalArgumentException("dir must not be null"); + } + } +} diff --git a/src/main/java/com/cta4j/bus/external/CtaVehicle.java b/src/main/java/com/cta4j/bus/external/CtaVehicle.java new file mode 100644 index 0000000..25db096 --- /dev/null +++ b/src/main/java/com/cta4j/bus/external/CtaVehicle.java @@ -0,0 +1,114 @@ +package com.cta4j.bus.external; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +@ApiStatus.Internal +@SuppressWarnings("ConstantConditions") +@JsonIgnoreProperties(ignoreUnknown = true) +public record CtaVehicle( + String vid, + + @Nullable + String rtpidatafeed, + + String tmpstmp, + + double lat, + + double lon, + + int hdg, + + int pid, + + String rt, + + String des, + + int pdist, + + @Nullable + Integer stopstatus, + + @Nullable + Integer timepointid, + + @Nullable + String stopid, + + @Nullable + Integer sequence, + + @Nullable + Integer gtfsseq, + + boolean dly, + + @Nullable + String srvtmstmp, + + int spd, + + @Nullable + Integer blk, + + String tablockid, + + String tatripid, + + String origtatripno, + + String zone, + + int mode, + + String psgld, + + @Nullable + Integer stst, + + @Nullable + String stsd +) { + public CtaVehicle { + if (vid == null) { + throw new IllegalArgumentException("vid must not be null"); + } + + if (tmpstmp == null) { + throw new IllegalArgumentException("tmpstmp must not be null"); + } + + if (rt == null) { + throw new IllegalArgumentException("rt must not be null"); + } + + if (des == null) { + throw new IllegalArgumentException("des must not be null"); + } + + if (tablockid == null) { + throw new IllegalArgumentException("tablockid must not be null"); + } + + if (tatripid == null) { + throw new IllegalArgumentException("tatripid must not be null"); + } + + if (origtatripno == null) { + throw new IllegalArgumentException("origtatripno must not be null"); + } + + if (zone == null) { + throw new IllegalArgumentException("zone must not be null"); + } + + if (psgld == null) { + throw new IllegalArgumentException("psgld must not be null"); + } + } +} diff --git a/src/main/java/com/cta4j/bus/external/detour/CtaDetour.java b/src/main/java/com/cta4j/bus/external/detour/CtaDetour.java deleted file mode 100644 index d11fabc..0000000 --- a/src/main/java/com/cta4j/bus/external/detour/CtaDetour.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.cta4j.bus.external.detour; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import org.jetbrains.annotations.ApiStatus; - -import java.util.List; - -@ApiStatus.Internal -@JsonIgnoreProperties(ignoreUnknown = true) -public record CtaDetour( - String id, - - String ver, - - String st, - - String desc, - - List rtdirs, - - String startdt, - - String enddt, - - String rtpidatafeed -) { -} diff --git a/src/main/java/com/cta4j/bus/external/detour/CtaDetoursBustimeResponse.java b/src/main/java/com/cta4j/bus/external/detour/CtaDetoursBustimeResponse.java deleted file mode 100644 index 940250c..0000000 --- a/src/main/java/com/cta4j/bus/external/detour/CtaDetoursBustimeResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.cta4j.bus.external.detour; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import org.jetbrains.annotations.ApiStatus; - -import java.util.List; - -@ApiStatus.Internal -@JsonIgnoreProperties(ignoreUnknown = true) -public record CtaDetoursBustimeResponse( - List dtrs -) { -} diff --git a/src/main/java/com/cta4j/bus/external/detour/CtaDetoursResponse.java b/src/main/java/com/cta4j/bus/external/detour/CtaDetoursResponse.java deleted file mode 100644 index afa7d3d..0000000 --- a/src/main/java/com/cta4j/bus/external/detour/CtaDetoursResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.cta4j.bus.external.detour; - -import com.fasterxml.jackson.annotation.JsonAlias; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import org.jetbrains.annotations.ApiStatus; - -@ApiStatus.Internal -@JsonIgnoreProperties(ignoreUnknown = true) -public record CtaDetoursResponse( - @JsonAlias("bustime-response") - CtaDetoursBustimeResponse bustimeResponse -) { -} diff --git a/src/main/java/com/cta4j/bus/external/detour/CtaDetoursRouteDirection.java b/src/main/java/com/cta4j/bus/external/detour/CtaDetoursRouteDirection.java deleted file mode 100644 index 64e6ff6..0000000 --- a/src/main/java/com/cta4j/bus/external/detour/CtaDetoursRouteDirection.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.cta4j.bus.external.detour; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import org.jetbrains.annotations.ApiStatus; - -@ApiStatus.Internal -@JsonIgnoreProperties(ignoreUnknown = true) -public record CtaDetoursRouteDirection( - String rt, - - String dir -) { -} diff --git a/src/main/java/com/cta4j/bus/external/vehicle/CtaVehicle.java b/src/main/java/com/cta4j/bus/external/vehicle/CtaVehicle.java deleted file mode 100644 index 92ad266..0000000 --- a/src/main/java/com/cta4j/bus/external/vehicle/CtaVehicle.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.cta4j.bus.external.vehicle; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import org.jetbrains.annotations.ApiStatus; - -@ApiStatus.Internal -@JsonIgnoreProperties(ignoreUnknown = true) -public record CtaVehicle( - String vid, - - String rtpidatafeed, - - String tmpstmp, - - Double lat, - - Double lon, - - Integer hdg, - - Integer pid, - - String rt, - - String des, - - Integer pdist, - - Byte stopstatus, - - Integer timepointid, - - String stopid, - - Integer sequence, - - Integer gtfsseq, - - Boolean dly, - - String srvtmstmp, - - Integer spd, - - Integer blk, - - String tablockid, - - String tatripid, - - String origtatripno, - - String zone, - - Integer mode, - - String psgld, - - Integer stst, - - String stsd -) { -} diff --git a/src/main/java/com/cta4j/bus/external/vehicle/CtaVehicleBustimeResponse.java b/src/main/java/com/cta4j/bus/external/vehicle/CtaVehicleBustimeResponse.java deleted file mode 100644 index a121eb1..0000000 --- a/src/main/java/com/cta4j/bus/external/vehicle/CtaVehicleBustimeResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.cta4j.bus.external.vehicle; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import org.jetbrains.annotations.ApiStatus; - -import java.util.List; - -@ApiStatus.Internal -@JsonIgnoreProperties(ignoreUnknown = true) -public record CtaVehicleBustimeResponse( - List vehicle -) { -} diff --git a/src/main/java/com/cta4j/bus/external/vehicle/CtaVehicleResponse.java b/src/main/java/com/cta4j/bus/external/vehicle/CtaVehicleResponse.java deleted file mode 100644 index c7de011..0000000 --- a/src/main/java/com/cta4j/bus/external/vehicle/CtaVehicleResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.cta4j.bus.external.vehicle; - -import com.fasterxml.jackson.annotation.JsonAlias; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import org.jetbrains.annotations.ApiStatus; - -@ApiStatus.Internal -@JsonIgnoreProperties(ignoreUnknown = true) -public record CtaVehicleResponse( - @JsonAlias("bustime-response") - CtaVehicleBustimeResponse bustimeResponse -) { -} diff --git a/src/main/java/com/cta4j/bus/mapper/BusMapper.java b/src/main/java/com/cta4j/bus/mapper/BusMapper.java index ae1758d..0193f7e 100644 --- a/src/main/java/com/cta4j/bus/mapper/BusMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/BusMapper.java @@ -1,6 +1,6 @@ package com.cta4j.bus.mapper; -import com.cta4j.bus.external.vehicle.CtaVehicle; +import com.cta4j.bus.external.CtaVehicle; import com.cta4j.bus.model.Bus; import org.jetbrains.annotations.ApiStatus; import org.mapstruct.Mapper; diff --git a/src/main/java/com/cta4j/bus/mapper/DetourMapper.java b/src/main/java/com/cta4j/bus/mapper/DetourMapper.java index 67984f4..e3c69c3 100644 --- a/src/main/java/com/cta4j/bus/mapper/DetourMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/DetourMapper.java @@ -1,7 +1,7 @@ package com.cta4j.bus.mapper; -import com.cta4j.bus.external.detour.CtaDetour; -import com.cta4j.bus.external.detour.CtaDetoursRouteDirection; +import com.cta4j.bus.external.CtaDetour; +import com.cta4j.bus.external.CtaDetoursRouteDirection; import com.cta4j.bus.model.Detour; import com.cta4j.bus.model.DetourRouteDirection; import org.jetbrains.annotations.ApiStatus; From c0192ec99b739721355d47a0193bbff0c8e936d9 Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sat, 10 Jan 2026 15:42:38 -0600 Subject: [PATCH 18/53] JSpecify + Model updates --- src/main/java/com/cta4j/bus/mapper/DetourMapper.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cta4j/bus/mapper/DetourMapper.java b/src/main/java/com/cta4j/bus/mapper/DetourMapper.java index e3c69c3..9612fe9 100644 --- a/src/main/java/com/cta4j/bus/mapper/DetourMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/DetourMapper.java @@ -7,12 +7,13 @@ import org.jetbrains.annotations.ApiStatus; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.Named; @Mapper @ApiStatus.Internal public interface DetourMapper { @Mapping(source = "ver", target = "version") - @Mapping(source = "st", target = "active") + @Mapping(source = "st", target = "active", qualifiedByName = "mapActive") @Mapping(source = "desc", target = "description") @Mapping(source = "rtdirs", target = "routeDirections") @Mapping(source = "startdt", target = "startTime") @@ -23,4 +24,9 @@ public interface DetourMapper { @Mapping(source = "rt", target = "route") @Mapping(source = "dir", target = "direction") DetourRouteDirection toDomain(CtaDetoursRouteDirection dto); + + @Named("mapActive") + default boolean mapActive(int st) { + return st == 1; + } } From b59ca4f2c461305727a767314910e56afe5b8449 Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sun, 11 Jan 2026 12:43:55 -0600 Subject: [PATCH 19/53] JSpecify + Model updates --- .../bus/client/internal/BusClientImpl.java | 52 ++++++++++------ .../bus/external/CtaBustimeResponse.java | 2 - .../java/com/cta4j/bus/external/CtaError.java | 10 --- .../com/cta4j/bus/external/CtaPrediction.java | 3 +- .../java/com/cta4j/bus/external/CtaStop.java | 13 +++- .../com/cta4j/bus/mapper/ArrivalMapper.java | 62 +++++++++++++++++-- .../com/cta4j/bus/mapper/DetourMapper.java | 2 +- .../com/cta4j/bus/model/ArrivalMetadata.java | 7 ++- 8 files changed, 107 insertions(+), 44 deletions(-) diff --git a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java index b84b29e..5856794 100644 --- a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java +++ b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java @@ -92,7 +92,7 @@ public List getRoutes() { try { routesResponse = this.objectMapper.readValue(response, typeReference); } catch (JacksonException e) { - String message = "Failed to parse response from %s".formatted(ROUTES_ENDPOINT); + String message = String.format("Failed to parse response from %s", ROUTES_ENDPOINT); throw new Cta4jException(message, e); } @@ -103,13 +103,15 @@ public List getRoutes() { List routes = bustimeResponse.data(); if ((errors == null) && (routes == null)) { - throw new Cta4jException("Invalid response from %s".formatted(ROUTES_ENDPOINT)); + String message = String.format("Invalid response from %s", ROUTES_ENDPOINT); + + throw new Cta4jException(message); } if ((errors != null) && !errors.isEmpty()) { String message = this.buildErrorMessage(ROUTES_ENDPOINT, errors); - throw new Cta4jException("Error response from %s: %s".formatted(ROUTES_ENDPOINT, message)); + throw new Cta4jException(message); } if ((routes == null) || routes.isEmpty()) { @@ -144,7 +146,7 @@ public List getDirections(String routeId) { try { directionsResponse = this.objectMapper.readValue(response, typeReference); } catch (JacksonException e) { - String message = "Failed to parse response from %s".formatted(DIRECTIONS_ENDPOINT); + String message = String.format("Failed to parse response from %s", DIRECTIONS_ENDPOINT); throw new Cta4jException(message, e); } @@ -155,13 +157,15 @@ public List getDirections(String routeId) { List directions = bustimeResponse.data(); if ((errors == null) && (directions == null)) { - throw new Cta4jException("Invalid response from %s".formatted(DIRECTIONS_ENDPOINT)); + String message = String.format("Invalid response from %s", DIRECTIONS_ENDPOINT); + + throw new Cta4jException(message); } if ((errors != null) && !errors.isEmpty()) { String message = this.buildErrorMessage(DIRECTIONS_ENDPOINT, errors); - throw new Cta4jException("Error response from %s: %s".formatted(DIRECTIONS_ENDPOINT, message)); + throw new Cta4jException(message); } if ((directions == null) || directions.isEmpty()) { @@ -201,7 +205,7 @@ public List getStops(String routeId, String direction) { try { stopsResponse = this.objectMapper.readValue(response, typeReference); } catch (JacksonException e) { - String message = "Failed to parse response from %s".formatted(STOPS_ENDPOINT); + String message = String.format("Failed to parse response from %s", STOPS_ENDPOINT); throw new Cta4jException(message, e); } @@ -212,13 +216,15 @@ public List getStops(String routeId, String direction) { List stops = bustimeResponse.data(); if ((errors == null) && (stops == null)) { - throw new Cta4jException("Invalid response from %s".formatted(STOPS_ENDPOINT)); + String message = String.format("Invalid response from %s", STOPS_ENDPOINT); + + throw new Cta4jException(message); } if ((errors != null) && !errors.isEmpty()) { String message = this.buildErrorMessage(STOPS_ENDPOINT, errors); - throw new Cta4jException("Error response from %s: %s".formatted(STOPS_ENDPOINT, message)); + throw new Cta4jException(message); } if ((stops == null) || stops.isEmpty()) { @@ -299,7 +305,7 @@ public List getDetours(String routeId, String direction) { try { detoursResponse = this.objectMapper.readValue(response, typeReference); } catch (JacksonException e) { - String message = "Failed to parse response from %s".formatted(DETOURS_ENDPOINT); + String message = String.format("Failed to parse response from %s", DETOURS_ENDPOINT); throw new Cta4jException(message, e); } @@ -310,13 +316,15 @@ public List getDetours(String routeId, String direction) { List detours = bustimeResponse.data(); if ((errors == null) && (detours == null)) { - throw new Cta4jException("Invalid response from %s".formatted(DETOURS_ENDPOINT)); + String message = String.format("Invalid response from %s", DETOURS_ENDPOINT); + + throw new Cta4jException(message); } if ((errors != null) && !errors.isEmpty()) { String message = this.buildErrorMessage(DETOURS_ENDPOINT, errors); - throw new Cta4jException("Error response from %s: %s".formatted(DETOURS_ENDPOINT, message)); + throw new Cta4jException(message); } if ((detours == null) || detours.isEmpty()) { @@ -351,7 +359,7 @@ public Optional getBus(String id) { try { vehicleResponse = this.objectMapper.readValue(response, typeReference); } catch (JacksonException e) { - String message = "Failed to parse response from %s".formatted(VEHICLES_ENDPOINT); + String message = String.format("Failed to parse response from %s", VEHICLES_ENDPOINT); throw new Cta4jException(message, e); } @@ -362,13 +370,15 @@ public Optional getBus(String id) { List vehicles = bustimeResponse.data(); if ((errors == null) && (vehicleResponse == null)) { - throw new Cta4jException("Invalid response from %s".formatted(VEHICLES_ENDPOINT)); + String message = String.format("Invalid response from %s", VEHICLES_ENDPOINT); + + throw new Cta4jException(message); } if ((errors != null) && !errors.isEmpty()) { String message = this.buildErrorMessage(VEHICLES_ENDPOINT, errors); - throw new Cta4jException("Error response from %s: %s".formatted(VEHICLES_ENDPOINT, message)); + throw new Cta4jException(message); } if ((vehicles == null) || vehicles.isEmpty()) { @@ -376,7 +386,7 @@ public Optional getBus(String id) { } if (vehicles.size() > 1) { - String message = "Multiple buses found for ID %s".formatted(id); + String message = String.format("Multiple buses found for ID %s", id); throw new Cta4jException(message); } @@ -394,7 +404,7 @@ private String buildErrorMessage(String endpoint, List errors) { .reduce("%s; %s"::formatted) .orElse("Unknown error"); - return "Error response from %s: %s".formatted(endpoint, message); + return String.format("Error response from %s: %s", endpoint, message); } private List getArrivals(String url) { @@ -406,7 +416,7 @@ private List getArrivals(String url) { try { predictionsResponse = this.objectMapper.readValue(response, typeReference); } catch (JacksonException e) { - String message = "Failed to parse response from %s".formatted(PREDICTIONS_ENDPOINT); + String message = String.format("Failed to parse response from %s", PREDICTIONS_ENDPOINT); throw new Cta4jException(message, e); } @@ -417,13 +427,15 @@ private List getArrivals(String url) { List predictions = bustimeResponse.data(); if ((errors == null) && (predictions == null)) { - throw new Cta4jException("Invalid response from %s".formatted(PREDICTIONS_ENDPOINT)); + String message = String.format("Invalid response from %s", PREDICTIONS_ENDPOINT); + + throw new Cta4jException(message); } if ((errors != null) && !errors.isEmpty()) { String message = this.buildErrorMessage(PREDICTIONS_ENDPOINT, errors); - throw new Cta4jException("Error response from %s: %s".formatted(PREDICTIONS_ENDPOINT, message)); + throw new Cta4jException(message); } if ((predictions == null) || predictions.isEmpty()) { diff --git a/src/main/java/com/cta4j/bus/external/CtaBustimeResponse.java b/src/main/java/com/cta4j/bus/external/CtaBustimeResponse.java index 4362230..17c6c4f 100644 --- a/src/main/java/com/cta4j/bus/external/CtaBustimeResponse.java +++ b/src/main/java/com/cta4j/bus/external/CtaBustimeResponse.java @@ -2,12 +2,10 @@ import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import java.util.List; -@NullMarked @JsonIgnoreProperties(ignoreUnknown = true) public record CtaBustimeResponse( @Nullable diff --git a/src/main/java/com/cta4j/bus/external/CtaError.java b/src/main/java/com/cta4j/bus/external/CtaError.java index 01ce54e..168634c 100644 --- a/src/main/java/com/cta4j/bus/external/CtaError.java +++ b/src/main/java/com/cta4j/bus/external/CtaError.java @@ -2,21 +2,11 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; @NullMarked @SuppressWarnings("ConstantConditions") @JsonIgnoreProperties(ignoreUnknown = true) public record CtaError( - @Nullable - String rtpidatafeed, - - @Nullable - String rt, - - @Nullable - String dir, - String msg ) { public CtaError { diff --git a/src/main/java/com/cta4j/bus/external/CtaPrediction.java b/src/main/java/com/cta4j/bus/external/CtaPrediction.java index 3fe6049..16019a0 100644 --- a/src/main/java/com/cta4j/bus/external/CtaPrediction.java +++ b/src/main/java/com/cta4j/bus/external/CtaPrediction.java @@ -47,7 +47,8 @@ public record CtaPrediction( String psgld, - int gtfsseq, + @Nullable + Integer gtfsseq, @Nullable String nbus, diff --git a/src/main/java/com/cta4j/bus/external/CtaStop.java b/src/main/java/com/cta4j/bus/external/CtaStop.java index 14724fa..458ad4b 100644 --- a/src/main/java/com/cta4j/bus/external/CtaStop.java +++ b/src/main/java/com/cta4j/bus/external/CtaStop.java @@ -16,9 +16,9 @@ public record CtaStop( String stpnm, - Double lat, + double lat, - Double lon, + double lon, @Nullable List dtradd, @@ -32,4 +32,13 @@ public record CtaStop( @Nullable Boolean ada ) { + public CtaStop { + if (stpid == null) { + throw new IllegalArgumentException("stpid must not be null"); + } + + if (stpnm == null) { + throw new IllegalArgumentException("stpnm must not be null"); + } + } } diff --git a/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java b/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java index b0ff5b1..86270cf 100644 --- a/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java @@ -2,14 +2,22 @@ import com.cta4j.bus.external.CtaPrediction; import com.cta4j.bus.model.Arrival; +import com.cta4j.bus.model.PassengerLoad; +import com.cta4j.bus.model.PredictionType; import org.jetbrains.annotations.ApiStatus; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.Named; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; @Mapper @ApiStatus.Internal public interface ArrivalMapper { - @Mapping(source = "typ", target = "predictionType") + @Mapping(source = "typ", target = "predictionType", qualifiedByName = "mapPredictionType") @Mapping(source = "stpid", target = "stopId") @Mapping(source = "stpnm", target = "stopName") @Mapping(source = "vid", target = "vehicleId") @@ -18,7 +26,7 @@ public interface ArrivalMapper { @Mapping(source = "rtdd", target = "routeDesignator") @Mapping(source = "rtdir", target = "routeDirection") @Mapping(source = "des", target = "destination") - @Mapping(source = "prdtm", target = "arrivalTime") + @Mapping(source = "prdtm", target = "arrivalTime", qualifiedByName = "mapArrivalTime") @Mapping(source = "dly", target = "delayed") @Mapping(source = "tmstmp", target = "metadata.timestamp") @Mapping(source = "dyn", target = "metadata.dynamicAction") @@ -26,11 +34,55 @@ public interface ArrivalMapper { @Mapping(source = "tatripid", target = "metadata.tripId") @Mapping(source = "origtatripno", target = "metadata.originalTripNumber") @Mapping(source = "zone", target = "metadata.zone") - @Mapping(source = "psgld", target = "metadata.passengerLoad") + @Mapping(source = "psgld", target = "metadata.passengerLoad", qualifiedByName = "mapPassengerLoad") @Mapping(source = "gtfsseq", target = "metadata.gtfsSequence") @Mapping(source = "nbus", target = "metadata.nextBus") - @Mapping(source = "stst", target = "metadata.stopStatus") - @Mapping(source = "stsd", target = "metadata.stopStatusDescription") + @Mapping(source = "stst", target = "metadata.scheduledStartTimeSeconds") + @Mapping(source = "stsd", target = "metadata.scheduledStartDate") @Mapping(source = "flagstop", target = "metadata.flagStop") Arrival toDomain(CtaPrediction prediction); + + @Named("mapPredictionType") + static PredictionType mapPredictionType(String typ) { + if (typ == null) { + throw new IllegalArgumentException("typ must not be null"); + } + + return switch (typ) { + case "A" -> PredictionType.ARRIVAL; + case "D" -> PredictionType.DEPARTURE; + default -> { + String message = String.format("Unknown prediction type: %s", typ); + + throw new IllegalArgumentException(message); + } + }; + } + + DateTimeFormatter ARRIVAL_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm"); + + @Named("mapArrivalTime") + static Instant mapArrivalTime(String prdtm) { + if (prdtm == null) { + throw new IllegalArgumentException("prdtm must not be null"); + } + + return LocalDateTime.parse(prdtm, ARRIVAL_TIME_FORMATTER) + .atZone(ZoneOffset.UTC) + .toInstant(); + } + + @Named("mapPassengerLoad") + static PassengerLoad mapPassengerLoad(String psgld) { + if (psgld == null) { + throw new IllegalArgumentException("psgld must not be null"); + } + + return switch (psgld) { + case "FULL" -> PassengerLoad.FULL; + case "HALF_EMPTY" -> PassengerLoad.HALF_EMPTY; + case "EMPTY" -> PassengerLoad.EMPTY; + default -> PassengerLoad.UNKNOWN; + }; + } } diff --git a/src/main/java/com/cta4j/bus/mapper/DetourMapper.java b/src/main/java/com/cta4j/bus/mapper/DetourMapper.java index 9612fe9..9d054e0 100644 --- a/src/main/java/com/cta4j/bus/mapper/DetourMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/DetourMapper.java @@ -26,7 +26,7 @@ public interface DetourMapper { DetourRouteDirection toDomain(CtaDetoursRouteDirection dto); @Named("mapActive") - default boolean mapActive(int st) { + static boolean mapActive(int st) { return st == 1; } } diff --git a/src/main/java/com/cta4j/bus/model/ArrivalMetadata.java b/src/main/java/com/cta4j/bus/model/ArrivalMetadata.java index dc2140c..afc300b 100644 --- a/src/main/java/com/cta4j/bus/model/ArrivalMetadata.java +++ b/src/main/java/com/cta4j/bus/model/ArrivalMetadata.java @@ -20,16 +20,17 @@ public record ArrivalMetadata( PassengerLoad passengerLoad, - int gtfsSequence, + @Nullable + Integer gtfsSequence, @Nullable String nextBus, @Nullable - Integer stopStatus, + Integer scheduledStartTimeSeconds, @Nullable - String stopStatusDescription, + String scheduledStartDate, int flagStop ) { From 6d519d0e735327b36946a3b93d0e1ccb99abf052 Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sun, 11 Jan 2026 12:51:27 -0600 Subject: [PATCH 20/53] JSpecify + Model updates --- .../com/cta4j/bus/mapper/ArrivalMapper.java | 32 +++++++++++++------ .../com/cta4j/bus/model/ArrivalMetadata.java | 4 ++- .../java/com/cta4j/bus/model/TransitMode.java | 4 +++ 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java b/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java index 86270cf..a9d900f 100644 --- a/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java @@ -2,6 +2,7 @@ import com.cta4j.bus.external.CtaPrediction; import com.cta4j.bus.model.Arrival; +import com.cta4j.bus.model.DynamicAction; import com.cta4j.bus.model.PassengerLoad; import com.cta4j.bus.model.PredictionType; import org.jetbrains.annotations.ApiStatus; @@ -26,10 +27,10 @@ public interface ArrivalMapper { @Mapping(source = "rtdd", target = "routeDesignator") @Mapping(source = "rtdir", target = "routeDirection") @Mapping(source = "des", target = "destination") - @Mapping(source = "prdtm", target = "arrivalTime", qualifiedByName = "mapArrivalTime") + @Mapping(source = "prdtm", target = "arrivalTime", qualifiedByName = "mapTimestamp") @Mapping(source = "dly", target = "delayed") - @Mapping(source = "tmstmp", target = "metadata.timestamp") - @Mapping(source = "dyn", target = "metadata.dynamicAction") + @Mapping(source = "tmstmp", target = "metadata.timestamp", qualifiedByName = "mapTimestamp") + @Mapping(source = "dyn", target = "metadata.dynamicAction", qualifiedByName = "mapDynamicAction") @Mapping(source = "tablockid", target = "metadata.blockId") @Mapping(source = "tatripid", target = "metadata.tripId") @Mapping(source = "origtatripno", target = "metadata.originalTripNumber") @@ -59,19 +60,32 @@ static PredictionType mapPredictionType(String typ) { }; } - DateTimeFormatter ARRIVAL_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm"); + DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm"); - @Named("mapArrivalTime") - static Instant mapArrivalTime(String prdtm) { - if (prdtm == null) { - throw new IllegalArgumentException("prdtm must not be null"); + @Named("mapTimestamp") + static Instant mapTimestamp(String timestamp) { + if (timestamp == null) { + throw new IllegalArgumentException("timestamp must not be null"); } - return LocalDateTime.parse(prdtm, ARRIVAL_TIME_FORMATTER) + return LocalDateTime.parse(timestamp, TIMESTAMP_FORMATTER) .atZone(ZoneOffset.UTC) .toInstant(); } + @Named("mapDynamicAction") + static DynamicAction mapDynamicAction(int dyn) { + for (DynamicAction dynamicAction : DynamicAction.values()) { + if (dynamicAction.getId() == dyn) { + return dynamicAction; + } + } + + String message = String.format("Unknown dynamic action id: %d", dyn); + + throw new IllegalArgumentException(message); + } + @Named("mapPassengerLoad") static PassengerLoad mapPassengerLoad(String psgld) { if (psgld == null) { diff --git a/src/main/java/com/cta4j/bus/model/ArrivalMetadata.java b/src/main/java/com/cta4j/bus/model/ArrivalMetadata.java index afc300b..0afc018 100644 --- a/src/main/java/com/cta4j/bus/model/ArrivalMetadata.java +++ b/src/main/java/com/cta4j/bus/model/ArrivalMetadata.java @@ -3,10 +3,12 @@ import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; +import java.time.Instant; + @NullMarked @SuppressWarnings("ConstantConditions") public record ArrivalMetadata( - String timestamp, + Instant timestamp, DynamicAction dynamicAction, diff --git a/src/main/java/com/cta4j/bus/model/TransitMode.java b/src/main/java/com/cta4j/bus/model/TransitMode.java index 413f8a0..ad2aeba 100644 --- a/src/main/java/com/cta4j/bus/model/TransitMode.java +++ b/src/main/java/com/cta4j/bus/model/TransitMode.java @@ -16,4 +16,8 @@ public enum TransitMode { TransitMode(int code) { this.code = code; } + + public int getCode() { + return this.code; + } } From 2a02d9dd54e12057f7bd3c84eb659bbbddcb6ab3 Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sun, 11 Jan 2026 13:03:00 -0600 Subject: [PATCH 21/53] JSpecify + Model updates --- .../com/cta4j/bus/mapper/ArrivalMapper.java | 27 ++++++++++++++++--- .../com/cta4j/bus/model/ArrivalMetadata.java | 2 +- .../com/cta4j/bus/model/DynamicAction.java | 10 +++---- .../java/com/cta4j/bus/model/FlagStop.java | 21 +++++++++++++++ 4 files changed, 50 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/cta4j/bus/model/FlagStop.java diff --git a/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java b/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java index a9d900f..66f6af1 100644 --- a/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java @@ -3,6 +3,7 @@ import com.cta4j.bus.external.CtaPrediction; import com.cta4j.bus.model.Arrival; import com.cta4j.bus.model.DynamicAction; +import com.cta4j.bus.model.FlagStop; import com.cta4j.bus.model.PassengerLoad; import com.cta4j.bus.model.PredictionType; import org.jetbrains.annotations.ApiStatus; @@ -40,7 +41,7 @@ public interface ArrivalMapper { @Mapping(source = "nbus", target = "metadata.nextBus") @Mapping(source = "stst", target = "metadata.scheduledStartTimeSeconds") @Mapping(source = "stsd", target = "metadata.scheduledStartDate") - @Mapping(source = "flagstop", target = "metadata.flagStop") + @Mapping(source = "flagstop", target = "metadata.flagStop", qualifiedByName = "mapFlagStop") Arrival toDomain(CtaPrediction prediction); @Named("mapPredictionType") @@ -76,12 +77,12 @@ static Instant mapTimestamp(String timestamp) { @Named("mapDynamicAction") static DynamicAction mapDynamicAction(int dyn) { for (DynamicAction dynamicAction : DynamicAction.values()) { - if (dynamicAction.getId() == dyn) { + if (dynamicAction.getCode() == dyn) { return dynamicAction; } } - String message = String.format("Unknown dynamic action id: %d", dyn); + String message = String.format("Unknown dynamic action code: %d", dyn); throw new IllegalArgumentException(message); } @@ -96,7 +97,25 @@ static PassengerLoad mapPassengerLoad(String psgld) { case "FULL" -> PassengerLoad.FULL; case "HALF_EMPTY" -> PassengerLoad.HALF_EMPTY; case "EMPTY" -> PassengerLoad.EMPTY; - default -> PassengerLoad.UNKNOWN; + case "N/A", "" -> PassengerLoad.UNKNOWN; + default -> { + String message = String.format("Unknown passenger load: %s", psgld); + + throw new IllegalArgumentException(message); + } }; } + + @Named("mapFlagStop") + static FlagStop mapFlagStop(int flagstop) { + for (FlagStop flagStop : FlagStop.values()) { + if (flagStop.getCode() == flagstop) { + return flagStop; + } + } + + String message = String.format("Unknown flag stop code: %d", flagstop); + + throw new IllegalArgumentException(message); + } } diff --git a/src/main/java/com/cta4j/bus/model/ArrivalMetadata.java b/src/main/java/com/cta4j/bus/model/ArrivalMetadata.java index 0afc018..f7e632a 100644 --- a/src/main/java/com/cta4j/bus/model/ArrivalMetadata.java +++ b/src/main/java/com/cta4j/bus/model/ArrivalMetadata.java @@ -34,7 +34,7 @@ public record ArrivalMetadata( @Nullable String scheduledStartDate, - int flagStop + FlagStop flagStop ) { public ArrivalMetadata { if (timestamp == null) { diff --git a/src/main/java/com/cta4j/bus/model/DynamicAction.java b/src/main/java/com/cta4j/bus/model/DynamicAction.java index aca6292..c46fb4c 100644 --- a/src/main/java/com/cta4j/bus/model/DynamicAction.java +++ b/src/main/java/com/cta4j/bus/model/DynamicAction.java @@ -35,13 +35,13 @@ public enum DynamicAction { STOPS_AFFECTED_NEW(19); - private final int id; + private final int code; - DynamicAction(int id) { - this.id = id; + DynamicAction(int code) { + this.code = code; } - public int getId() { - return this.id; + public int getCode() { + return this.code; } } diff --git a/src/main/java/com/cta4j/bus/model/FlagStop.java b/src/main/java/com/cta4j/bus/model/FlagStop.java new file mode 100644 index 0000000..fc038c7 --- /dev/null +++ b/src/main/java/com/cta4j/bus/model/FlagStop.java @@ -0,0 +1,21 @@ +package com.cta4j.bus.model; + +public enum FlagStop { + UNDEFINED(-1), + + NORMAL(0), + + PICKUP_AND_DISCHARGE(1), + + ONLY_DISCHARGE(2); + + private final int code; + + FlagStop(int code) { + this.code = code; + } + + public int getCode() { + return this.code; + } +} From 2cad8083740a50dce6f3e348abf929ced9daa525 Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sun, 11 Jan 2026 13:07:52 -0600 Subject: [PATCH 22/53] JSpecify + Model updates --- .../com/cta4j/bus/mapper/ArrivalMapper.java | 88 +---------------- .../com/cta4j/bus/mapper/DetourMapper.java | 9 +- .../mapper/util/CtaBusMappingQualifiers.java | 98 +++++++++++++++++++ 3 files changed, 102 insertions(+), 93 deletions(-) create mode 100644 src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java diff --git a/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java b/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java index 66f6af1..c1b6b6f 100644 --- a/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java @@ -1,22 +1,13 @@ package com.cta4j.bus.mapper; import com.cta4j.bus.external.CtaPrediction; +import com.cta4j.bus.mapper.util.CtaBusMappingQualifiers; import com.cta4j.bus.model.Arrival; -import com.cta4j.bus.model.DynamicAction; -import com.cta4j.bus.model.FlagStop; -import com.cta4j.bus.model.PassengerLoad; -import com.cta4j.bus.model.PredictionType; import org.jetbrains.annotations.ApiStatus; import org.mapstruct.Mapper; import org.mapstruct.Mapping; -import org.mapstruct.Named; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; - -@Mapper +@Mapper(uses = CtaBusMappingQualifiers.class) @ApiStatus.Internal public interface ArrivalMapper { @Mapping(source = "typ", target = "predictionType", qualifiedByName = "mapPredictionType") @@ -43,79 +34,4 @@ public interface ArrivalMapper { @Mapping(source = "stsd", target = "metadata.scheduledStartDate") @Mapping(source = "flagstop", target = "metadata.flagStop", qualifiedByName = "mapFlagStop") Arrival toDomain(CtaPrediction prediction); - - @Named("mapPredictionType") - static PredictionType mapPredictionType(String typ) { - if (typ == null) { - throw new IllegalArgumentException("typ must not be null"); - } - - return switch (typ) { - case "A" -> PredictionType.ARRIVAL; - case "D" -> PredictionType.DEPARTURE; - default -> { - String message = String.format("Unknown prediction type: %s", typ); - - throw new IllegalArgumentException(message); - } - }; - } - - DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm"); - - @Named("mapTimestamp") - static Instant mapTimestamp(String timestamp) { - if (timestamp == null) { - throw new IllegalArgumentException("timestamp must not be null"); - } - - return LocalDateTime.parse(timestamp, TIMESTAMP_FORMATTER) - .atZone(ZoneOffset.UTC) - .toInstant(); - } - - @Named("mapDynamicAction") - static DynamicAction mapDynamicAction(int dyn) { - for (DynamicAction dynamicAction : DynamicAction.values()) { - if (dynamicAction.getCode() == dyn) { - return dynamicAction; - } - } - - String message = String.format("Unknown dynamic action code: %d", dyn); - - throw new IllegalArgumentException(message); - } - - @Named("mapPassengerLoad") - static PassengerLoad mapPassengerLoad(String psgld) { - if (psgld == null) { - throw new IllegalArgumentException("psgld must not be null"); - } - - return switch (psgld) { - case "FULL" -> PassengerLoad.FULL; - case "HALF_EMPTY" -> PassengerLoad.HALF_EMPTY; - case "EMPTY" -> PassengerLoad.EMPTY; - case "N/A", "" -> PassengerLoad.UNKNOWN; - default -> { - String message = String.format("Unknown passenger load: %s", psgld); - - throw new IllegalArgumentException(message); - } - }; - } - - @Named("mapFlagStop") - static FlagStop mapFlagStop(int flagstop) { - for (FlagStop flagStop : FlagStop.values()) { - if (flagStop.getCode() == flagstop) { - return flagStop; - } - } - - String message = String.format("Unknown flag stop code: %d", flagstop); - - throw new IllegalArgumentException(message); - } } diff --git a/src/main/java/com/cta4j/bus/mapper/DetourMapper.java b/src/main/java/com/cta4j/bus/mapper/DetourMapper.java index 9d054e0..29b2e14 100644 --- a/src/main/java/com/cta4j/bus/mapper/DetourMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/DetourMapper.java @@ -2,14 +2,14 @@ import com.cta4j.bus.external.CtaDetour; import com.cta4j.bus.external.CtaDetoursRouteDirection; +import com.cta4j.bus.mapper.util.CtaBusMappingQualifiers; import com.cta4j.bus.model.Detour; import com.cta4j.bus.model.DetourRouteDirection; import org.jetbrains.annotations.ApiStatus; import org.mapstruct.Mapper; import org.mapstruct.Mapping; -import org.mapstruct.Named; -@Mapper +@Mapper(uses = CtaBusMappingQualifiers.class) @ApiStatus.Internal public interface DetourMapper { @Mapping(source = "ver", target = "version") @@ -24,9 +24,4 @@ public interface DetourMapper { @Mapping(source = "rt", target = "route") @Mapping(source = "dir", target = "direction") DetourRouteDirection toDomain(CtaDetoursRouteDirection dto); - - @Named("mapActive") - static boolean mapActive(int st) { - return st == 1; - } } diff --git a/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java b/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java new file mode 100644 index 0000000..206544b --- /dev/null +++ b/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java @@ -0,0 +1,98 @@ +package com.cta4j.bus.mapper.util; + +import com.cta4j.bus.model.DynamicAction; +import com.cta4j.bus.model.FlagStop; +import com.cta4j.bus.model.PassengerLoad; +import com.cta4j.bus.model.PredictionType; +import org.mapstruct.Named; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; + +public final class CtaBusMappingQualifiers { + private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm"); + + private CtaBusMappingQualifiers() { + throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } + + @Named("mapPredictionType") + public static PredictionType mapPredictionType(String typ) { + if (typ == null) { + throw new IllegalArgumentException("typ must not be null"); + } + + return switch (typ) { + case "A" -> PredictionType.ARRIVAL; + case "D" -> PredictionType.DEPARTURE; + default -> { + String message = String.format("Unknown prediction type: %s", typ); + + throw new IllegalArgumentException(message); + } + }; + } + + @Named("mapTimestamp") + public static Instant mapTimestamp(String timestamp) { + if (timestamp == null) { + throw new IllegalArgumentException("timestamp must not be null"); + } + + return LocalDateTime.parse(timestamp, TIMESTAMP_FORMATTER) + .atZone(ZoneOffset.UTC) + .toInstant(); + } + + @Named("mapDynamicAction") + public static DynamicAction mapDynamicAction(int dyn) { + for (DynamicAction dynamicAction : DynamicAction.values()) { + if (dynamicAction.getCode() == dyn) { + return dynamicAction; + } + } + + String message = String.format("Unknown dynamic action code: %d", dyn); + + throw new IllegalArgumentException(message); + } + + @Named("mapPassengerLoad") + public static PassengerLoad mapPassengerLoad(String psgld) { + if (psgld == null) { + throw new IllegalArgumentException("psgld must not be null"); + } + + return switch (psgld) { + case "FULL" -> PassengerLoad.FULL; + case "HALF_EMPTY" -> PassengerLoad.HALF_EMPTY; + case "EMPTY" -> PassengerLoad.EMPTY; + case "N/A", "" -> PassengerLoad.UNKNOWN; + default -> { + String message = String.format("Unknown passenger load: %s", psgld); + + throw new IllegalArgumentException(message); + } + }; + } + + @Named("mapFlagStop") + public static FlagStop mapFlagStop(int flagstop) { + for (FlagStop flagStop : FlagStop.values()) { + if (flagStop.getCode() == flagstop) { + return flagStop; + } + } + + String message = String.format("Unknown flag stop code: %d", flagstop); + + throw new IllegalArgumentException(message); + } + + @Named("mapActive") + public static boolean mapActive(int st) { + return st == 1; + } +} From 5f7c6049bc1e0ea7dde6a1df2ec1c047fe34238c Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sun, 11 Jan 2026 13:31:46 -0600 Subject: [PATCH 23/53] JSpecify + Model updates --- .../java/com/cta4j/bus/external/CtaVehicle.java | 8 +++----- .../java/com/cta4j/bus/mapper/ArrivalMapper.java | 2 +- .../java/com/cta4j/bus/mapper/BusMapper.java | 11 ++++++----- .../bus/mapper/util/CtaBusMappingQualifiers.java | 16 +++++++++++++++- .../com/cta4j/bus/model/ArrivalMetadata.java | 2 +- .../java/com/cta4j/bus/model/BusMetadata.java | 3 ++- 6 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/cta4j/bus/external/CtaVehicle.java b/src/main/java/com/cta4j/bus/external/CtaVehicle.java index 25db096..27c8dc9 100644 --- a/src/main/java/com/cta4j/bus/external/CtaVehicle.java +++ b/src/main/java/com/cta4j/bus/external/CtaVehicle.java @@ -15,6 +15,7 @@ public record CtaVehicle( @Nullable String rtpidatafeed, + @Nullable String tmpstmp, double lat, @@ -51,7 +52,8 @@ public record CtaVehicle( @Nullable String srvtmstmp, - int spd, + @Nullable + Integer spd, @Nullable Integer blk, @@ -79,10 +81,6 @@ public record CtaVehicle( throw new IllegalArgumentException("vid must not be null"); } - if (tmpstmp == null) { - throw new IllegalArgumentException("tmpstmp must not be null"); - } - if (rt == null) { throw new IllegalArgumentException("rt must not be null"); } diff --git a/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java b/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java index c1b6b6f..9c3ee11 100644 --- a/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java @@ -30,7 +30,7 @@ public interface ArrivalMapper { @Mapping(source = "psgld", target = "metadata.passengerLoad", qualifiedByName = "mapPassengerLoad") @Mapping(source = "gtfsseq", target = "metadata.gtfsSequence") @Mapping(source = "nbus", target = "metadata.nextBus") - @Mapping(source = "stst", target = "metadata.scheduledStartTimeSeconds") + @Mapping(source = "stst", target = "metadata.scheduledStartSeconds") @Mapping(source = "stsd", target = "metadata.scheduledStartDate") @Mapping(source = "flagstop", target = "metadata.flagStop", qualifiedByName = "mapFlagStop") Arrival toDomain(CtaPrediction prediction); diff --git a/src/main/java/com/cta4j/bus/mapper/BusMapper.java b/src/main/java/com/cta4j/bus/mapper/BusMapper.java index 0193f7e..19769ac 100644 --- a/src/main/java/com/cta4j/bus/mapper/BusMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/BusMapper.java @@ -1,17 +1,18 @@ package com.cta4j.bus.mapper; import com.cta4j.bus.external.CtaVehicle; +import com.cta4j.bus.mapper.util.CtaBusMappingQualifiers; import com.cta4j.bus.model.Bus; import org.jetbrains.annotations.ApiStatus; import org.mapstruct.Mapper; import org.mapstruct.Mapping; -@Mapper +@Mapper(uses = CtaBusMappingQualifiers.class) @ApiStatus.Internal public interface BusMapper { @Mapping(source = "vid", target = "id") @Mapping(source = "rtpidatafeed", target = "metadata.dataFeed") - @Mapping(source = "tmpstmp", target = "metadata.lastUpdated") + @Mapping(source = "tmpstmp", target = "metadata.lastUpdated", qualifiedByName = "mapTimestamp") @Mapping(source = "lat", target = "coordinates.latitude") @Mapping(source = "lon", target = "coordinates.longitude") @Mapping(source = "hdg", target = "coordinates.heading") @@ -25,15 +26,15 @@ public interface BusMapper { @Mapping(source = "sequence", target = "metadata.sequence") @Mapping(source = "gtfsseq", target = "metadata.gtfsSequence") @Mapping(source = "dly", target = "delayed") - @Mapping(source = "srvtmstmp", target = "metadata.serverTimestamp") + @Mapping(source = "srvtmstmp", target = "metadata.serverTimestamp", qualifiedByName = "mapTimestamp") @Mapping(source = "spd", target = "metadata.speed") @Mapping(source = "blk", target = "metadata.block") @Mapping(source = "tablockid", target = "metadata.blockId") @Mapping(source = "tatripid", target = "metadata.tripId") @Mapping(source = "origtatripno", target = "metadata.originalTripNumber") @Mapping(source = "zone", target = "metadata.zone") - @Mapping(source = "mode", target = "metadata.mode") - @Mapping(source = "psgld", target = "metadata.passengerLoad") + @Mapping(source = "mode", target = "metadata.mode", qualifiedByName = "mapTransitMode") + @Mapping(source = "psgld", target = "metadata.passengerLoad", qualifiedByName = "mapPassengerLoad") @Mapping(source = "stst", target = "metadata.scheduledStartSeconds") @Mapping(source = "stsd", target = "metadata.scheduledStartDate") Bus toDomain(CtaVehicle vehicle); diff --git a/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java b/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java index 206544b..446f405 100644 --- a/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java +++ b/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java @@ -4,6 +4,7 @@ import com.cta4j.bus.model.FlagStop; import com.cta4j.bus.model.PassengerLoad; import com.cta4j.bus.model.PredictionType; +import com.cta4j.bus.model.TransitMode; import org.mapstruct.Named; import java.time.Instant; @@ -38,7 +39,7 @@ public static PredictionType mapPredictionType(String typ) { @Named("mapTimestamp") public static Instant mapTimestamp(String timestamp) { if (timestamp == null) { - throw new IllegalArgumentException("timestamp must not be null"); + return null; } return LocalDateTime.parse(timestamp, TIMESTAMP_FORMATTER) @@ -91,6 +92,19 @@ public static FlagStop mapFlagStop(int flagstop) { throw new IllegalArgumentException(message); } + @Named("mapTransitMode") + public static TransitMode mapTransitMode(int mode) { + for (TransitMode transitMode : TransitMode.values()) { + if (transitMode.getCode() == mode) { + return transitMode; + } + } + + String message = String.format("Unknown transit mode code: %d", mode); + + throw new IllegalArgumentException(message); + } + @Named("mapActive") public static boolean mapActive(int st) { return st == 1; diff --git a/src/main/java/com/cta4j/bus/model/ArrivalMetadata.java b/src/main/java/com/cta4j/bus/model/ArrivalMetadata.java index f7e632a..86b768a 100644 --- a/src/main/java/com/cta4j/bus/model/ArrivalMetadata.java +++ b/src/main/java/com/cta4j/bus/model/ArrivalMetadata.java @@ -29,7 +29,7 @@ public record ArrivalMetadata( String nextBus, @Nullable - Integer scheduledStartTimeSeconds, + Integer scheduledStartSeconds, @Nullable String scheduledStartDate, diff --git a/src/main/java/com/cta4j/bus/model/BusMetadata.java b/src/main/java/com/cta4j/bus/model/BusMetadata.java index 7629520..6562ff4 100644 --- a/src/main/java/com/cta4j/bus/model/BusMetadata.java +++ b/src/main/java/com/cta4j/bus/model/BusMetadata.java @@ -37,7 +37,8 @@ public record BusMetadata( @Nullable Instant serverTimestamp, - int speed, + @Nullable + Integer speed, @Nullable Integer block, From 01dfa64f06f163d46a76b56d93ea160a52ce2350 Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sun, 11 Jan 2026 13:40:02 -0600 Subject: [PATCH 24/53] JSpecify + Model updates --- .../java/com/cta4j/bus/client/internal/BusClientImpl.java | 4 +--- src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java | 2 +- src/main/java/com/cta4j/bus/mapper/BusMapper.java | 2 +- src/main/java/com/cta4j/bus/mapper/DetourMapper.java | 6 +++--- src/main/java/com/cta4j/bus/mapper/RouteMapper.java | 2 +- src/main/java/com/cta4j/bus/mapper/StopMapper.java | 2 +- .../com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java | 2 ++ 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java index 5856794..74ab824 100644 --- a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java +++ b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java @@ -316,9 +316,7 @@ public List getDetours(String routeId, String direction) { List detours = bustimeResponse.data(); if ((errors == null) && (detours == null)) { - String message = String.format("Invalid response from %s", DETOURS_ENDPOINT); - - throw new Cta4jException(message); + return List.of(); } if ((errors != null) && !errors.isEmpty()) { diff --git a/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java b/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java index 9c3ee11..57ea2e1 100644 --- a/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java @@ -7,8 +7,8 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; -@Mapper(uses = CtaBusMappingQualifiers.class) @ApiStatus.Internal +@Mapper(uses = CtaBusMappingQualifiers.class) public interface ArrivalMapper { @Mapping(source = "typ", target = "predictionType", qualifiedByName = "mapPredictionType") @Mapping(source = "stpid", target = "stopId") diff --git a/src/main/java/com/cta4j/bus/mapper/BusMapper.java b/src/main/java/com/cta4j/bus/mapper/BusMapper.java index 19769ac..ace9308 100644 --- a/src/main/java/com/cta4j/bus/mapper/BusMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/BusMapper.java @@ -7,8 +7,8 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; -@Mapper(uses = CtaBusMappingQualifiers.class) @ApiStatus.Internal +@Mapper(uses = CtaBusMappingQualifiers.class) public interface BusMapper { @Mapping(source = "vid", target = "id") @Mapping(source = "rtpidatafeed", target = "metadata.dataFeed") diff --git a/src/main/java/com/cta4j/bus/mapper/DetourMapper.java b/src/main/java/com/cta4j/bus/mapper/DetourMapper.java index 29b2e14..51a0bdd 100644 --- a/src/main/java/com/cta4j/bus/mapper/DetourMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/DetourMapper.java @@ -9,15 +9,15 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; -@Mapper(uses = CtaBusMappingQualifiers.class) @ApiStatus.Internal +@Mapper(uses = CtaBusMappingQualifiers.class) public interface DetourMapper { @Mapping(source = "ver", target = "version") @Mapping(source = "st", target = "active", qualifiedByName = "mapActive") @Mapping(source = "desc", target = "description") @Mapping(source = "rtdirs", target = "routeDirections") - @Mapping(source = "startdt", target = "startTime") - @Mapping(source = "enddt", target = "endTime") + @Mapping(source = "startdt", target = "startTime", qualifiedByName = "mapTimestamp") + @Mapping(source = "enddt", target = "endTime", qualifiedByName = "mapTimestamp") @Mapping(source = "rtpidatafeed", target = "dataFeed") Detour toDomain(CtaDetour dto); diff --git a/src/main/java/com/cta4j/bus/mapper/RouteMapper.java b/src/main/java/com/cta4j/bus/mapper/RouteMapper.java index 2fe2434..65b0206 100644 --- a/src/main/java/com/cta4j/bus/mapper/RouteMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/RouteMapper.java @@ -6,8 +6,8 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; -@Mapper @ApiStatus.Internal +@Mapper public interface RouteMapper { @Mapping(source = "rt", target = "id") @Mapping(source = "rtnm", target = "name") diff --git a/src/main/java/com/cta4j/bus/mapper/StopMapper.java b/src/main/java/com/cta4j/bus/mapper/StopMapper.java index 251e1f1..82a23e9 100644 --- a/src/main/java/com/cta4j/bus/mapper/StopMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/StopMapper.java @@ -6,8 +6,8 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; -@Mapper @ApiStatus.Internal +@Mapper public interface StopMapper { @Mapping(source = "stpid", target = "id") @Mapping(source = "stpnm", target = "name") diff --git a/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java b/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java index 446f405..f486e4f 100644 --- a/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java +++ b/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java @@ -5,6 +5,7 @@ import com.cta4j.bus.model.PassengerLoad; import com.cta4j.bus.model.PredictionType; import com.cta4j.bus.model.TransitMode; +import org.jetbrains.annotations.ApiStatus; import org.mapstruct.Named; import java.time.Instant; @@ -12,6 +13,7 @@ import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; +@ApiStatus.Internal public final class CtaBusMappingQualifiers { private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm"); From 70a4af3819a771f376633bd117f2439eef4a460a Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sun, 11 Jan 2026 13:54:14 -0600 Subject: [PATCH 25/53] JSpecify + Model updates --- .../bus/client/internal/BusClientImpl.java | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java index 74ab824..f9c3819 100644 --- a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java +++ b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java @@ -25,6 +25,8 @@ import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import org.mapstruct.factory.Mappers; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import tools.jackson.core.JacksonException; import tools.jackson.core.type.TypeReference; import tools.jackson.databind.ObjectMapper; @@ -38,6 +40,8 @@ @ApiStatus.Internal @SuppressWarnings("ConstantConditions") public final class BusClientImpl implements BusClient { + private static final Logger log = LoggerFactory.getLogger(BusClientImpl.class); + private static final String DEFAULT_HOST = "ctabustracker.com"; private static final String ROUTES_ENDPOINT = "/bustime/api/v3/getroutes"; private static final String DIRECTIONS_ENDPOINT = "/bustime/api/v3/getdirections"; @@ -103,9 +107,9 @@ public List getRoutes() { List routes = bustimeResponse.data(); if ((errors == null) && (routes == null)) { - String message = String.format("Invalid response from %s", ROUTES_ENDPOINT); + log.debug("Routes bustime response missing both error and data from {}", ROUTES_ENDPOINT); - throw new Cta4jException(message); + return List.of(); } if ((errors != null) && !errors.isEmpty()) { @@ -157,9 +161,9 @@ public List getDirections(String routeId) { List directions = bustimeResponse.data(); if ((errors == null) && (directions == null)) { - String message = String.format("Invalid response from %s", DIRECTIONS_ENDPOINT); + log.debug("Directions bustime response missing both error and data from {}", DIRECTIONS_ENDPOINT); - throw new Cta4jException(message); + return List.of(); } if ((errors != null) && !errors.isEmpty()) { @@ -216,9 +220,9 @@ public List getStops(String routeId, String direction) { List stops = bustimeResponse.data(); if ((errors == null) && (stops == null)) { - String message = String.format("Invalid response from %s", STOPS_ENDPOINT); + log.debug("Stops bustime response missing both error and data from {}", STOPS_ENDPOINT); - throw new Cta4jException(message); + return List.of(); } if ((errors != null) && !errors.isEmpty()) { @@ -316,6 +320,8 @@ public List getDetours(String routeId, String direction) { List detours = bustimeResponse.data(); if ((errors == null) && (detours == null)) { + log.debug("Detours bustime response missing both error and data from {}", DETOURS_ENDPOINT); + return List.of(); } @@ -368,9 +374,9 @@ public Optional getBus(String id) { List vehicles = bustimeResponse.data(); if ((errors == null) && (vehicleResponse == null)) { - String message = String.format("Invalid response from %s", VEHICLES_ENDPOINT); + log.debug("Vehicles bustime response missing both error and data from {}", VEHICLES_ENDPOINT); - throw new Cta4jException(message); + return Optional.empty(); } if ((errors != null) && !errors.isEmpty()) { @@ -425,9 +431,9 @@ private List getArrivals(String url) { List predictions = bustimeResponse.data(); if ((errors == null) && (predictions == null)) { - String message = String.format("Invalid response from %s", PREDICTIONS_ENDPOINT); + log.debug("Predictions bustime response missing both error and data from {}", PREDICTIONS_ENDPOINT); - throw new Cta4jException(message); + return List.of(); } if ((errors != null) && !errors.isEmpty()) { From 88c2db1f6cd269d06a7d17b3a17b822bb18e54fb Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sun, 11 Jan 2026 14:09:31 -0600 Subject: [PATCH 26/53] JSpecify + Model updates --- .../java/com/cta4j/bus/client/BusClient.java | 3 + .../bus/client/internal/BusClientImpl.java | 103 ++++++++++++++---- .../bus/external/CtaBustimeResponse.java | 3 +- .../mapper/util/CtaBusMappingQualifiers.java | 7 +- 4 files changed, 88 insertions(+), 28 deletions(-) diff --git a/src/main/java/com/cta4j/bus/client/BusClient.java b/src/main/java/com/cta4j/bus/client/BusClient.java index 6a02105..3050a3b 100644 --- a/src/main/java/com/cta4j/bus/client/BusClient.java +++ b/src/main/java/com/cta4j/bus/client/BusClient.java @@ -9,6 +9,7 @@ import com.cta4j.exception.Cta4jException; import org.jspecify.annotations.NullMarked; +import java.time.Instant; import java.util.List; import java.util.Optional; @@ -17,6 +18,8 @@ */ @NullMarked public interface BusClient { + Optional getSystemTime(); + /** * Retrieves a {@link List} of all bus routes. * diff --git a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java index f9c3819..08a9026 100644 --- a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java +++ b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java @@ -10,6 +10,7 @@ import com.cta4j.bus.mapper.DetourMapper; import com.cta4j.bus.mapper.RouteMapper; import com.cta4j.bus.mapper.StopMapper; +import com.cta4j.bus.mapper.util.CtaBusMappingQualifiers; import com.cta4j.bus.model.Arrival; import com.cta4j.bus.model.Bus; import com.cta4j.bus.model.Detour; @@ -33,6 +34,7 @@ import org.apache.hc.core5.net.URIBuilder; import org.jetbrains.annotations.ApiStatus; +import java.time.Instant; import java.util.List; import java.util.Optional; @@ -43,12 +45,14 @@ public final class BusClientImpl implements BusClient { private static final Logger log = LoggerFactory.getLogger(BusClientImpl.class); private static final String DEFAULT_HOST = "ctabustracker.com"; - private static final String ROUTES_ENDPOINT = "/bustime/api/v3/getroutes"; - private static final String DIRECTIONS_ENDPOINT = "/bustime/api/v3/getdirections"; - private static final String STOPS_ENDPOINT = "/bustime/api/v3/getstops"; - private static final String PREDICTIONS_ENDPOINT = "/bustime/api/v3/getpredictions"; - private static final String DETOURS_ENDPOINT = "/bustime/api/v3/getdetours"; - private static final String VEHICLES_ENDPOINT = "/bustime/api/v3/getvehicles"; + private static final String API_VERSION = "v3"; + private static final String SYSTEM_TIME_ENDPOINT = String.format("/bustime/api/%s/gettime", API_VERSION); + private static final String ROUTES_ENDPOINT = String.format("/bustime/api/%s/getroutes", API_VERSION); + private static final String DIRECTIONS_ENDPOINT = String.format("/bustime/api/%s/getdirections", API_VERSION); + private static final String STOPS_ENDPOINT = String.format("/bustime/api/%s/getstops", API_VERSION); + private static final String PREDICTIONS_ENDPOINT = String.format("/bustime/api/%s/getpredictions", API_VERSION); + private static final String DETOURS_ENDPOINT = String.format("/bustime/api/%s/getdetours", API_VERSION); + private static final String VEHICLES_ENDPOINT = String.format("/bustime/api/%s/getvehicles", API_VERSION); private final String host; private final String apiKey; @@ -78,6 +82,57 @@ private BusClientImpl(String host, String apiKey) { this.busMapper = Mappers.getMapper(BusMapper.class); } + @Override + public Optional getSystemTime() { + String url = new URIBuilder() + .setScheme("https") + .setHost(this.host) + .setPath(SYSTEM_TIME_ENDPOINT) + .addParameter("key", this.apiKey) + .addParameter("format", "json") + .toString(); + + String response = HttpUtils.get(url); + + TypeReference> typeReference = new TypeReference<>() {}; + CtaResponse timeResponse; + + try { + timeResponse = this.objectMapper.readValue(response, typeReference); + } catch (JacksonException e) { + String message = String.format("Failed to parse response from %s", SYSTEM_TIME_ENDPOINT); + + throw new Cta4jException(message, e); + } + + CtaBustimeResponse bustimeResponse = timeResponse.bustimeResponse(); + + List errors = bustimeResponse.error(); + String systemTime = bustimeResponse.data(); + + if ((errors == null) && (systemTime == null)) { + log.debug("System time bustime response missing both error and data from {}", SYSTEM_TIME_ENDPOINT); + + return Optional.empty(); + } + + if ((errors != null) && !errors.isEmpty()) { + String message = this.buildErrorMessage(SYSTEM_TIME_ENDPOINT, errors); + + throw new Cta4jException(message); + } + + if (systemTime == null) { + String message = String.format("No system time data returned from %s", SYSTEM_TIME_ENDPOINT); + + throw new Cta4jException(message); + } + + Instant instant = CtaBusMappingQualifiers.mapTimestamp(systemTime); + + return Optional.of(instant); + } + @Override public List getRoutes() { String url = new URIBuilder() @@ -90,8 +145,8 @@ public List getRoutes() { String response = HttpUtils.get(url); - TypeReference> typeReference = new TypeReference<>() {}; - CtaResponse routesResponse; + TypeReference>> typeReference = new TypeReference<>() {}; + CtaResponse> routesResponse; try { routesResponse = this.objectMapper.readValue(response, typeReference); @@ -101,7 +156,7 @@ public List getRoutes() { throw new Cta4jException(message, e); } - CtaBustimeResponse bustimeResponse = routesResponse.bustimeResponse(); + CtaBustimeResponse> bustimeResponse = routesResponse.bustimeResponse(); List errors = bustimeResponse.error(); List routes = bustimeResponse.data(); @@ -144,8 +199,8 @@ public List getDirections(String routeId) { String response = HttpUtils.get(url); - TypeReference> typeReference = new TypeReference<>() {}; - CtaResponse directionsResponse; + TypeReference>> typeReference = new TypeReference<>() {}; + CtaResponse> directionsResponse; try { directionsResponse = this.objectMapper.readValue(response, typeReference); @@ -155,7 +210,7 @@ public List getDirections(String routeId) { throw new Cta4jException(message, e); } - CtaBustimeResponse bustimeResponse = directionsResponse.bustimeResponse(); + CtaBustimeResponse> bustimeResponse = directionsResponse.bustimeResponse(); List errors = bustimeResponse.error(); List directions = bustimeResponse.data(); @@ -203,8 +258,8 @@ public List getStops(String routeId, String direction) { String response = HttpUtils.get(url); - TypeReference> typeReference = new TypeReference<>() {}; - CtaResponse stopsResponse; + TypeReference>> typeReference = new TypeReference<>() {}; + CtaResponse> stopsResponse; try { stopsResponse = this.objectMapper.readValue(response, typeReference); @@ -214,7 +269,7 @@ public List getStops(String routeId, String direction) { throw new Cta4jException(message, e); } - CtaBustimeResponse bustimeResponse = stopsResponse.bustimeResponse(); + CtaBustimeResponse> bustimeResponse = stopsResponse.bustimeResponse(); List errors = bustimeResponse.error(); List stops = bustimeResponse.data(); @@ -303,8 +358,8 @@ public List getDetours(String routeId, String direction) { String response = HttpUtils.get(url); - TypeReference> typeReference = new TypeReference<>() {}; - CtaResponse detoursResponse; + TypeReference>> typeReference = new TypeReference<>() {}; + CtaResponse> detoursResponse; try { detoursResponse = this.objectMapper.readValue(response, typeReference); @@ -314,7 +369,7 @@ public List getDetours(String routeId, String direction) { throw new Cta4jException(message, e); } - CtaBustimeResponse bustimeResponse = detoursResponse.bustimeResponse(); + CtaBustimeResponse> bustimeResponse = detoursResponse.bustimeResponse(); List errors = bustimeResponse.error(); List detours = bustimeResponse.data(); @@ -357,8 +412,8 @@ public Optional getBus(String id) { String response = HttpUtils.get(url); - TypeReference> typeReference = new TypeReference<>() {}; - CtaResponse vehicleResponse; + TypeReference>> typeReference = new TypeReference<>() {}; + CtaResponse> vehicleResponse; try { vehicleResponse = this.objectMapper.readValue(response, typeReference); @@ -368,7 +423,7 @@ public Optional getBus(String id) { throw new Cta4jException(message, e); } - CtaBustimeResponse bustimeResponse = vehicleResponse.bustimeResponse(); + CtaBustimeResponse> bustimeResponse = vehicleResponse.bustimeResponse(); List errors = bustimeResponse.error(); List vehicles = bustimeResponse.data(); @@ -414,8 +469,8 @@ private String buildErrorMessage(String endpoint, List errors) { private List getArrivals(String url) { String response = HttpUtils.get(url); - TypeReference> typeReference = new TypeReference<>() {}; - CtaResponse predictionsResponse; + TypeReference>> typeReference = new TypeReference<>() {}; + CtaResponse> predictionsResponse; try { predictionsResponse = this.objectMapper.readValue(response, typeReference); @@ -425,7 +480,7 @@ private List getArrivals(String url) { throw new Cta4jException(message, e); } - CtaBustimeResponse bustimeResponse = predictionsResponse.bustimeResponse(); + CtaBustimeResponse> bustimeResponse = predictionsResponse.bustimeResponse(); List errors = bustimeResponse.error(); List predictions = bustimeResponse.data(); diff --git a/src/main/java/com/cta4j/bus/external/CtaBustimeResponse.java b/src/main/java/com/cta4j/bus/external/CtaBustimeResponse.java index 17c6c4f..a214a25 100644 --- a/src/main/java/com/cta4j/bus/external/CtaBustimeResponse.java +++ b/src/main/java/com/cta4j/bus/external/CtaBustimeResponse.java @@ -13,6 +13,7 @@ public record CtaBustimeResponse( @Nullable @JsonAlias({ + "tm", "routes", "directions", "stops", @@ -20,6 +21,6 @@ public record CtaBustimeResponse( "dtrs", "vehicle", }) - List data + T data ) { } diff --git a/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java b/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java index f486e4f..03491ff 100644 --- a/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java +++ b/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java @@ -10,12 +10,13 @@ import java.time.Instant; import java.time.LocalDateTime; -import java.time.ZoneOffset; +import java.time.ZoneId; import java.time.format.DateTimeFormatter; @ApiStatus.Internal public final class CtaBusMappingQualifiers { - private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm"); + private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm[:ss]"); + private static final ZoneId CHICAGO_ZONE_ID = ZoneId.of("America/Chicago"); private CtaBusMappingQualifiers() { throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); @@ -45,7 +46,7 @@ public static Instant mapTimestamp(String timestamp) { } return LocalDateTime.parse(timestamp, TIMESTAMP_FORMATTER) - .atZone(ZoneOffset.UTC) + .atZone(CHICAGO_ZONE_ID) .toInstant(); } From 4fc335dca744caccba79eed2acf59c72807eab24 Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sun, 11 Jan 2026 15:00:04 -0600 Subject: [PATCH 27/53] JSpecify + Model updates --- .../java/com/cta4j/bus/client/BusClient.java | 85 ++++++++++-- .../bus/client/internal/BusClientImpl.java | 129 +++++++++++------- .../mapper/util/CtaBusMappingQualifiers.java | 2 +- 3 files changed, 156 insertions(+), 60 deletions(-) diff --git a/src/main/java/com/cta4j/bus/client/BusClient.java b/src/main/java/com/cta4j/bus/client/BusClient.java index 3050a3b..9e1e476 100644 --- a/src/main/java/com/cta4j/bus/client/BusClient.java +++ b/src/main/java/com/cta4j/bus/client/BusClient.java @@ -17,8 +17,81 @@ * A client for interacting with the CTA Bus Tracker API. */ @NullMarked +@SuppressWarnings("ConstantConditions") public interface BusClient { - Optional getSystemTime(); + /** + * Retrieves the current system time from the CTA Bus Tracker API. + * + * @return the current system time as an {@link Instant} + * @throws Cta4jException if an error occurs while fetching the data + */ + Instant getSystemTime(); + + /** + * Finds buses matching the specified bus IDs. + * + * @param ids an {@link Iterable} of bus IDs + * @return a {@link List} of buses corresponding to the specified IDs + * @throws IllegalArgumentException if the specified IDs are {@code null} + * @throws Cta4jException if an error occurs while fetching the data + */ + List findBusesById(Iterable ids); + + /** + * Finds buses operating on the specified route IDs. + * + * @param routeIds an {@link Iterable} of bus route IDs + * @return a {@link List} of buses corresponding to the specified route IDs + * @throws IllegalArgumentException if the specified route IDs are {@code null} + * @throws Cta4jException if an error occurs while fetching the data + */ + List findBusesByRouteId(Iterable routeIds); + + /** + * Finds buses operating on the specified route ID. + * + * @param routeId the bus route ID + * @return a {@link List} of buses corresponding to the specified route ID + * @throws IllegalArgumentException if the specified route ID is {@code null} + * @throws Cta4jException if an error occurs while fetching the data + */ + default List findBusesByRouteId(String routeId) { + if (routeId == null) { + throw new IllegalArgumentException("routeId must not be null"); + } + + List routeIds = List.of(routeId); + + return this.findBusesByRouteId(routeIds); + } + + /** + * Finds a bus by its ID. + * + * @param id the ID of the bus + * @return an {@link Optional} containing the bus information if found, or an empty {@link Optional} if not found + * @throws IllegalArgumentException if the specified bus ID is {@code null} + * @throws Cta4jException if an error occurs while fetching the data + */ + default Optional findBusById(String id) { + if (id == null) { + throw new IllegalArgumentException("id must not be null"); + } + + List ids = List.of(id); + + List buses = this.findBusesById(ids); + + if (buses.isEmpty()) { + return Optional.empty(); + } if (buses.size() > 1) { + throw new Cta4jException("Multiple buses found for ID: " + id); + } + + Bus bus = buses.getFirst(); + + return Optional.of(bus); + } /** * Retrieves a {@link List} of all bus routes. @@ -81,16 +154,6 @@ public interface BusClient { */ List getDetours(String routeId, String direction); - /** - * Retrieves information about a specific bus by its ID. - * - * @param id the ID of the bus - * @return an {@link Optional} containing the bus information if found, or an empty {@link Optional} if not found - * @throws IllegalArgumentException if the specified bus ID is {@code null} - * @throws Cta4jException if an error occurs while fetching the data - */ - Optional getBus(String id); - /** * A builder for configuring and creating {@link BusClient} instances. * diff --git a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java index 08a9026..c103cd7 100644 --- a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java +++ b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java @@ -36,7 +36,6 @@ import java.time.Instant; import java.util.List; -import java.util.Optional; @NullMarked @ApiStatus.Internal @@ -44,6 +43,7 @@ public final class BusClientImpl implements BusClient { private static final Logger log = LoggerFactory.getLogger(BusClientImpl.class); + private static final String SCHEME = "https"; private static final String DEFAULT_HOST = "ctabustracker.com"; private static final String API_VERSION = "v3"; private static final String SYSTEM_TIME_ENDPOINT = String.format("/bustime/api/%s/gettime", API_VERSION); @@ -83,9 +83,9 @@ private BusClientImpl(String host, String apiKey) { } @Override - public Optional getSystemTime() { + public Instant getSystemTime() { String url = new URIBuilder() - .setScheme("https") + .setScheme(SCHEME) .setHost(this.host) .setPath(SYSTEM_TIME_ENDPOINT) .addParameter("key", this.apiKey) @@ -111,9 +111,12 @@ public Optional getSystemTime() { String systemTime = bustimeResponse.data(); if ((errors == null) && (systemTime == null)) { - log.debug("System time bustime response missing both error and data from {}", SYSTEM_TIME_ENDPOINT); + String message = String.format( + "System time bustime response missing both error and data from %s", + SYSTEM_TIME_ENDPOINT + ); - return Optional.empty(); + throw new Cta4jException(message); } if ((errors != null) && !errors.isEmpty()) { @@ -128,15 +131,67 @@ public Optional getSystemTime() { throw new Cta4jException(message); } - Instant instant = CtaBusMappingQualifiers.mapTimestamp(systemTime); + return CtaBusMappingQualifiers.mapTimestamp(systemTime); + } + + @Override + public List findBusesById(Iterable ids) { + if (ids == null) { + throw new IllegalArgumentException("ids must not be null"); + } + + for (String id : ids) { + if (id == null) { + throw new IllegalArgumentException("ids must not contain null elements"); + } + } + + String idsString = String.join(",", ids); + + String url = new URIBuilder() + .setScheme(SCHEME) + .setHost(this.host) + .setPath(VEHICLES_ENDPOINT) + .addParameter("vid", idsString) + .addParameter("tmres", "s") + .addParameter("key", this.apiKey) + .addParameter("format", "json") + .toString(); + + return this.getBuses(url); + } + + @Override + public List findBusesByRouteId(Iterable routeIds) { + if (routeIds == null) { + throw new IllegalArgumentException("routeIds must not be null"); + } + + for (String routeId : routeIds) { + if (routeId == null) { + throw new IllegalArgumentException("routeIds must not contain null elements"); + } + } + + String routeIdsString = String.join(",", routeIds); + + String url = new URIBuilder() + .setScheme(SCHEME) + .setHost(this.host) + .setPath(VEHICLES_ENDPOINT) + .addParameter("rt", routeIdsString) + .addParameter("tmres", "s") + .addParameter("key", this.apiKey) + .addParameter("format", "json") + .toString(); - return Optional.of(instant); + return this.getBuses(url); } @Override public List getRoutes() { String url = new URIBuilder() - .setScheme("https") + .setScheme(SCHEME) .setHost(this.host) .setPath(ROUTES_ENDPOINT) .addParameter("key", this.apiKey) @@ -189,7 +244,7 @@ public List getDirections(String routeId) { } String url = new URIBuilder() - .setScheme("https") + .setScheme(SCHEME) .setHost(this.host) .setPath(DIRECTIONS_ENDPOINT) .addParameter("rt", routeId) @@ -247,7 +302,7 @@ public List getStops(String routeId, String direction) { } String url = new URIBuilder() - .setScheme("https") + .setScheme(SCHEME) .setHost(this.host) .setPath(STOPS_ENDPOINT) .addParameter("rt", routeId) @@ -306,7 +361,7 @@ public List getArrivalsByStop(String routeId, String stopId) { } String url = new URIBuilder() - .setScheme("https") + .setScheme(SCHEME) .setHost(this.host) .setPath(PREDICTIONS_ENDPOINT) .addParameter("rt", routeId) @@ -325,7 +380,7 @@ public List getArrivalsByBus(String busId) { } String url = new URIBuilder() - .setScheme("https") + .setScheme(SCHEME) .setHost(this.host) .setPath(PREDICTIONS_ENDPOINT) .addParameter("vid", busId) @@ -347,7 +402,7 @@ public List getDetours(String routeId, String direction) { } String url = new URIBuilder() - .setScheme("https") + .setScheme(SCHEME) .setHost(this.host) .setPath(DETOURS_ENDPOINT) .addParameter("rt", routeId) @@ -395,21 +450,16 @@ public List getDetours(String routeId, String direction) { .toList(); } - @Override - public Optional getBus(String id) { - if (id == null) { - throw new IllegalArgumentException("id must not be null"); - } + private String buildErrorMessage(String endpoint, List errors) { + String message = errors.stream() + .map(CtaError::msg) + .reduce("%s; %s"::formatted) + .orElse("Unknown error"); - String url = new URIBuilder() - .setScheme("https") - .setHost(this.host) - .setPath(VEHICLES_ENDPOINT) - .addParameter("vid", id) - .addParameter("key", this.apiKey) - .addParameter("format", "json") - .toString(); + return String.format("Error response from %s: %s", endpoint, message); + } + private List getBuses(String url) { String response = HttpUtils.get(url); TypeReference>> typeReference = new TypeReference<>() {}; @@ -431,7 +481,7 @@ public Optional getBus(String id) { if ((errors == null) && (vehicleResponse == null)) { log.debug("Vehicles bustime response missing both error and data from {}", VEHICLES_ENDPOINT); - return Optional.empty(); + return List.of(); } if ((errors != null) && !errors.isEmpty()) { @@ -441,29 +491,12 @@ public Optional getBus(String id) { } if ((vehicles == null) || vehicles.isEmpty()) { - return Optional.empty(); - } - - if (vehicles.size() > 1) { - String message = String.format("Multiple buses found for ID %s", id); - - throw new Cta4jException(message); + return List.of(); } - CtaVehicle vehicle = vehicles.getFirst(); - - Bus bus = this.busMapper.toDomain(vehicle); - - return Optional.of(bus); - } - - private String buildErrorMessage(String endpoint, List errors) { - String message = errors.stream() - .map(CtaError::msg) - .reduce("%s; %s"::formatted) - .orElse("Unknown error"); - - return String.format("Error response from %s: %s", endpoint, message); + return vehicles.stream() + .map(this.busMapper::toDomain) + .toList(); } private List getArrivals(String url) { diff --git a/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java b/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java index 03491ff..9414771 100644 --- a/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java +++ b/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java @@ -15,7 +15,7 @@ @ApiStatus.Internal public final class CtaBusMappingQualifiers { - private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm[:ss]"); + private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm:ss"); private static final ZoneId CHICAGO_ZONE_ID = ZoneId.of("America/Chicago"); private CtaBusMappingQualifiers() { From 6fdb075270d4d464a9fe36659a8b289913eb0530 Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sun, 11 Jan 2026 15:13:08 -0600 Subject: [PATCH 28/53] JSpecify + Model updates --- README.md | 2 +- .../java/com/cta4j/bus/client/BusClient.java | 24 +++++++++---------- .../bus/client/internal/BusClientImpl.java | 10 ++++---- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 6f1dc00..56d8db6 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ public final class Application { .apiKey("BUS_API_KEY") .build(); - busClient.getArrivalsByStop("22", "1828") + busClient.findArrivalsByRouteIdAndStopId("22", "1828") .stream() .map(arrival -> String.format( "%s-bound bus is arriving at %s in %d minutes", diff --git a/src/main/java/com/cta4j/bus/client/BusClient.java b/src/main/java/com/cta4j/bus/client/BusClient.java index 9e1e476..7e70645 100644 --- a/src/main/java/com/cta4j/bus/client/BusClient.java +++ b/src/main/java/com/cta4j/bus/client/BusClient.java @@ -102,17 +102,17 @@ default Optional findBusById(String id) { List getRoutes(); /** - * Retrieves a {@link List} of directions for a specific bus route. + * Finds directions for the specified route ID. * * @param routeId the ID of the bus route * @return a {@link List} of directions for the specified bus route * @throws IllegalArgumentException if the specified bus route is {@code null} * @throws Cta4jException if an error occurs while fetching the data */ - List getDirections(String routeId); + List findDirectionsByRouteId(String routeId); /** - * Retrieves a {@link List} of stops for a specific bus route and direction. + * Finds stops for the specified route ID and direction. * * @param routeId the ID of the bus route * @param direction the direction of the bus route @@ -120,31 +120,31 @@ default Optional findBusById(String id) { * @throws IllegalArgumentException if the specified bus route or direction is {@code null} * @throws Cta4jException if an error occurs while fetching the data */ - List getStops(String routeId, String direction); + List findStopsByRouteIdAndDirection(String routeId, String direction); /** - * Retrieves a {@link List} of upcoming arrivals for a specific bus route and stop. + * Finds arrivals for the specified route ID and stop ID. * * @param routeId the ID of the bus route * @param stopId the ID of the bus stop - * @return a {@link List} of upcoming arrivals for the specified bus route and stop + * @return a {@link List} of arrivals for the specified bus route and stop * @throws IllegalArgumentException if the specified bus route or stop is {@code null} * @throws Cta4jException if an error occurs while fetching the data */ - List getArrivalsByStop(String routeId, String stopId); + List findArrivalsByRouteIdAndStopId(String routeId, String stopId); /** - * Retrieves a {@link List} of upcoming arrivals for a specific bus by its ID. + * Finds arrivals for the specified bus ID. * * @param busId the ID of the bus - * @return a {@link List} of upcoming arrivals for the specified bus + * @return a {@link List} of arrivals for the specified bus * @throws IllegalArgumentException if the specified bus ID is {@code null} * @throws Cta4jException if an error occurs while fetching the data */ - List getArrivalsByBus(String busId); + List findArrivalsByBusId(String busId); /** - * Retrieves a {@link List} of detours for a specific bus route and direction. + * Finds detours for the specified route ID and direction. * * @param routeId the ID of the bus route * @param direction the direction of the bus route @@ -152,7 +152,7 @@ default Optional findBusById(String id) { * @throws IllegalArgumentException if the specified bus route or direction is {@code null} * @throws Cta4jException if an error occurs while fetching the data */ - List getDetours(String routeId, String direction); + List findDetoursByRouteIdAndDirection(String routeId, String direction); /** * A builder for configuring and creating {@link BusClient} instances. diff --git a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java index c103cd7..09d7cdf 100644 --- a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java +++ b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java @@ -238,7 +238,7 @@ public List getRoutes() { } @Override - public List getDirections(String routeId) { + public List findDirectionsByRouteId(String routeId) { if (routeId == null) { throw new IllegalArgumentException("routeId must not be null"); } @@ -292,7 +292,7 @@ public List getDirections(String routeId) { } @Override - public List getStops(String routeId, String direction) { + public List findStopsByRouteIdAndDirection(String routeId, String direction) { if (routeId == null) { throw new IllegalArgumentException("routeId must not be null"); } @@ -351,7 +351,7 @@ public List getStops(String routeId, String direction) { } @Override - public List getArrivalsByStop(String routeId, String stopId) { + public List findArrivalsByRouteIdAndStopId(String routeId, String stopId) { if (routeId == null) { throw new IllegalArgumentException("routeId must not be null"); } @@ -374,7 +374,7 @@ public List getArrivalsByStop(String routeId, String stopId) { } @Override - public List getArrivalsByBus(String busId) { + public List findArrivalsByBusId(String busId) { if (busId == null) { throw new IllegalArgumentException("busId must not be null"); } @@ -392,7 +392,7 @@ public List getArrivalsByBus(String busId) { } @Override - public List getDetours(String routeId, String direction) { + public List findDetoursByRouteIdAndDirection(String routeId, String direction) { if (routeId == null) { throw new IllegalArgumentException("routeId must not be null"); } From afdd10bada8e0b2c148d6f759bf7d07f7346d4ec Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sun, 11 Jan 2026 15:19:07 -0600 Subject: [PATCH 29/53] JSpecify + Model updates --- .../bus/client/internal/BusClientImpl.java | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java index 09d7cdf..563e108 100644 --- a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java +++ b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java @@ -45,14 +45,14 @@ public final class BusClientImpl implements BusClient { private static final String SCHEME = "https"; private static final String DEFAULT_HOST = "ctabustracker.com"; - private static final String API_VERSION = "v3"; - private static final String SYSTEM_TIME_ENDPOINT = String.format("/bustime/api/%s/gettime", API_VERSION); - private static final String ROUTES_ENDPOINT = String.format("/bustime/api/%s/getroutes", API_VERSION); - private static final String DIRECTIONS_ENDPOINT = String.format("/bustime/api/%s/getdirections", API_VERSION); - private static final String STOPS_ENDPOINT = String.format("/bustime/api/%s/getstops", API_VERSION); - private static final String PREDICTIONS_ENDPOINT = String.format("/bustime/api/%s/getpredictions", API_VERSION); - private static final String DETOURS_ENDPOINT = String.format("/bustime/api/%s/getdetours", API_VERSION); - private static final String VEHICLES_ENDPOINT = String.format("/bustime/api/%s/getvehicles", API_VERSION); + private static final String API_PREFIX = "/bustime/api/v3"; + private static final String SYSTEM_TIME_ENDPOINT = String.format("%s/gettime", API_PREFIX); + private static final String ROUTES_ENDPOINT = String.format("%s/getroutes", API_PREFIX); + private static final String DIRECTIONS_ENDPOINT = String.format("%s/getdirections", API_PREFIX); + private static final String STOPS_ENDPOINT = String.format("%s/getstops", API_PREFIX); + private static final String PREDICTIONS_ENDPOINT = String.format("%s/getpredictions", API_PREFIX); + private static final String DETOURS_ENDPOINT = String.format("%s/getdetours", API_PREFIX); + private static final String VEHICLES_ENDPOINT = String.format("%s/getvehicles", API_PREFIX); private final String host; private final String apiKey; @@ -366,6 +366,7 @@ public List findArrivalsByRouteIdAndStopId(String routeId, String stopI .setPath(PREDICTIONS_ENDPOINT) .addParameter("rt", routeId) .addParameter("stpid", stopId) + .addParameter("tmres", "s") .addParameter("key", this.apiKey) .addParameter("format", "json") .toString(); @@ -384,6 +385,7 @@ public List findArrivalsByBusId(String busId) { .setHost(this.host) .setPath(PREDICTIONS_ENDPOINT) .addParameter("vid", busId) + .addParameter("tmres", "s") .addParameter("key", this.apiKey) .addParameter("format", "json") .toString(); @@ -407,6 +409,7 @@ public List findDetoursByRouteIdAndDirection(String routeId, String dire .setPath(DETOURS_ENDPOINT) .addParameter("rt", routeId) .addParameter("dir", direction) + .addParameter("tmres", "s") //TODO: Is tmres an option for detours? .addParameter("key", this.apiKey) .addParameter("format", "json") .toString(); From 2300f809905dc6942eea36c512829d6ef4517258 Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Fri, 16 Jan 2026 14:12:33 -0600 Subject: [PATCH 30/53] Added pattern endpoints --- .../java/com/cta4j/bus/client/BusClient.java | 83 ++++++++- .../bus/client/internal/BusClientImpl.java | 163 +++++++++++++++++- .../com/cta4j/bus/external/CtaPattern.java | 44 +++++ .../java/com/cta4j/bus/external/CtaPoint.java | 35 ++++ .../cta4j/bus/mapper/RoutePatternMapper.java | 30 ++++ .../com/cta4j/bus/model/PatternPoint.java | 41 +++++ .../com/cta4j/bus/model/PatternPointType.java | 6 + .../com/cta4j/bus/model/RoutePattern.java | 40 +++++ 8 files changed, 440 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/cta4j/bus/external/CtaPattern.java create mode 100644 src/main/java/com/cta4j/bus/external/CtaPoint.java create mode 100644 src/main/java/com/cta4j/bus/mapper/RoutePatternMapper.java create mode 100644 src/main/java/com/cta4j/bus/model/PatternPoint.java create mode 100644 src/main/java/com/cta4j/bus/model/PatternPointType.java create mode 100644 src/main/java/com/cta4j/bus/model/RoutePattern.java diff --git a/src/main/java/com/cta4j/bus/client/BusClient.java b/src/main/java/com/cta4j/bus/client/BusClient.java index 7e70645..d3ab5ce 100644 --- a/src/main/java/com/cta4j/bus/client/BusClient.java +++ b/src/main/java/com/cta4j/bus/client/BusClient.java @@ -5,6 +5,7 @@ import com.cta4j.bus.model.Bus; import com.cta4j.bus.model.Detour; import com.cta4j.bus.model.Route; +import com.cta4j.bus.model.RoutePattern; import com.cta4j.bus.model.Stop; import com.cta4j.exception.Cta4jException; import org.jspecify.annotations.NullMarked; @@ -85,7 +86,9 @@ default Optional findBusById(String id) { if (buses.isEmpty()) { return Optional.empty(); } if (buses.size() > 1) { - throw new Cta4jException("Multiple buses found for ID: " + id); + String message = String.format("Multiple buses found for ID: %s", id); + + throw new Cta4jException(message); } Bus bus = buses.getFirst(); @@ -122,6 +125,84 @@ default Optional findBusById(String id) { */ List findStopsByRouteIdAndDirection(String routeId, String direction); + /** + * Finds stops for the specified stop IDs. + * + * @param stopIds an {@link Iterable} of stop IDs + * @return a {@link List} of stops corresponding to the specified stop IDs + * @throws IllegalArgumentException if the specified stop IDs are {@code null} + * @throws Cta4jException if an error occurs while fetching the data + */ + List findStopsByStopId(Iterable stopIds); + + /** + * Finds a stop by its ID. + * + * @param stopId the ID of the stop + * @return an {@link Optional} containing the stop information if found, or an empty {@link Optional} if not found + * @throws IllegalArgumentException if the specified stop ID is {@code null} + * @throws Cta4jException if an error occurs while fetching the data + */ + default Optional findStopsByStopId(String stopId) { + if (stopId == null) { + throw new IllegalArgumentException("stopId must not be null"); + } + + List ids = List.of(stopId); + + List stops = this.findStopsByStopId(ids); + + if (stops.isEmpty()) { + return Optional.empty(); + } if (stops.size() > 1) { + String message = String.format("Multiple stops found for ID: %s", stopId); + + throw new Cta4jException(message); + } + + Stop stop = stops.getFirst(); + + return Optional.of(stop); + } + + /** + * Finds route patterns for the specified pattern IDs. + * + * @param patternIds an {@link Iterable} of route pattern IDs + * @return a {@link List} of route patterns corresponding to the specified pattern IDs + * @throws IllegalArgumentException if the specified pattern IDs are {@code null} + * @throws Cta4jException if an error occurs while fetching the data + */ + List findPatternsByPatternId(Iterable patternIds); + + /** + * Finds route patterns for the specified pattern ID. + * + * @param patternId the ID of the route pattern + * @return a {@link List} of route patterns corresponding to the specified pattern ID + * @throws IllegalArgumentException if the specified pattern ID is {@code null} + * @throws Cta4jException if an error occurs while fetching the data + */ + default List findPatternsByPatternId(String patternId) { + if (patternId == null) { + throw new IllegalArgumentException("patternId must not be null"); + } + + List ids = List.of(patternId); + + return this.findPatternsByPatternId(ids); + } + + /** + * Finds route patterns for the specified route ID. + * + * @param routeId the ID of the bus route + * @return a {@link List} of route patterns for the specified bus route + * @throws IllegalArgumentException if the specified bus route is {@code null} + * @throws Cta4jException if an error occurs while fetching the data + */ + List findPatternsByRouteId(String routeId); + /** * Finds arrivals for the specified route ID and stop ID. * diff --git a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java index 563e108..e645f5f 100644 --- a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java +++ b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java @@ -3,18 +3,21 @@ import com.cta4j.bus.client.BusClient; import com.cta4j.bus.external.CtaBustimeResponse; import com.cta4j.bus.external.CtaError; +import com.cta4j.bus.external.CtaPattern; import com.cta4j.bus.external.CtaResponse; import com.cta4j.bus.external.CtaPrediction; import com.cta4j.bus.mapper.ArrivalMapper; import com.cta4j.bus.mapper.BusMapper; import com.cta4j.bus.mapper.DetourMapper; import com.cta4j.bus.mapper.RouteMapper; +import com.cta4j.bus.mapper.RoutePatternMapper; import com.cta4j.bus.mapper.StopMapper; import com.cta4j.bus.mapper.util.CtaBusMappingQualifiers; import com.cta4j.bus.model.Arrival; import com.cta4j.bus.model.Bus; import com.cta4j.bus.model.Detour; import com.cta4j.bus.model.Route; +import com.cta4j.bus.model.RoutePattern; import com.cta4j.bus.model.Stop; import com.cta4j.exception.Cta4jException; import com.cta4j.bus.external.CtaDetour; @@ -50,6 +53,7 @@ public final class BusClientImpl implements BusClient { private static final String ROUTES_ENDPOINT = String.format("%s/getroutes", API_PREFIX); private static final String DIRECTIONS_ENDPOINT = String.format("%s/getdirections", API_PREFIX); private static final String STOPS_ENDPOINT = String.format("%s/getstops", API_PREFIX); + private static final String PATTERNS_ENDPOINT = String.format("%s/getpatterns", API_PREFIX); private static final String PREDICTIONS_ENDPOINT = String.format("%s/getpredictions", API_PREFIX); private static final String DETOURS_ENDPOINT = String.format("%s/getdetours", API_PREFIX); private static final String VEHICLES_ENDPOINT = String.format("%s/getvehicles", API_PREFIX); @@ -60,6 +64,7 @@ public final class BusClientImpl implements BusClient { private final ArrivalMapper arrivalMapper; private final RouteMapper routeMapper; private final StopMapper stopMapper; + private final RoutePatternMapper routePatternMapper; private final DetourMapper detourMapper; private final BusMapper busMapper; @@ -78,6 +83,7 @@ private BusClientImpl(String host, String apiKey) { this.arrivalMapper = Mappers.getMapper(ArrivalMapper.class); this.routeMapper = Mappers.getMapper(RouteMapper.class); this.stopMapper = Mappers.getMapper(StopMapper.class); + this.routePatternMapper = Mappers.getMapper(RoutePatternMapper.class); this.detourMapper = Mappers.getMapper(DetourMapper.class); this.busMapper = Mappers.getMapper(BusMapper.class); } @@ -330,7 +336,12 @@ public List findStopsByRouteIdAndDirection(String routeId, String directio List stops = bustimeResponse.data(); if ((errors == null) && (stops == null)) { - log.debug("Stops bustime response missing both error and data from {}", STOPS_ENDPOINT); + log.debug( + "Stops bustime response missing both error and data for route={} direction={} from {}", + routeId, + direction, + STOPS_ENDPOINT + ); return List.of(); } @@ -350,6 +361,116 @@ public List findStopsByRouteIdAndDirection(String routeId, String directio .toList(); } + @Override + public List findStopsByStopId(Iterable stopIds) { + if (stopIds == null) { + throw new IllegalArgumentException("stopIds must not be null"); + } + + for (String stopId : stopIds) { + if (stopId == null) { + throw new IllegalArgumentException("stopIds must not contain null elements"); + } + } + + String stopIdsString = String.join(",", stopIds); + + String url = new URIBuilder() + .setScheme(SCHEME) + .setHost(this.host) + .setPath(STOPS_ENDPOINT) + .addParameter("stpid", stopIdsString) + .addParameter("key", this.apiKey) + .addParameter("format", "json") + .toString(); + + String response = HttpUtils.get(url); + + TypeReference>> typeReference = new TypeReference<>() {}; + CtaResponse> stopsResponse; + + try { + stopsResponse = this.objectMapper.readValue(response, typeReference); + } catch (JacksonException e) { + String message = String.format("Failed to parse response from %s", STOPS_ENDPOINT); + + throw new Cta4jException(message, e); + } + + CtaBustimeResponse> bustimeResponse = stopsResponse.bustimeResponse(); + + List errors = bustimeResponse.error(); + List stops = bustimeResponse.data(); + + if ((errors == null) && (stops == null)) { + log.debug( + "Stop bustime response missing both error and data for stopIds={} from {}", + stopIds, + STOPS_ENDPOINT + ); + + return List.of(); + } + + if ((errors != null) && !errors.isEmpty()) { + String message = this.buildErrorMessage(STOPS_ENDPOINT, errors); + + throw new Cta4jException(message); + } + + if ((stops == null) || stops.isEmpty()) { + return List.of(); + } + + return stops.stream() + .map(this.stopMapper::toDomain) + .toList(); + } + + @Override + public List findPatternsByPatternId(Iterable patternIds) { + if (patternIds == null) { + throw new IllegalArgumentException("patternIds must not be null"); + } + + for (String patternId : patternIds) { + if (patternId == null) { + throw new IllegalArgumentException("patternIds must not contain null elements"); + } + } + + String patternIdsString = String.join(",", patternIds); + + String url = new URIBuilder() + .setScheme(SCHEME) + .setHost(this.host) + .setPath(PATTERNS_ENDPOINT) + .addParameter("pid", patternIdsString) + .addParameter("key", this.apiKey) + .addParameter("format", "json") + .toString(); + + return this.getPatterns(url); + } + + @Override + public List findPatternsByRouteId(String routeId) { + if (routeId == null) { + throw new IllegalArgumentException("routeId must not be null"); + } + + String url = new URIBuilder() + .setScheme(SCHEME) + .setHost(this.host) + .setPath(PATTERNS_ENDPOINT) + .addParameter("rt", routeId) + .addParameter("key", this.apiKey) + .addParameter("format", "json") + .toString(); + + return this.getPatterns(url); + } + @Override public List findArrivalsByRouteIdAndStopId(String routeId, String stopId) { if (routeId == null) { @@ -502,6 +623,46 @@ private List getBuses(String url) { .toList(); } + private List getPatterns(String url) { + String response = HttpUtils.get(url); + + TypeReference>> typeReference = new TypeReference<>() {}; + CtaResponse> patternsResponse; + + try { + patternsResponse = this.objectMapper.readValue(response, typeReference); + } catch (JacksonException e) { + String message = String.format("Failed to parse response from %s", PATTERNS_ENDPOINT); + + throw new Cta4jException(message, e); + } + + CtaBustimeResponse> bustimeResponse = patternsResponse.bustimeResponse(); + + List errors = bustimeResponse.error(); + List patterns = bustimeResponse.data(); + + if ((errors == null) && (patterns == null)) { + log.debug("Pattern bustime response missing both error and data from {}", PATTERNS_ENDPOINT); + + return List.of(); + } + + if ((errors != null) && !errors.isEmpty()) { + String message = this.buildErrorMessage(PATTERNS_ENDPOINT, errors); + + throw new Cta4jException(message); + } + + if ((patterns == null) || patterns.isEmpty()) { + return List.of(); + } + + return patterns.stream() + .map(this.routePatternMapper::toDomain) + .toList(); + } + private List getArrivals(String url) { String response = HttpUtils.get(url); diff --git a/src/main/java/com/cta4j/bus/external/CtaPattern.java b/src/main/java/com/cta4j/bus/external/CtaPattern.java new file mode 100644 index 0000000..c233cb0 --- /dev/null +++ b/src/main/java/com/cta4j/bus/external/CtaPattern.java @@ -0,0 +1,44 @@ +package com.cta4j.bus.external; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.util.List; + +@NullMarked +@ApiStatus.Internal +@SuppressWarnings("ConstantConditions") +@JsonIgnoreProperties(ignoreUnknown = true) +public record CtaPattern( + int pid, + + int ln, + + String rtdir, + + List pt, + + @Nullable + String dtrid, + + @Nullable + List dtrpt +) { + public CtaPattern { + if (rtdir == null) { + throw new IllegalArgumentException("rtdir must not be null"); + } + + if (pt == null) { + throw new IllegalArgumentException("pt must not be null"); + } + + for (CtaPoint point : pt) { + if (point == null) { + throw new IllegalArgumentException("pt must not contain null elements"); + } + } + } +} diff --git a/src/main/java/com/cta4j/bus/external/CtaPoint.java b/src/main/java/com/cta4j/bus/external/CtaPoint.java new file mode 100644 index 0000000..a563c30 --- /dev/null +++ b/src/main/java/com/cta4j/bus/external/CtaPoint.java @@ -0,0 +1,35 @@ +package com.cta4j.bus.external; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +@ApiStatus.Internal +@SuppressWarnings("ConstantConditions") +@JsonIgnoreProperties(ignoreUnknown = true) +public record CtaPoint( + int seq, + + String typ, + + @Nullable + String stpid, + + @Nullable + String stpnm, + + @Nullable + Float pdist, + + double lat, + + double lon +) { + public CtaPoint { + if (typ == null) { + throw new IllegalArgumentException("typ must not be null"); + } + } +} diff --git a/src/main/java/com/cta4j/bus/mapper/RoutePatternMapper.java b/src/main/java/com/cta4j/bus/mapper/RoutePatternMapper.java new file mode 100644 index 0000000..edbf9a8 --- /dev/null +++ b/src/main/java/com/cta4j/bus/mapper/RoutePatternMapper.java @@ -0,0 +1,30 @@ +package com.cta4j.bus.mapper; + +import com.cta4j.bus.external.CtaPattern; +import com.cta4j.bus.external.CtaPoint; +import com.cta4j.bus.model.PatternPoint; +import com.cta4j.bus.model.RoutePattern; +import org.jetbrains.annotations.ApiStatus; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@ApiStatus.Internal +@Mapper +public interface RoutePatternMapper { + @Mapping(source = "pid", target = "patternId") + @Mapping(source = "ln", target = "patternCount") + @Mapping(source = "rtdir", target = "direction") + @Mapping(source = "pt", target = "points") + @Mapping(source = "dtrid", target = "detourId") + @Mapping(source = "dtrpt", target = "detourPoints") + RoutePattern toDomain(CtaPattern pattern); + + @Mapping(source = "seq", target = "sequence") + @Mapping(source = "typ", target = "type") + @Mapping(source = "stpid", target = "stopId") + @Mapping(source = "stpnm", target = "stopName") + @Mapping(source = "pdist", target = "distanceToPatternPoint") + @Mapping(source = "lat", target = "latitude") + @Mapping(source = "lon", target = "longitude") + PatternPoint toDomain(CtaPoint point); +} diff --git a/src/main/java/com/cta4j/bus/model/PatternPoint.java b/src/main/java/com/cta4j/bus/model/PatternPoint.java new file mode 100644 index 0000000..3ffa75b --- /dev/null +++ b/src/main/java/com/cta4j/bus/model/PatternPoint.java @@ -0,0 +1,41 @@ +package com.cta4j.bus.model; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.math.BigDecimal; + +@NullMarked +@SuppressWarnings("ConstantConditions") +public record PatternPoint( + int sequence, + + PatternPointType type, + + @Nullable + String stopId, + + @Nullable + String stopName, + + @Nullable + BigDecimal distanceToPatternPoint, + + BigDecimal latitude, + + BigDecimal longitude +) { + public PatternPoint { + if (type == null) { + throw new IllegalArgumentException("type must not be null"); + } + + if (latitude == null) { + throw new IllegalArgumentException("latitude must not be null"); + } + + if (longitude == null) { + throw new IllegalArgumentException("longitude must not be null"); + } + } +} diff --git a/src/main/java/com/cta4j/bus/model/PatternPointType.java b/src/main/java/com/cta4j/bus/model/PatternPointType.java new file mode 100644 index 0000000..f2bf938 --- /dev/null +++ b/src/main/java/com/cta4j/bus/model/PatternPointType.java @@ -0,0 +1,6 @@ +package com.cta4j.bus.model; + +public enum PatternPointType { + STOP, + WAYPOINT +} diff --git a/src/main/java/com/cta4j/bus/model/RoutePattern.java b/src/main/java/com/cta4j/bus/model/RoutePattern.java new file mode 100644 index 0000000..5703da4 --- /dev/null +++ b/src/main/java/com/cta4j/bus/model/RoutePattern.java @@ -0,0 +1,40 @@ +package com.cta4j.bus.model; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.util.List; + +@NullMarked +@SuppressWarnings("ConstantConditions") +public record RoutePattern( + String patternId, + + int patternCount, + + String direction, + + List points, + + @Nullable + String detourId, + + @Nullable + List detourPoints +) { + public RoutePattern { + if (direction == null) { + throw new IllegalArgumentException("direction must not be null"); + } + + if (points == null) { + throw new IllegalArgumentException("points must not be null"); + } + + for (PatternPoint point : points) { + if (point == null) { + throw new IllegalArgumentException("points must not contain null elements"); + } + } + } +} From 66739a3be6908ea5873f7c8a94dae4f51bb27a1b Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Fri, 16 Jan 2026 14:19:16 -0600 Subject: [PATCH 31/53] Added pattern endpoints --- .../bus/external/CtaBustimeResponse.java | 1 + .../cta4j/bus/mapper/RoutePatternMapper.java | 5 +++-- .../mapper/util/CtaBusMappingQualifiers.java | 20 ++++++++++++++++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/cta4j/bus/external/CtaBustimeResponse.java b/src/main/java/com/cta4j/bus/external/CtaBustimeResponse.java index a214a25..141b625 100644 --- a/src/main/java/com/cta4j/bus/external/CtaBustimeResponse.java +++ b/src/main/java/com/cta4j/bus/external/CtaBustimeResponse.java @@ -17,6 +17,7 @@ public record CtaBustimeResponse( "routes", "directions", "stops", + "ptr", "prd", "dtrs", "vehicle", diff --git a/src/main/java/com/cta4j/bus/mapper/RoutePatternMapper.java b/src/main/java/com/cta4j/bus/mapper/RoutePatternMapper.java index edbf9a8..d1febdb 100644 --- a/src/main/java/com/cta4j/bus/mapper/RoutePatternMapper.java +++ b/src/main/java/com/cta4j/bus/mapper/RoutePatternMapper.java @@ -2,6 +2,7 @@ import com.cta4j.bus.external.CtaPattern; import com.cta4j.bus.external.CtaPoint; +import com.cta4j.bus.mapper.util.CtaBusMappingQualifiers; import com.cta4j.bus.model.PatternPoint; import com.cta4j.bus.model.RoutePattern; import org.jetbrains.annotations.ApiStatus; @@ -9,7 +10,7 @@ import org.mapstruct.Mapping; @ApiStatus.Internal -@Mapper +@Mapper(uses = CtaBusMappingQualifiers.class) public interface RoutePatternMapper { @Mapping(source = "pid", target = "patternId") @Mapping(source = "ln", target = "patternCount") @@ -20,7 +21,7 @@ public interface RoutePatternMapper { RoutePattern toDomain(CtaPattern pattern); @Mapping(source = "seq", target = "sequence") - @Mapping(source = "typ", target = "type") + @Mapping(source = "typ", target = "type", qualifiedByName = "mapPatternPointType") @Mapping(source = "stpid", target = "stopId") @Mapping(source = "stpnm", target = "stopName") @Mapping(source = "pdist", target = "distanceToPatternPoint") diff --git a/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java b/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java index 9414771..fd91000 100644 --- a/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java +++ b/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java @@ -3,6 +3,7 @@ import com.cta4j.bus.model.DynamicAction; import com.cta4j.bus.model.FlagStop; import com.cta4j.bus.model.PassengerLoad; +import com.cta4j.bus.model.PatternPointType; import com.cta4j.bus.model.PredictionType; import com.cta4j.bus.model.TransitMode; import org.jetbrains.annotations.ApiStatus; @@ -42,7 +43,7 @@ public static PredictionType mapPredictionType(String typ) { @Named("mapTimestamp") public static Instant mapTimestamp(String timestamp) { if (timestamp == null) { - return null; + throw new IllegalArgumentException("timestamp must not be null"); } return LocalDateTime.parse(timestamp, TIMESTAMP_FORMATTER) @@ -112,4 +113,21 @@ public static TransitMode mapTransitMode(int mode) { public static boolean mapActive(int st) { return st == 1; } + + @Named("mapPatternPointType") + public static PatternPointType mapPatternPointType(String type) { + if (type == null) { + throw new IllegalArgumentException("type must not be null"); + } + + return switch (type) { + case "S" -> PatternPointType.STOP; + case "W" -> PatternPointType.WAYPOINT; + default -> { + String message = String.format("Unknown pattern point type: %s", type); + + throw new IllegalArgumentException(message); + } + }; + } } From 08149ed6220fac0158b254bc49213d568cd9a44b Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Fri, 16 Jan 2026 15:20:31 -0600 Subject: [PATCH 32/53] API refactor --- CHANGELOG.md | 10 +- README.md | 10 +- pom.xml | 8 +- src/main/java/com/cta4j/bus/api/ApiUtils.java | 40 +++++ src/main/java/com/cta4j/bus/api/BusApi.java | 25 +++ .../java/com/cta4j/bus/api/DetoursApi.java | 4 + .../java/com/cta4j/bus/api/DirectionsApi.java | 4 + .../java/com/cta4j/bus/api/LocalesApi.java | 4 + .../java/com/cta4j/bus/api/PatternsApi.java | 4 + .../com/cta4j/bus/api/PredictionsApi.java | 4 + .../java/com/cta4j/bus/api/RoutesApi.java | 4 + src/main/java/com/cta4j/bus/api/StopsApi.java | 4 + .../cta4j/bus/api/vehicle/VehiclesApi.java | 47 ++++++ .../vehicle}/external/CtaVehicle.java | 2 +- .../bus/api/vehicle/impl/VehiclesApiImpl.java | 157 ++++++++++++++++++ .../vehicle/mapper/VehicleMapper.java} | 10 +- .../vehicle/model/Vehicle.java} | 10 +- .../vehicle/model/VehicleCoordinates.java} | 6 +- .../vehicle/model/VehicleMetadata.java} | 8 +- .../java/com/cta4j/bus/client/BusClient.java | 20 +-- .../bus/client/internal/BusClientImpl.java | 18 +- 21 files changed, 349 insertions(+), 50 deletions(-) create mode 100644 src/main/java/com/cta4j/bus/api/ApiUtils.java create mode 100644 src/main/java/com/cta4j/bus/api/BusApi.java create mode 100644 src/main/java/com/cta4j/bus/api/DetoursApi.java create mode 100644 src/main/java/com/cta4j/bus/api/DirectionsApi.java create mode 100644 src/main/java/com/cta4j/bus/api/LocalesApi.java create mode 100644 src/main/java/com/cta4j/bus/api/PatternsApi.java create mode 100644 src/main/java/com/cta4j/bus/api/PredictionsApi.java create mode 100644 src/main/java/com/cta4j/bus/api/RoutesApi.java create mode 100644 src/main/java/com/cta4j/bus/api/StopsApi.java create mode 100644 src/main/java/com/cta4j/bus/api/vehicle/VehiclesApi.java rename src/main/java/com/cta4j/bus/{ => api/vehicle}/external/CtaVehicle.java (97%) create mode 100644 src/main/java/com/cta4j/bus/api/vehicle/impl/VehiclesApiImpl.java rename src/main/java/com/cta4j/bus/{mapper/BusMapper.java => api/vehicle/mapper/VehicleMapper.java} (90%) rename src/main/java/com/cta4j/bus/{model/Bus.java => api/vehicle/model/Vehicle.java} (83%) rename src/main/java/com/cta4j/bus/{model/BusCoordinates.java => api/vehicle/model/VehicleCoordinates.java} (88%) rename src/main/java/com/cta4j/bus/{model/BusMetadata.java => api/vehicle/model/VehicleMetadata.java} (89%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8780663..07e2f41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,14 +48,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- A new type `UpcomingBusArrival` representing upcoming bus arrival information. -- A new type `BusCoordinates` representing bus coordinates (latitude, longitude, heading). -- An `arrivals` field of type `List` has been added to the `Bus` class to provide information about upcoming bus arrivals. +- A new type `UpcomingBusArrival` representing upcoming vehicle arrival information. +- A new type `BusCoordinates` representing vehicle coordinates (latitude, longitude, heading). +- An `arrivals` field of type `List` has been added to the `Bus` class to provide information about upcoming vehicle arrivals. ### Changed - BREAKING CHANGE: Organized classes into packages by functionality: - - `com.cta4j.bus` for bus-related classes + - `com.cta4j.vehicle` for vehicle-related classes - `com.cta4j.train` for train-related classes - `com.cta4j.common` for shared/common classes - BREAKING CHANGE: Public concrete client classes replaced by interfaces with a fluent `Builder` API (e.g. `BusClient`, `TrainClient`) @@ -136,7 +136,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Vehicle` class and `getVehicle` method in `BusClient` class. - `parseString` method from `BusPredictionType` enum. - `fromExternal` method from `Detour` class. -- `fromExternal` method from bus `Route` class. +- `fromExternal` method from vehicle `Route` class. - `fromExternal` method from `Stop` class. - `fromExternal` method from `StopArrival` class. - `parseString` method from train `Route` enum. diff --git a/README.md b/README.md index 56d8db6..55b52db 100644 --- a/README.md +++ b/README.md @@ -79,10 +79,10 @@ public final class Application { } ``` -### Fetch upcoming bus arrivals for a stop +### Fetch upcoming vehicle arrivals for a stop ```java -import com.cta4j.bus.client.BusClient; +import com.cta4j.vehicle.client.BusClient; public final class Application { static void main(String[] args) { @@ -93,7 +93,7 @@ public final class Application { busClient.findArrivalsByRouteIdAndStopId("22", "1828") .stream() .map(arrival -> String.format( - "%s-bound bus is arriving at %s in %d minutes", + "%s-bound vehicle is arriving at %s in %d minutes", arrival.destination(), arrival.stopName(), arrival.etaMinutes() @@ -101,8 +101,8 @@ public final class Application { .forEach(System.out::println); // Example output: - // Harrison-bound bus is arriving at Clark & Belmont in 1 minutes - // Harrison-bound bus is arriving at Clark & Belmont in 26 minutes + // Harrison-bound vehicle is arriving at Clark & Belmont in 1 minutes + // Harrison-bound vehicle is arriving at Clark & Belmont in 26 minutes } } ``` diff --git a/pom.xml b/pom.xml index 256cd3e..8ae235f 100644 --- a/pom.xml +++ b/pom.xml @@ -92,9 +92,9 @@ jar - com/cta4j/bus/client/internal/** - com/cta4j/bus/external/** - com/cta4j/bus/mapper/** + com/cta4j/vehicle/client/internal/** + com/cta4j/vehicle/external/** + com/cta4j/vehicle/mapper/** com/cta4j/train/client/internal/** com/cta4j/train/external/** com/cta4j/train/mapper/** @@ -116,7 +116,7 @@ - com.cta4j.bus.client.internal.*,com.cta4j.bus.external.*,com.cta4j.bus.mapper.*,com.cta4j.train.client.internal.*,com.cta4j.train.external.*,com.cta4j.train.mapper.*,com.cta4j.util.* + com.cta4j.vehicle.client.internal.*,com.cta4j.vehicle.external.*,com.cta4j.vehicle.mapper.*,com.cta4j.train.client.internal.*,com.cta4j.train.external.*,com.cta4j.train.mapper.*,com.cta4j.util.* diff --git a/src/main/java/com/cta4j/bus/api/ApiUtils.java b/src/main/java/com/cta4j/bus/api/ApiUtils.java new file mode 100644 index 0000000..4d2018c --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/ApiUtils.java @@ -0,0 +1,40 @@ +package com.cta4j.bus.api; + +import com.cta4j.bus.external.CtaError; +import org.jetbrains.annotations.ApiStatus; + +import java.util.List; + +@ApiStatus.Internal +public final class ApiUtils { + public static final String SCHEME = "https"; + public static final String DEFAULT_HOST = "ctabustracker.com"; + public static final String API_PREFIX = "/bustime/api/v3"; + + private ApiUtils() { + throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } + + public static String buildErrorMessage(String endpoint, List errors) { + if (endpoint == null) { + throw new IllegalArgumentException("endpoint must not be null"); + } + + if (errors == null) { + throw new IllegalArgumentException("errors must not be null"); + } + + for (CtaError error : errors) { + if (error == null) { + throw new IllegalArgumentException("errors must not contain null elements"); + } + } + + String message = errors.stream() + .map(CtaError::msg) + .reduce("%s; %s"::formatted) + .orElse("Unknown error"); + + return String.format("Error response from %s: %s", endpoint, message); + } +} diff --git a/src/main/java/com/cta4j/bus/api/BusApi.java b/src/main/java/com/cta4j/bus/api/BusApi.java new file mode 100644 index 0000000..61bcdc4 --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/BusApi.java @@ -0,0 +1,25 @@ +package com.cta4j.bus.api; + +import com.cta4j.bus.api.vehicle.VehiclesApi; + +import java.time.Instant; + +public interface BusApi { + Instant systemTime(); + + VehiclesApi vehicles(); + + RoutesApi routes(); + + DirectionsApi directions(); + + StopsApi stops(); + + PatternsApi patterns(); + + PredictionsApi predictions(); + + LocalesApi locales(); + + DetoursApi detours(); +} diff --git a/src/main/java/com/cta4j/bus/api/DetoursApi.java b/src/main/java/com/cta4j/bus/api/DetoursApi.java new file mode 100644 index 0000000..b458a0f --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/DetoursApi.java @@ -0,0 +1,4 @@ +package com.cta4j.bus.api; + +public interface DetoursApi { +} diff --git a/src/main/java/com/cta4j/bus/api/DirectionsApi.java b/src/main/java/com/cta4j/bus/api/DirectionsApi.java new file mode 100644 index 0000000..5cfd66e --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/DirectionsApi.java @@ -0,0 +1,4 @@ +package com.cta4j.bus.api; + +public interface DirectionsApi { +} diff --git a/src/main/java/com/cta4j/bus/api/LocalesApi.java b/src/main/java/com/cta4j/bus/api/LocalesApi.java new file mode 100644 index 0000000..64f90f5 --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/LocalesApi.java @@ -0,0 +1,4 @@ +package com.cta4j.bus.api; + +public interface LocalesApi { +} diff --git a/src/main/java/com/cta4j/bus/api/PatternsApi.java b/src/main/java/com/cta4j/bus/api/PatternsApi.java new file mode 100644 index 0000000..02e75c1 --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/PatternsApi.java @@ -0,0 +1,4 @@ +package com.cta4j.bus.api; + +public interface PatternsApi { +} diff --git a/src/main/java/com/cta4j/bus/api/PredictionsApi.java b/src/main/java/com/cta4j/bus/api/PredictionsApi.java new file mode 100644 index 0000000..a7e6738 --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/PredictionsApi.java @@ -0,0 +1,4 @@ +package com.cta4j.bus.api; + +public interface PredictionsApi { +} diff --git a/src/main/java/com/cta4j/bus/api/RoutesApi.java b/src/main/java/com/cta4j/bus/api/RoutesApi.java new file mode 100644 index 0000000..6565861 --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/RoutesApi.java @@ -0,0 +1,4 @@ +package com.cta4j.bus.api; + +public interface RoutesApi { +} diff --git a/src/main/java/com/cta4j/bus/api/StopsApi.java b/src/main/java/com/cta4j/bus/api/StopsApi.java new file mode 100644 index 0000000..eb5578b --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/StopsApi.java @@ -0,0 +1,4 @@ +package com.cta4j.bus.api; + +public interface StopsApi { +} diff --git a/src/main/java/com/cta4j/bus/api/vehicle/VehiclesApi.java b/src/main/java/com/cta4j/bus/api/vehicle/VehiclesApi.java new file mode 100644 index 0000000..923d9fb --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/vehicle/VehiclesApi.java @@ -0,0 +1,47 @@ +package com.cta4j.bus.api.vehicle; + +import com.cta4j.bus.api.vehicle.model.Vehicle; +import com.cta4j.exception.Cta4jException; +import org.jspecify.annotations.NonNull; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +public interface VehiclesApi { + @NonNull List findByIds(Collection ids); + + @NonNull default Optional findById(String id) { + if (id == null) { + throw new IllegalArgumentException("id must not be null"); + } + + List ids = List.of(id); + + List vehicles = this.findByIds(ids); + + if (vehicles.isEmpty()) { + return Optional.empty(); + } else if (vehicles.size() > 1) { + String message = String.format("Expected at most one bus for ID: %s, but found %d", id, vehicles.size()); + + throw new Cta4jException(message); + } + + Vehicle vehicle = vehicles.getFirst(); + + return Optional.of(vehicle); + } + + @NonNull List findByRouteIds(Collection routeIds); + + @NonNull default List findByRouteId(String routeId) { + if (routeId == null) { + throw new IllegalArgumentException("routeId must not be null"); + } + + List routeIds = List.of(routeId); + + return this.findByRouteIds(routeIds); + } +} diff --git a/src/main/java/com/cta4j/bus/external/CtaVehicle.java b/src/main/java/com/cta4j/bus/api/vehicle/external/CtaVehicle.java similarity index 97% rename from src/main/java/com/cta4j/bus/external/CtaVehicle.java rename to src/main/java/com/cta4j/bus/api/vehicle/external/CtaVehicle.java index 27c8dc9..25a16d1 100644 --- a/src/main/java/com/cta4j/bus/external/CtaVehicle.java +++ b/src/main/java/com/cta4j/bus/api/vehicle/external/CtaVehicle.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.external; +package com.cta4j.bus.api.vehicle.external; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.jetbrains.annotations.ApiStatus; diff --git a/src/main/java/com/cta4j/bus/api/vehicle/impl/VehiclesApiImpl.java b/src/main/java/com/cta4j/bus/api/vehicle/impl/VehiclesApiImpl.java new file mode 100644 index 0000000..b0bd5ad --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/vehicle/impl/VehiclesApiImpl.java @@ -0,0 +1,157 @@ +package com.cta4j.bus.api.vehicle.impl; + +import com.cta4j.bus.api.ApiUtils; +import com.cta4j.bus.api.vehicle.VehiclesApi; +import com.cta4j.bus.api.vehicle.external.CtaVehicle; +import com.cta4j.bus.api.vehicle.mapper.VehicleMapper; +import com.cta4j.bus.api.vehicle.model.Vehicle; +import com.cta4j.bus.external.CtaBustimeResponse; +import com.cta4j.bus.external.CtaError; +import com.cta4j.bus.external.CtaResponse; +import com.cta4j.exception.Cta4jException; +import com.cta4j.util.HttpUtils; +import org.apache.hc.core5.net.URIBuilder; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NonNull; +import org.mapstruct.factory.Mappers; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import tools.jackson.core.JacksonException; +import tools.jackson.core.type.TypeReference; +import tools.jackson.databind.ObjectMapper; + +import java.util.Collection; +import java.util.List; + +@ApiStatus.Internal +public final class VehiclesApiImpl implements VehiclesApi { + private static final Logger log = LoggerFactory.getLogger(VehiclesApiImpl.class); + + private static final String VEHICLES_ENDPOINT = String.format("%s/getvehicles", ApiUtils.API_PREFIX); + + private final String host; + private final String apiKey; + private final ObjectMapper objectMapper; + private final VehicleMapper vehicleMapper; + + public VehiclesApiImpl(String host, String apiKey, ObjectMapper objectMapper) { + if (host == null) { + throw new IllegalArgumentException("host must not be null"); + } + + if (apiKey == null) { + throw new IllegalArgumentException("apiKey must not be null"); + } + + if (objectMapper == null) { + throw new IllegalArgumentException("objectMapper must not be null"); + } + + this.host = host; + this.apiKey = apiKey; + this.objectMapper = objectMapper; + this.vehicleMapper = Mappers.getMapper(VehicleMapper.class); + } + + @Override + public @NonNull List findByIds(Collection ids) { + if (ids == null) { + throw new IllegalArgumentException("ids must not be null"); + } + + if (ids.isEmpty()) { + return List.of(); + } + + for (String id : ids) { + if (id == null) { + throw new IllegalArgumentException("ids must not contain null elements"); + } + } + + String idsString = String.join(",", ids); + + String url = new URIBuilder() + .setScheme(ApiUtils.SCHEME) + .setHost(this.host) + .setPath(VEHICLES_ENDPOINT) + .addParameter("vid", idsString) + .addParameter("tmres", "s") + .addParameter("key", this.apiKey) + .addParameter("format", "json") + .toString(); + + return this.getVehicles(url); + } + + @Override + public @NonNull List findByRouteIds(Collection routeIds) { + if (routeIds == null) { + throw new IllegalArgumentException("routeIds must not be null"); + } + + if (routeIds.isEmpty()) { + return List.of(); + } + + for (String routeId : routeIds) { + if (routeId == null) { + throw new IllegalArgumentException("routeIds must not contain null elements"); + } + } + + String routeIdsString = String.join(",", routeIds); + + String url = new URIBuilder() + .setScheme(ApiUtils.SCHEME) + .setHost(this.host) + .setPath(VEHICLES_ENDPOINT) + .addParameter("rt", routeIdsString) + .addParameter("tmres", "s") + .addParameter("key", this.apiKey) + .addParameter("format", "json") + .toString(); + + return this.getVehicles(url); + } + + private List getVehicles(String url) { + String response = HttpUtils.get(url); + + TypeReference>> typeReference = new TypeReference<>() {}; + CtaResponse> vehicleResponse; + + try { + vehicleResponse = this.objectMapper.readValue(response, typeReference); + } catch (JacksonException e) { + String message = String.format("Failed to parse response from %s", VEHICLES_ENDPOINT); + + throw new Cta4jException(message, e); + } + + CtaBustimeResponse> bustimeResponse = vehicleResponse.bustimeResponse(); + + List errors = bustimeResponse.error(); + List vehicles = bustimeResponse.data(); + + if ((errors == null) && (vehicleResponse == null)) { + log.debug("Vehicles bustime response missing both error and data from {}", VEHICLES_ENDPOINT); + + return List.of(); + } + + if ((errors != null) && !errors.isEmpty()) { + String message = ApiUtils.buildErrorMessage(VEHICLES_ENDPOINT, errors); + + throw new Cta4jException(message); + } + + if ((vehicles == null) || vehicles.isEmpty()) { + return List.of(); + } + + return vehicles.stream() + .map(this.vehicleMapper::toDomain) + .toList(); + } +} diff --git a/src/main/java/com/cta4j/bus/mapper/BusMapper.java b/src/main/java/com/cta4j/bus/api/vehicle/mapper/VehicleMapper.java similarity index 90% rename from src/main/java/com/cta4j/bus/mapper/BusMapper.java rename to src/main/java/com/cta4j/bus/api/vehicle/mapper/VehicleMapper.java index ace9308..b5897a9 100644 --- a/src/main/java/com/cta4j/bus/mapper/BusMapper.java +++ b/src/main/java/com/cta4j/bus/api/vehicle/mapper/VehicleMapper.java @@ -1,15 +1,15 @@ -package com.cta4j.bus.mapper; +package com.cta4j.bus.api.vehicle.mapper; -import com.cta4j.bus.external.CtaVehicle; +import com.cta4j.bus.api.vehicle.external.CtaVehicle; import com.cta4j.bus.mapper.util.CtaBusMappingQualifiers; -import com.cta4j.bus.model.Bus; +import com.cta4j.bus.api.vehicle.model.Vehicle; import org.jetbrains.annotations.ApiStatus; import org.mapstruct.Mapper; import org.mapstruct.Mapping; @ApiStatus.Internal @Mapper(uses = CtaBusMappingQualifiers.class) -public interface BusMapper { +public interface VehicleMapper { @Mapping(source = "vid", target = "id") @Mapping(source = "rtpidatafeed", target = "metadata.dataFeed") @Mapping(source = "tmpstmp", target = "metadata.lastUpdated", qualifiedByName = "mapTimestamp") @@ -37,5 +37,5 @@ public interface BusMapper { @Mapping(source = "psgld", target = "metadata.passengerLoad", qualifiedByName = "mapPassengerLoad") @Mapping(source = "stst", target = "metadata.scheduledStartSeconds") @Mapping(source = "stsd", target = "metadata.scheduledStartDate") - Bus toDomain(CtaVehicle vehicle); + Vehicle toDomain(CtaVehicle vehicle); } diff --git a/src/main/java/com/cta4j/bus/model/Bus.java b/src/main/java/com/cta4j/bus/api/vehicle/model/Vehicle.java similarity index 83% rename from src/main/java/com/cta4j/bus/model/Bus.java rename to src/main/java/com/cta4j/bus/api/vehicle/model/Vehicle.java index 4aa7675..f80b3b2 100644 --- a/src/main/java/com/cta4j/bus/model/Bus.java +++ b/src/main/java/com/cta4j/bus/api/vehicle/model/Vehicle.java @@ -1,23 +1,23 @@ -package com.cta4j.bus.model; +package com.cta4j.bus.api.vehicle.model; import org.jspecify.annotations.NullMarked; @NullMarked @SuppressWarnings("ConstantConditions") -public record Bus( +public record Vehicle( String id, String route, String destination, - BusCoordinates coordinates, + VehicleCoordinates coordinates, boolean delayed, - BusMetadata metadata + VehicleMetadata metadata ) { - public Bus { + public Vehicle { if (id == null) { throw new IllegalArgumentException("id must not be null"); } diff --git a/src/main/java/com/cta4j/bus/model/BusCoordinates.java b/src/main/java/com/cta4j/bus/api/vehicle/model/VehicleCoordinates.java similarity index 88% rename from src/main/java/com/cta4j/bus/model/BusCoordinates.java rename to src/main/java/com/cta4j/bus/api/vehicle/model/VehicleCoordinates.java index a0c56ca..5b503ac 100644 --- a/src/main/java/com/cta4j/bus/model/BusCoordinates.java +++ b/src/main/java/com/cta4j/bus/api/vehicle/model/VehicleCoordinates.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.model; +package com.cta4j.bus.api.vehicle.model; import org.jspecify.annotations.NullMarked; @@ -13,14 +13,14 @@ */ @NullMarked @SuppressWarnings("ConstantConditions") -public record BusCoordinates( +public record VehicleCoordinates( BigDecimal latitude, BigDecimal longitude, int heading ) { - public BusCoordinates { + public VehicleCoordinates { if (latitude == null) { throw new IllegalArgumentException("latitude must not be null"); } diff --git a/src/main/java/com/cta4j/bus/model/BusMetadata.java b/src/main/java/com/cta4j/bus/api/vehicle/model/VehicleMetadata.java similarity index 89% rename from src/main/java/com/cta4j/bus/model/BusMetadata.java rename to src/main/java/com/cta4j/bus/api/vehicle/model/VehicleMetadata.java index 6562ff4..2302807 100644 --- a/src/main/java/com/cta4j/bus/model/BusMetadata.java +++ b/src/main/java/com/cta4j/bus/api/vehicle/model/VehicleMetadata.java @@ -1,5 +1,7 @@ -package com.cta4j.bus.model; +package com.cta4j.bus.api.vehicle.model; +import com.cta4j.bus.model.PassengerLoad; +import com.cta4j.bus.model.TransitMode; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -8,7 +10,7 @@ @NullMarked @SuppressWarnings("ConstantConditions") -public record BusMetadata( +public record VehicleMetadata( @Nullable String dataFeed, @@ -61,7 +63,7 @@ public record BusMetadata( @Nullable LocalDate scheduledStartDate ) { - public BusMetadata { + public VehicleMetadata { if (blockId == null) { throw new IllegalArgumentException("blockId must not be null"); } diff --git a/src/main/java/com/cta4j/bus/client/BusClient.java b/src/main/java/com/cta4j/bus/client/BusClient.java index d3ab5ce..230f831 100644 --- a/src/main/java/com/cta4j/bus/client/BusClient.java +++ b/src/main/java/com/cta4j/bus/client/BusClient.java @@ -2,7 +2,7 @@ import com.cta4j.bus.client.internal.BusClientImpl; import com.cta4j.bus.model.Arrival; -import com.cta4j.bus.model.Bus; +import com.cta4j.bus.api.vehicle.model.Vehicle; import com.cta4j.bus.model.Detour; import com.cta4j.bus.model.Route; import com.cta4j.bus.model.RoutePattern; @@ -36,7 +36,7 @@ public interface BusClient { * @throws IllegalArgumentException if the specified IDs are {@code null} * @throws Cta4jException if an error occurs while fetching the data */ - List findBusesById(Iterable ids); + List findBusesById(Iterable ids); /** * Finds buses operating on the specified route IDs. @@ -46,7 +46,7 @@ public interface BusClient { * @throws IllegalArgumentException if the specified route IDs are {@code null} * @throws Cta4jException if an error occurs while fetching the data */ - List findBusesByRouteId(Iterable routeIds); + List findBusesByRouteId(Iterable routeIds); /** * Finds buses operating on the specified route ID. @@ -56,7 +56,7 @@ public interface BusClient { * @throws IllegalArgumentException if the specified route ID is {@code null} * @throws Cta4jException if an error occurs while fetching the data */ - default List findBusesByRouteId(String routeId) { + default List findBusesByRouteId(String routeId) { if (routeId == null) { throw new IllegalArgumentException("routeId must not be null"); } @@ -74,26 +74,26 @@ default List findBusesByRouteId(String routeId) { * @throws IllegalArgumentException if the specified bus ID is {@code null} * @throws Cta4jException if an error occurs while fetching the data */ - default Optional findBusById(String id) { + default Optional findBusById(String id) { if (id == null) { throw new IllegalArgumentException("id must not be null"); } List ids = List.of(id); - List buses = this.findBusesById(ids); + List vehicles = this.findBusesById(ids); - if (buses.isEmpty()) { + if (vehicles.isEmpty()) { return Optional.empty(); - } if (buses.size() > 1) { + } if (vehicles.size() > 1) { String message = String.format("Multiple buses found for ID: %s", id); throw new Cta4jException(message); } - Bus bus = buses.getFirst(); + Vehicle vehicle = vehicles.getFirst(); - return Optional.of(bus); + return Optional.of(vehicle); } /** diff --git a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java index e645f5f..b05e21a 100644 --- a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java +++ b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java @@ -7,14 +7,14 @@ import com.cta4j.bus.external.CtaResponse; import com.cta4j.bus.external.CtaPrediction; import com.cta4j.bus.mapper.ArrivalMapper; -import com.cta4j.bus.mapper.BusMapper; +import com.cta4j.bus.api.vehicle.mapper.VehicleMapper; import com.cta4j.bus.mapper.DetourMapper; import com.cta4j.bus.mapper.RouteMapper; import com.cta4j.bus.mapper.RoutePatternMapper; import com.cta4j.bus.mapper.StopMapper; import com.cta4j.bus.mapper.util.CtaBusMappingQualifiers; import com.cta4j.bus.model.Arrival; -import com.cta4j.bus.model.Bus; +import com.cta4j.bus.api.vehicle.model.Vehicle; import com.cta4j.bus.model.Detour; import com.cta4j.bus.model.Route; import com.cta4j.bus.model.RoutePattern; @@ -24,7 +24,7 @@ import com.cta4j.bus.external.CtaDirection; import com.cta4j.bus.external.CtaRoute; import com.cta4j.bus.external.CtaStop; -import com.cta4j.bus.external.CtaVehicle; +import com.cta4j.bus.api.vehicle.external.CtaVehicle; import com.cta4j.util.HttpUtils; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -66,7 +66,7 @@ public final class BusClientImpl implements BusClient { private final StopMapper stopMapper; private final RoutePatternMapper routePatternMapper; private final DetourMapper detourMapper; - private final BusMapper busMapper; + private final VehicleMapper vehicleMapper; private BusClientImpl(String host, String apiKey) { if (host == null) { @@ -85,7 +85,7 @@ private BusClientImpl(String host, String apiKey) { this.stopMapper = Mappers.getMapper(StopMapper.class); this.routePatternMapper = Mappers.getMapper(RoutePatternMapper.class); this.detourMapper = Mappers.getMapper(DetourMapper.class); - this.busMapper = Mappers.getMapper(BusMapper.class); + this.vehicleMapper = Mappers.getMapper(VehicleMapper.class); } @Override @@ -141,7 +141,7 @@ public Instant getSystemTime() { } @Override - public List findBusesById(Iterable ids) { + public List findBusesById(Iterable ids) { if (ids == null) { throw new IllegalArgumentException("ids must not be null"); } @@ -168,7 +168,7 @@ public List findBusesById(Iterable ids) { } @Override - public List findBusesByRouteId(Iterable routeIds) { + public List findBusesByRouteId(Iterable routeIds) { if (routeIds == null) { throw new IllegalArgumentException("routeIds must not be null"); } @@ -583,7 +583,7 @@ private String buildErrorMessage(String endpoint, List errors) { return String.format("Error response from %s: %s", endpoint, message); } - private List getBuses(String url) { + private List getBuses(String url) { String response = HttpUtils.get(url); TypeReference>> typeReference = new TypeReference<>() {}; @@ -619,7 +619,7 @@ private List getBuses(String url) { } return vehicles.stream() - .map(this.busMapper::toDomain) + .map(this.vehicleMapper::toDomain) .toList(); } From b9c4ad696d6c45a4d13d952b6693ebb393fad502 Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Fri, 16 Jan 2026 15:29:53 -0600 Subject: [PATCH 33/53] API refactor --- src/main/java/com/cta4j/bus/api/BusApi.java | 42 ++++++ .../com/cta4j/bus/api/impl/BusApiImpl.java | 125 ++++++++++++++++++ .../mapper/util/CtaBusMappingQualifiers.java | 2 +- 3 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java diff --git a/src/main/java/com/cta4j/bus/api/BusApi.java b/src/main/java/com/cta4j/bus/api/BusApi.java index 61bcdc4..9aed4f5 100644 --- a/src/main/java/com/cta4j/bus/api/BusApi.java +++ b/src/main/java/com/cta4j/bus/api/BusApi.java @@ -1,5 +1,6 @@ package com.cta4j.bus.api; +import com.cta4j.bus.api.impl.BusApiImpl; import com.cta4j.bus.api.vehicle.VehiclesApi; import java.time.Instant; @@ -22,4 +23,45 @@ public interface BusApi { LocalesApi locales(); DetoursApi detours(); + + /** + * A builder for configuring and creating {@link BusApi} instances. + * + *

Fluent, non-thread-safe builder. Call {@link #build()} to obtain a configured client. + */ + interface Builder { + /** + * Sets the API host used by the client. + * + * @param host the host + * @return this {@link Builder} for method chaining + * @throws IllegalArgumentException if {@code host} is {@code null} + */ + Builder host(String host); + + /** + * Sets the API key used for authentication. + * + * @param apiKey the API key + * @return this {@link Builder} for method chaining + * @throws IllegalArgumentException if {@code apiKey} is {@code null} + */ + Builder apiKey(String apiKey); + + /** + * Builds a configured {@link BusApi} instance. + * + * @return a new {@link BusApi} + */ + BusApi build(); + } + + /** + * Creates a new {@link Builder} for configuring and building a {@link BusApi}. + * + * @return a new {@link Builder} instance + */ + static Builder builder() { + return new BusApiImpl.BuilderImpl(); + } } diff --git a/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java b/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java new file mode 100644 index 0000000..09ec806 --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java @@ -0,0 +1,125 @@ +package com.cta4j.bus.api.impl; + +import com.cta4j.bus.api.ApiUtils; +import com.cta4j.bus.api.BusApi; +import com.cta4j.bus.api.DetoursApi; +import com.cta4j.bus.api.DirectionsApi; +import com.cta4j.bus.api.LocalesApi; +import com.cta4j.bus.api.PatternsApi; +import com.cta4j.bus.api.PredictionsApi; +import com.cta4j.bus.api.RoutesApi; +import com.cta4j.bus.api.StopsApi; +import com.cta4j.bus.api.vehicle.VehiclesApi; +import com.cta4j.bus.api.vehicle.impl.VehiclesApiImpl; +import org.jspecify.annotations.NonNull; +import tools.jackson.databind.ObjectMapper; + +import java.time.Instant; + +public final class BusApiImpl implements BusApi { + private final String host; + private final String apiKey; + private final ObjectMapper objectMapper; + + public BusApiImpl(String host, String apiKey) { + if (host == null) { + throw new IllegalArgumentException("host must not be null"); + } + + if (apiKey == null) { + throw new IllegalArgumentException("apiKey must not be null"); + } + + this.host = host; + this.apiKey = apiKey; + this.objectMapper = new ObjectMapper(); + } + + @Override + public Instant systemTime() { + return null; + } + + @Override + public VehiclesApi vehicles() { + return new VehiclesApiImpl(this.host, this.apiKey, this.objectMapper); + } + + @Override + public RoutesApi routes() { + return null; + } + + @Override + public DirectionsApi directions() { + return null; + } + + @Override + public StopsApi stops() { + return null; + } + + @Override + public PatternsApi patterns() { + return null; + } + + @Override + public PredictionsApi predictions() { + return null; + } + + @Override + public LocalesApi locales() { + return null; + } + + @Override + public DetoursApi detours() { + return null; + } + + public static final class BuilderImpl implements BusApi.Builder { + private String host; + private String apiKey; + + public BuilderImpl() { + this.host = null; + this.apiKey = null; + } + + @Override + public @NonNull Builder host(String host) { + if (host == null) { + throw new IllegalArgumentException("host must not be null"); + } + + this.host = host; + + return this; + } + + @Override + public @NonNull Builder apiKey(String apiKey) { + if (apiKey == null) { + throw new IllegalArgumentException("apiKey must not be null"); + } + + this.apiKey = apiKey; + + return this; + } + + @Override + public BusApi build() { + String finalHost = (this.host == null) ? ApiUtils.DEFAULT_HOST : this.host; + + if (this.apiKey == null) { + throw new IllegalStateException("API key must not be null"); + } + + return new BusApiImpl(finalHost, this.apiKey); + } + } +} diff --git a/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java b/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java index fd91000..e4f7c78 100644 --- a/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java +++ b/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java @@ -43,7 +43,7 @@ public static PredictionType mapPredictionType(String typ) { @Named("mapTimestamp") public static Instant mapTimestamp(String timestamp) { if (timestamp == null) { - throw new IllegalArgumentException("timestamp must not be null"); + return null; } return LocalDateTime.parse(timestamp, TIMESTAMP_FORMATTER) From a91ff6fcc536c4fbea25530199c143e7239ee10e Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sat, 17 Jan 2026 15:18:25 -0600 Subject: [PATCH 34/53] API refactor --- src/main/java/com/cta4j/bus/api/BusApi.java | 3 + .../java/com/cta4j/bus/api/RoutesApi.java | 4 - .../com/cta4j/bus/api/impl/BusApiImpl.java | 83 +++++++++++-- .../com/cta4j/bus/api/route/RoutesApi.java | 11 ++ .../{ => api/route}/external/CtaRoute.java | 17 ++- .../bus/api/route/impl/RoutesApiImpl.java | 109 ++++++++++++++++++ .../{ => api/route}/mapper/RouteMapper.java | 8 +- .../bus/{ => api/route}/model/Route.java | 17 ++- .../cta4j/bus/api/vehicle/VehiclesApi.java | 12 +- .../bus/api/vehicle/external/CtaVehicle.java | 59 +++++++++- .../bus/api/vehicle/impl/VehiclesApiImpl.java | 24 ++-- .../bus/api/vehicle/mapper/VehicleMapper.java | 2 +- .../cta4j/bus/api/vehicle/model/Vehicle.java | 18 ++- .../api/vehicle/model/VehicleCoordinates.java | 14 ++- .../api/vehicle/model/VehicleMetadata.java | 45 +++++++- .../java/com/cta4j/bus/client/BusClient.java | 2 +- .../bus/client/internal/BusClientImpl.java | 6 +- .../bus/external/CtaBustimeResponse.java | 2 + .../com/cta4j/bus/external/CtaResponse.java | 6 +- 19 files changed, 384 insertions(+), 58 deletions(-) delete mode 100644 src/main/java/com/cta4j/bus/api/RoutesApi.java create mode 100644 src/main/java/com/cta4j/bus/api/route/RoutesApi.java rename src/main/java/com/cta4j/bus/{ => api/route}/external/CtaRoute.java (69%) create mode 100644 src/main/java/com/cta4j/bus/api/route/impl/RoutesApiImpl.java rename src/main/java/com/cta4j/bus/{ => api/route}/mapper/RouteMapper.java (77%) rename src/main/java/com/cta4j/bus/{ => api/route}/model/Route.java (65%) diff --git a/src/main/java/com/cta4j/bus/api/BusApi.java b/src/main/java/com/cta4j/bus/api/BusApi.java index 9aed4f5..c269377 100644 --- a/src/main/java/com/cta4j/bus/api/BusApi.java +++ b/src/main/java/com/cta4j/bus/api/BusApi.java @@ -1,10 +1,13 @@ package com.cta4j.bus.api; import com.cta4j.bus.api.impl.BusApiImpl; +import com.cta4j.bus.api.route.RoutesApi; import com.cta4j.bus.api.vehicle.VehiclesApi; +import org.jspecify.annotations.NullMarked; import java.time.Instant; +@NullMarked public interface BusApi { Instant systemTime(); diff --git a/src/main/java/com/cta4j/bus/api/RoutesApi.java b/src/main/java/com/cta4j/bus/api/RoutesApi.java deleted file mode 100644 index 6565861..0000000 --- a/src/main/java/com/cta4j/bus/api/RoutesApi.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.cta4j.bus.api; - -public interface RoutesApi { -} diff --git a/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java b/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java index 09ec806..4735d0f 100644 --- a/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java @@ -7,21 +7,39 @@ import com.cta4j.bus.api.LocalesApi; import com.cta4j.bus.api.PatternsApi; import com.cta4j.bus.api.PredictionsApi; -import com.cta4j.bus.api.RoutesApi; +import com.cta4j.bus.api.route.RoutesApi; import com.cta4j.bus.api.StopsApi; +import com.cta4j.bus.api.route.impl.RoutesApiImpl; import com.cta4j.bus.api.vehicle.VehiclesApi; import com.cta4j.bus.api.vehicle.impl.VehiclesApiImpl; -import org.jspecify.annotations.NonNull; +import com.cta4j.bus.external.CtaBustimeResponse; +import com.cta4j.bus.external.CtaError; +import com.cta4j.bus.external.CtaResponse; +import com.cta4j.bus.mapper.util.CtaBusMappingQualifiers; +import com.cta4j.exception.Cta4jException; +import com.cta4j.util.HttpUtils; +import org.apache.hc.core5.net.URIBuilder; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +import tools.jackson.core.JacksonException; +import tools.jackson.core.type.TypeReference; import tools.jackson.databind.ObjectMapper; import java.time.Instant; +import java.util.List; +@NullMarked public final class BusApiImpl implements BusApi { + private static final String SYSTEM_TIME_ENDPOINT = String.format("%s/gettime", ApiUtils.API_PREFIX); + private final String host; private final String apiKey; private final ObjectMapper objectMapper; - public BusApiImpl(String host, String apiKey) { + public BusApiImpl( + @Nullable String host, + @Nullable String apiKey + ) { if (host == null) { throw new IllegalArgumentException("host must not be null"); } @@ -37,7 +55,54 @@ public BusApiImpl(String host, String apiKey) { @Override public Instant systemTime() { - return null; + String url = new URIBuilder() + .setScheme(ApiUtils.SCHEME) + .setHost(this.host) + .setPath(SYSTEM_TIME_ENDPOINT) + .addParameter("key", this.apiKey) + .addParameter("format", "json") + .toString(); + + String response = HttpUtils.get(url); + + TypeReference> typeReference = new TypeReference<>() {}; + CtaResponse timeResponse; + + try { + timeResponse = this.objectMapper.readValue(response, typeReference); + } catch (JacksonException e) { + String message = String.format("Failed to parse response from %s", SYSTEM_TIME_ENDPOINT); + + throw new Cta4jException(message, e); + } + + CtaBustimeResponse bustimeResponse = timeResponse.bustimeResponse(); + + List errors = bustimeResponse.error(); + String systemTime = bustimeResponse.data(); + + if ((errors == null) && (systemTime == null)) { + String message = String.format( + "System time bustime response missing both error and data from %s", + SYSTEM_TIME_ENDPOINT + ); + + throw new Cta4jException(message); + } + + if ((errors != null) && !errors.isEmpty()) { + String message = ApiUtils.buildErrorMessage(SYSTEM_TIME_ENDPOINT, errors); + + throw new Cta4jException(message); + } + + if (systemTime == null) { + String message = String.format("No system time data returned from %s", SYSTEM_TIME_ENDPOINT); + + throw new Cta4jException(message); + } + + return CtaBusMappingQualifiers.mapTimestamp(systemTime); } @Override @@ -47,7 +112,7 @@ public VehiclesApi vehicles() { @Override public RoutesApi routes() { - return null; + return new RoutesApiImpl(this.host, this.apiKey, this.objectMapper); } @Override @@ -81,8 +146,8 @@ public DetoursApi detours() { } public static final class BuilderImpl implements BusApi.Builder { - private String host; - private String apiKey; + private @Nullable String host; + private @Nullable String apiKey; public BuilderImpl() { this.host = null; @@ -90,7 +155,7 @@ public BuilderImpl() { } @Override - public @NonNull Builder host(String host) { + public Builder host(@Nullable String host) { if (host == null) { throw new IllegalArgumentException("host must not be null"); } @@ -101,7 +166,7 @@ public BuilderImpl() { } @Override - public @NonNull Builder apiKey(String apiKey) { + public Builder apiKey(@Nullable String apiKey) { if (apiKey == null) { throw new IllegalArgumentException("apiKey must not be null"); } diff --git a/src/main/java/com/cta4j/bus/api/route/RoutesApi.java b/src/main/java/com/cta4j/bus/api/route/RoutesApi.java new file mode 100644 index 0000000..51c694d --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/route/RoutesApi.java @@ -0,0 +1,11 @@ +package com.cta4j.bus.api.route; + +import com.cta4j.bus.api.route.model.Route; +import org.jspecify.annotations.NullMarked; + +import java.util.List; + +@NullMarked +public interface RoutesApi { + List getRoutes(); +} diff --git a/src/main/java/com/cta4j/bus/external/CtaRoute.java b/src/main/java/com/cta4j/bus/api/route/external/CtaRoute.java similarity index 69% rename from src/main/java/com/cta4j/bus/external/CtaRoute.java rename to src/main/java/com/cta4j/bus/api/route/external/CtaRoute.java index b80a77b..b8089a1 100644 --- a/src/main/java/com/cta4j/bus/external/CtaRoute.java +++ b/src/main/java/com/cta4j/bus/api/route/external/CtaRoute.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.external; +package com.cta4j.bus.api.route.external; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.jetbrains.annotations.ApiStatus; @@ -7,7 +7,6 @@ @NullMarked @ApiStatus.Internal -@SuppressWarnings("ConstantConditions") @JsonIgnoreProperties(ignoreUnknown = true) public record CtaRoute( String rt, @@ -21,7 +20,13 @@ public record CtaRoute( @Nullable String rtpidatafeed ) { - public CtaRoute { + public CtaRoute( + @Nullable String rt, + @Nullable String rtnm, + @Nullable String rtclr, + @Nullable String rtdd, + @Nullable String rtpidatafeed + ) { if (rt == null) { throw new IllegalArgumentException("rt must not be null"); } @@ -37,5 +42,11 @@ public record CtaRoute( if (rtdd == null) { throw new IllegalArgumentException("rtdd must not be null"); } + + this.rt = rt; + this.rtnm = rtnm; + this.rtclr = rtclr; + this.rtdd = rtdd; + this.rtpidatafeed = rtpidatafeed; } } diff --git a/src/main/java/com/cta4j/bus/api/route/impl/RoutesApiImpl.java b/src/main/java/com/cta4j/bus/api/route/impl/RoutesApiImpl.java new file mode 100644 index 0000000..7026d41 --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/route/impl/RoutesApiImpl.java @@ -0,0 +1,109 @@ +package com.cta4j.bus.api.route.impl; + +import com.cta4j.bus.api.ApiUtils; +import com.cta4j.bus.api.route.RoutesApi; +import com.cta4j.bus.api.route.external.CtaRoute; +import com.cta4j.bus.api.route.mapper.RouteMapper; +import com.cta4j.bus.api.route.model.Route; +import com.cta4j.bus.external.CtaBustimeResponse; +import com.cta4j.bus.external.CtaError; +import com.cta4j.bus.external.CtaResponse; +import com.cta4j.exception.Cta4jException; +import com.cta4j.util.HttpUtils; +import org.apache.hc.core5.net.URIBuilder; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +import org.mapstruct.factory.Mappers; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import tools.jackson.core.JacksonException; +import tools.jackson.core.type.TypeReference; +import tools.jackson.databind.ObjectMapper; + +import java.util.List; + +@NullMarked +@ApiStatus.Internal +public final class RoutesApiImpl implements RoutesApi { + private static final Logger log = LoggerFactory.getLogger(RoutesApiImpl.class); + + private static final String ROUTES_ENDPOINT = String.format("%s/getroutes", ApiUtils.API_PREFIX); + + private final String host; + private final String apiKey; + private final ObjectMapper objectMapper; + private final RouteMapper routeMapper; + + public RoutesApiImpl( + @Nullable String host, + @Nullable String apiKey, + @Nullable ObjectMapper objectMapper + ) { + if (host == null) { + throw new IllegalArgumentException("host must not be null"); + } + + if (apiKey == null) { + throw new IllegalArgumentException("apiKey must not be null"); + } + + if (objectMapper == null) { + throw new IllegalArgumentException("objectMapper must not be null"); + } + + this.host = host; + this.apiKey = apiKey; + this.objectMapper = objectMapper; + this.routeMapper = Mappers.getMapper(RouteMapper.class); + } + + @Override + public List getRoutes() { + String url = new URIBuilder() + .setScheme(ApiUtils.SCHEME) + .setHost(this.host) + .setPath(ROUTES_ENDPOINT) + .addParameter("key", this.apiKey) + .addParameter("format", "json") + .toString(); + + String response = HttpUtils.get(url); + + TypeReference>> typeReference = new TypeReference<>() {}; + CtaResponse> routesResponse; + + try { + routesResponse = this.objectMapper.readValue(response, typeReference); + } catch (JacksonException e) { + String message = String.format("Failed to parse response from %s", ROUTES_ENDPOINT); + + throw new Cta4jException(message, e); + } + + CtaBustimeResponse> bustimeResponse = routesResponse.bustimeResponse(); + + List errors = bustimeResponse.error(); + List routes = bustimeResponse.data(); + + if ((errors == null) && (routes == null)) { + log.debug("Routes bustime response missing both error and data from {}", ROUTES_ENDPOINT); + + return List.of(); + } + + if ((errors != null) && !errors.isEmpty()) { + String message = ApiUtils.buildErrorMessage(ROUTES_ENDPOINT, errors); + + throw new Cta4jException(message); + } + + if ((routes == null) || routes.isEmpty()) { + return List.of(); + } + + return routes.stream() + .map(this.routeMapper::toDomain) + .toList(); + } +} diff --git a/src/main/java/com/cta4j/bus/mapper/RouteMapper.java b/src/main/java/com/cta4j/bus/api/route/mapper/RouteMapper.java similarity index 77% rename from src/main/java/com/cta4j/bus/mapper/RouteMapper.java rename to src/main/java/com/cta4j/bus/api/route/mapper/RouteMapper.java index 65b0206..2b9f2d9 100644 --- a/src/main/java/com/cta4j/bus/mapper/RouteMapper.java +++ b/src/main/java/com/cta4j/bus/api/route/mapper/RouteMapper.java @@ -1,13 +1,13 @@ -package com.cta4j.bus.mapper; +package com.cta4j.bus.api.route.mapper; -import com.cta4j.bus.external.CtaRoute; -import com.cta4j.bus.model.Route; +import com.cta4j.bus.api.route.external.CtaRoute; +import com.cta4j.bus.api.route.model.Route; import org.jetbrains.annotations.ApiStatus; import org.mapstruct.Mapper; import org.mapstruct.Mapping; -@ApiStatus.Internal @Mapper +@ApiStatus.Internal public interface RouteMapper { @Mapping(source = "rt", target = "id") @Mapping(source = "rtnm", target = "name") diff --git a/src/main/java/com/cta4j/bus/model/Route.java b/src/main/java/com/cta4j/bus/api/route/model/Route.java similarity index 65% rename from src/main/java/com/cta4j/bus/model/Route.java rename to src/main/java/com/cta4j/bus/api/route/model/Route.java index b7cb187..a3ca068 100644 --- a/src/main/java/com/cta4j/bus/model/Route.java +++ b/src/main/java/com/cta4j/bus/api/route/model/Route.java @@ -1,10 +1,9 @@ -package com.cta4j.bus.model; +package com.cta4j.bus.api.route.model; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @NullMarked -@SuppressWarnings("ConstantConditions") public record Route( String id, @@ -17,7 +16,13 @@ public record Route( @Nullable String dataFeed ) { - public Route { + public Route( + @Nullable String id, + @Nullable String name, + @Nullable String color, + @Nullable String designator, + @Nullable String dataFeed + ) { if (id == null) { throw new IllegalArgumentException("id must not be null"); } @@ -33,5 +38,11 @@ public record Route( if (color == null) { throw new IllegalArgumentException("color must not be null"); } + + this.id = id; + this.name = name; + this.color = color; + this.designator = designator; + this.dataFeed = dataFeed; } } diff --git a/src/main/java/com/cta4j/bus/api/vehicle/VehiclesApi.java b/src/main/java/com/cta4j/bus/api/vehicle/VehiclesApi.java index 923d9fb..5bb8a6c 100644 --- a/src/main/java/com/cta4j/bus/api/vehicle/VehiclesApi.java +++ b/src/main/java/com/cta4j/bus/api/vehicle/VehiclesApi.java @@ -2,16 +2,18 @@ import com.cta4j.bus.api.vehicle.model.Vehicle; import com.cta4j.exception.Cta4jException; -import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.Collection; import java.util.List; import java.util.Optional; +@NullMarked public interface VehiclesApi { - @NonNull List findByIds(Collection ids); + List findByIds(@Nullable Collection<@Nullable String> ids); - @NonNull default Optional findById(String id) { + default Optional findById(@Nullable String id) { if (id == null) { throw new IllegalArgumentException("id must not be null"); } @@ -33,9 +35,9 @@ public interface VehiclesApi { return Optional.of(vehicle); } - @NonNull List findByRouteIds(Collection routeIds); + List findByRouteIds(@Nullable Collection<@Nullable String> routeIds); - @NonNull default List findByRouteId(String routeId) { + default List findByRouteId(@Nullable String routeId) { if (routeId == null) { throw new IllegalArgumentException("routeId must not be null"); } diff --git a/src/main/java/com/cta4j/bus/api/vehicle/external/CtaVehicle.java b/src/main/java/com/cta4j/bus/api/vehicle/external/CtaVehicle.java index 25a16d1..06b67b2 100644 --- a/src/main/java/com/cta4j/bus/api/vehicle/external/CtaVehicle.java +++ b/src/main/java/com/cta4j/bus/api/vehicle/external/CtaVehicle.java @@ -7,7 +7,6 @@ @NullMarked @ApiStatus.Internal -@SuppressWarnings("ConstantConditions") @JsonIgnoreProperties(ignoreUnknown = true) public record CtaVehicle( String vid, @@ -76,7 +75,35 @@ public record CtaVehicle( @Nullable String stsd ) { - public CtaVehicle { + public CtaVehicle( + @Nullable String vid, + @Nullable String rtpidatafeed, + @Nullable String tmpstmp, + double lat, + double lon, + int hdg, + int pid, + @Nullable String rt, + @Nullable String des, + int pdist, + @Nullable Integer stopstatus, + @Nullable Integer timepointid, + @Nullable String stopid, + @Nullable Integer sequence, + @Nullable Integer gtfsseq, + boolean dly, + @Nullable String srvtmstmp, + @Nullable Integer spd, + @Nullable Integer blk, + @Nullable String tablockid, + @Nullable String tatripid, + @Nullable String origtatripno, + @Nullable String zone, + int mode, + @Nullable String psgld, + @Nullable Integer stst, + @Nullable String stsd + ) { if (vid == null) { throw new IllegalArgumentException("vid must not be null"); } @@ -108,5 +135,33 @@ public record CtaVehicle( if (psgld == null) { throw new IllegalArgumentException("psgld must not be null"); } + + this.vid = vid; + this.rtpidatafeed = rtpidatafeed; + this.tmpstmp = tmpstmp; + this.lat = lat; + this.lon = lon; + this.hdg = hdg; + this.pid = pid; + this.rt = rt; + this.des = des; + this.pdist = pdist; + this.stopstatus = stopstatus; + this.timepointid = timepointid; + this.stopid = stopid; + this.sequence = sequence; + this.gtfsseq = gtfsseq; + this.dly = dly; + this.srvtmstmp = srvtmstmp; + this.spd = spd; + this.blk = blk; + this.tablockid = tablockid; + this.tatripid = tatripid; + this.origtatripno = origtatripno; + this.zone = zone; + this.mode = mode; + this.psgld = psgld; + this.stst = stst; + this.stsd = stsd; } } diff --git a/src/main/java/com/cta4j/bus/api/vehicle/impl/VehiclesApiImpl.java b/src/main/java/com/cta4j/bus/api/vehicle/impl/VehiclesApiImpl.java index b0bd5ad..82f99b1 100644 --- a/src/main/java/com/cta4j/bus/api/vehicle/impl/VehiclesApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/vehicle/impl/VehiclesApiImpl.java @@ -12,10 +12,9 @@ import com.cta4j.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; import org.jetbrains.annotations.ApiStatus; -import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.mapstruct.factory.Mappers; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import tools.jackson.core.JacksonException; import tools.jackson.core.type.TypeReference; import tools.jackson.databind.ObjectMapper; @@ -23,10 +22,9 @@ import java.util.Collection; import java.util.List; +@NullMarked @ApiStatus.Internal public final class VehiclesApiImpl implements VehiclesApi { - private static final Logger log = LoggerFactory.getLogger(VehiclesApiImpl.class); - private static final String VEHICLES_ENDPOINT = String.format("%s/getvehicles", ApiUtils.API_PREFIX); private final String host; @@ -34,7 +32,11 @@ public final class VehiclesApiImpl implements VehiclesApi { private final ObjectMapper objectMapper; private final VehicleMapper vehicleMapper; - public VehiclesApiImpl(String host, String apiKey, ObjectMapper objectMapper) { + public VehiclesApiImpl( + @Nullable String host, + @Nullable String apiKey, + @Nullable ObjectMapper objectMapper + ) { if (host == null) { throw new IllegalArgumentException("host must not be null"); } @@ -54,7 +56,7 @@ public VehiclesApiImpl(String host, String apiKey, ObjectMapper objectMapper) { } @Override - public @NonNull List findByIds(Collection ids) { + public List findByIds(@Nullable Collection<@Nullable String> ids) { if (ids == null) { throw new IllegalArgumentException("ids must not be null"); } @@ -85,7 +87,7 @@ public VehiclesApiImpl(String host, String apiKey, ObjectMapper objectMapper) { } @Override - public @NonNull List findByRouteIds(Collection routeIds) { + public List findByRouteIds(@Nullable Collection<@Nullable String> routeIds) { if (routeIds == null) { throw new IllegalArgumentException("routeIds must not be null"); } @@ -134,12 +136,6 @@ private List getVehicles(String url) { List errors = bustimeResponse.error(); List vehicles = bustimeResponse.data(); - if ((errors == null) && (vehicleResponse == null)) { - log.debug("Vehicles bustime response missing both error and data from {}", VEHICLES_ENDPOINT); - - return List.of(); - } - if ((errors != null) && !errors.isEmpty()) { String message = ApiUtils.buildErrorMessage(VEHICLES_ENDPOINT, errors); diff --git a/src/main/java/com/cta4j/bus/api/vehicle/mapper/VehicleMapper.java b/src/main/java/com/cta4j/bus/api/vehicle/mapper/VehicleMapper.java index b5897a9..dcc030c 100644 --- a/src/main/java/com/cta4j/bus/api/vehicle/mapper/VehicleMapper.java +++ b/src/main/java/com/cta4j/bus/api/vehicle/mapper/VehicleMapper.java @@ -7,8 +7,8 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; -@ApiStatus.Internal @Mapper(uses = CtaBusMappingQualifiers.class) +@ApiStatus.Internal public interface VehicleMapper { @Mapping(source = "vid", target = "id") @Mapping(source = "rtpidatafeed", target = "metadata.dataFeed") diff --git a/src/main/java/com/cta4j/bus/api/vehicle/model/Vehicle.java b/src/main/java/com/cta4j/bus/api/vehicle/model/Vehicle.java index f80b3b2..c200202 100644 --- a/src/main/java/com/cta4j/bus/api/vehicle/model/Vehicle.java +++ b/src/main/java/com/cta4j/bus/api/vehicle/model/Vehicle.java @@ -1,9 +1,9 @@ package com.cta4j.bus.api.vehicle.model; import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; @NullMarked -@SuppressWarnings("ConstantConditions") public record Vehicle( String id, @@ -17,7 +17,14 @@ public record Vehicle( VehicleMetadata metadata ) { - public Vehicle { + public Vehicle( + @Nullable String id, + @Nullable String route, + @Nullable String destination, + @Nullable VehicleCoordinates coordinates, + boolean delayed, + @Nullable VehicleMetadata metadata + ) { if (id == null) { throw new IllegalArgumentException("id must not be null"); } @@ -37,5 +44,12 @@ public record Vehicle( if (metadata == null) { throw new IllegalArgumentException("metadata must not be null"); } + + this.id = id; + this.route = route; + this.destination = destination; + this.coordinates = coordinates; + this.delayed = delayed; + this.metadata = metadata; } } diff --git a/src/main/java/com/cta4j/bus/api/vehicle/model/VehicleCoordinates.java b/src/main/java/com/cta4j/bus/api/vehicle/model/VehicleCoordinates.java index 5b503ac..4bd18e2 100644 --- a/src/main/java/com/cta4j/bus/api/vehicle/model/VehicleCoordinates.java +++ b/src/main/java/com/cta4j/bus/api/vehicle/model/VehicleCoordinates.java @@ -1,6 +1,7 @@ package com.cta4j.bus.api.vehicle.model; import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.math.BigDecimal; @@ -12,7 +13,6 @@ * @param heading the heading of the bus in degrees (0-359) */ @NullMarked -@SuppressWarnings("ConstantConditions") public record VehicleCoordinates( BigDecimal latitude, @@ -20,7 +20,11 @@ public record VehicleCoordinates( int heading ) { - public VehicleCoordinates { + public VehicleCoordinates( + @Nullable BigDecimal latitude, + @Nullable BigDecimal longitude, + int heading + ) { if (latitude == null) { throw new IllegalArgumentException("latitude must not be null"); } @@ -30,7 +34,11 @@ public record VehicleCoordinates( } if ((heading < 0) || (heading > 359)) { - throw new IllegalArgumentException("heading must be between 0 and 359"); + throw new IllegalArgumentException("heading must be between 0 and 359 (inclusive)"); } + + this.latitude = latitude; + this.longitude = longitude; + this.heading = heading; } } diff --git a/src/main/java/com/cta4j/bus/api/vehicle/model/VehicleMetadata.java b/src/main/java/com/cta4j/bus/api/vehicle/model/VehicleMetadata.java index 2302807..fef9a88 100644 --- a/src/main/java/com/cta4j/bus/api/vehicle/model/VehicleMetadata.java +++ b/src/main/java/com/cta4j/bus/api/vehicle/model/VehicleMetadata.java @@ -9,7 +9,6 @@ import java.time.LocalDate; @NullMarked -@SuppressWarnings("ConstantConditions") public record VehicleMetadata( @Nullable String dataFeed, @@ -63,7 +62,28 @@ public record VehicleMetadata( @Nullable LocalDate scheduledStartDate ) { - public VehicleMetadata { + public VehicleMetadata( + @Nullable String dataFeed, + @Nullable Instant lastUpdated, + int patternId, + int distanceToPatternPoint, + @Nullable Integer stopStatus, + @Nullable Integer timepointId, + @Nullable String stopId, + @Nullable Integer sequence, + @Nullable Integer gtfsSequence, + @Nullable Instant serverTimestamp, + @Nullable Integer speed, + @Nullable Integer block, + @Nullable String blockId, + @Nullable String tripId, + @Nullable String originalTripNumber, + @Nullable String zone, + @Nullable TransitMode mode, + @Nullable PassengerLoad passengerLoad, + @Nullable Integer scheduledStartSeconds, + @Nullable LocalDate scheduledStartDate + ) { if (blockId == null) { throw new IllegalArgumentException("blockId must not be null"); } @@ -87,5 +107,26 @@ public record VehicleMetadata( if (passengerLoad == null) { throw new IllegalArgumentException("passengerLoad must not be null"); } + + this.dataFeed = dataFeed; + this.lastUpdated = lastUpdated; + this.patternId = patternId; + this.distanceToPatternPoint = distanceToPatternPoint; + this.stopStatus = stopStatus; + this.timepointId = timepointId; + this.stopId = stopId; + this.sequence = sequence; + this.gtfsSequence = gtfsSequence; + this.serverTimestamp = serverTimestamp; + this.speed = speed; + this.block = block; + this.blockId = blockId; + this.tripId = tripId; + this.originalTripNumber = originalTripNumber; + this.zone = zone; + this.mode = mode; + this.passengerLoad = passengerLoad; + this.scheduledStartSeconds = scheduledStartSeconds; + this.scheduledStartDate = scheduledStartDate; } } diff --git a/src/main/java/com/cta4j/bus/client/BusClient.java b/src/main/java/com/cta4j/bus/client/BusClient.java index 230f831..ee7f105 100644 --- a/src/main/java/com/cta4j/bus/client/BusClient.java +++ b/src/main/java/com/cta4j/bus/client/BusClient.java @@ -4,7 +4,7 @@ import com.cta4j.bus.model.Arrival; import com.cta4j.bus.api.vehicle.model.Vehicle; import com.cta4j.bus.model.Detour; -import com.cta4j.bus.model.Route; +import com.cta4j.bus.api.route.model.Route; import com.cta4j.bus.model.RoutePattern; import com.cta4j.bus.model.Stop; import com.cta4j.exception.Cta4jException; diff --git a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java index b05e21a..284dfa7 100644 --- a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java +++ b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java @@ -9,20 +9,20 @@ import com.cta4j.bus.mapper.ArrivalMapper; import com.cta4j.bus.api.vehicle.mapper.VehicleMapper; import com.cta4j.bus.mapper.DetourMapper; -import com.cta4j.bus.mapper.RouteMapper; +import com.cta4j.bus.api.route.mapper.RouteMapper; import com.cta4j.bus.mapper.RoutePatternMapper; import com.cta4j.bus.mapper.StopMapper; import com.cta4j.bus.mapper.util.CtaBusMappingQualifiers; import com.cta4j.bus.model.Arrival; import com.cta4j.bus.api.vehicle.model.Vehicle; import com.cta4j.bus.model.Detour; -import com.cta4j.bus.model.Route; +import com.cta4j.bus.api.route.model.Route; import com.cta4j.bus.model.RoutePattern; import com.cta4j.bus.model.Stop; import com.cta4j.exception.Cta4jException; import com.cta4j.bus.external.CtaDetour; import com.cta4j.bus.external.CtaDirection; -import com.cta4j.bus.external.CtaRoute; +import com.cta4j.bus.api.route.external.CtaRoute; import com.cta4j.bus.external.CtaStop; import com.cta4j.bus.api.vehicle.external.CtaVehicle; import com.cta4j.util.HttpUtils; diff --git a/src/main/java/com/cta4j/bus/external/CtaBustimeResponse.java b/src/main/java/com/cta4j/bus/external/CtaBustimeResponse.java index 141b625..97dc2d4 100644 --- a/src/main/java/com/cta4j/bus/external/CtaBustimeResponse.java +++ b/src/main/java/com/cta4j/bus/external/CtaBustimeResponse.java @@ -2,10 +2,12 @@ import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import java.util.List; +@NullMarked @JsonIgnoreProperties(ignoreUnknown = true) public record CtaBustimeResponse( @Nullable diff --git a/src/main/java/com/cta4j/bus/external/CtaResponse.java b/src/main/java/com/cta4j/bus/external/CtaResponse.java index 7ccb649..12a805c 100644 --- a/src/main/java/com/cta4j/bus/external/CtaResponse.java +++ b/src/main/java/com/cta4j/bus/external/CtaResponse.java @@ -3,17 +3,19 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; @NullMarked -@SuppressWarnings("ConstantConditions") @JsonIgnoreProperties(ignoreUnknown = true) public record CtaResponse( @JsonProperty("bustime-response") CtaBustimeResponse bustimeResponse ) { - public CtaResponse { + public CtaResponse(@Nullable CtaBustimeResponse bustimeResponse) { if (bustimeResponse == null) { throw new IllegalArgumentException("bustimeResponse must not be null"); } + + this.bustimeResponse = bustimeResponse; } } From 799520c1acb2c2d0b43268c5ef95e9893aa5b951 Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sat, 17 Jan 2026 15:29:54 -0600 Subject: [PATCH 35/53] API refactor --- src/main/java/com/cta4j/bus/api/BusApi.java | 1 + .../java/com/cta4j/bus/api/DirectionsApi.java | 4 - .../bus/api/direction/DirectionsApi.java | 11 +++ .../api/direction/impl/DirectionsApiImpl.java | 97 +++++++++++++++++++ .../com/cta4j/bus/api/impl/BusApiImpl.java | 15 ++- 5 files changed, 120 insertions(+), 8 deletions(-) delete mode 100644 src/main/java/com/cta4j/bus/api/DirectionsApi.java create mode 100644 src/main/java/com/cta4j/bus/api/direction/DirectionsApi.java create mode 100644 src/main/java/com/cta4j/bus/api/direction/impl/DirectionsApiImpl.java diff --git a/src/main/java/com/cta4j/bus/api/BusApi.java b/src/main/java/com/cta4j/bus/api/BusApi.java index c269377..8ddaac4 100644 --- a/src/main/java/com/cta4j/bus/api/BusApi.java +++ b/src/main/java/com/cta4j/bus/api/BusApi.java @@ -1,5 +1,6 @@ package com.cta4j.bus.api; +import com.cta4j.bus.api.direction.DirectionsApi; import com.cta4j.bus.api.impl.BusApiImpl; import com.cta4j.bus.api.route.RoutesApi; import com.cta4j.bus.api.vehicle.VehiclesApi; diff --git a/src/main/java/com/cta4j/bus/api/DirectionsApi.java b/src/main/java/com/cta4j/bus/api/DirectionsApi.java deleted file mode 100644 index 5cfd66e..0000000 --- a/src/main/java/com/cta4j/bus/api/DirectionsApi.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.cta4j.bus.api; - -public interface DirectionsApi { -} diff --git a/src/main/java/com/cta4j/bus/api/direction/DirectionsApi.java b/src/main/java/com/cta4j/bus/api/direction/DirectionsApi.java new file mode 100644 index 0000000..2c2228e --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/direction/DirectionsApi.java @@ -0,0 +1,11 @@ +package com.cta4j.bus.api.direction; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.util.List; + +@NullMarked +public interface DirectionsApi { + List findDirectionsByRouteId(@Nullable String routeId); +} diff --git a/src/main/java/com/cta4j/bus/api/direction/impl/DirectionsApiImpl.java b/src/main/java/com/cta4j/bus/api/direction/impl/DirectionsApiImpl.java new file mode 100644 index 0000000..b0075cb --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/direction/impl/DirectionsApiImpl.java @@ -0,0 +1,97 @@ +package com.cta4j.bus.api.direction.impl; + +import com.cta4j.bus.api.ApiUtils; +import com.cta4j.bus.api.direction.DirectionsApi; +import com.cta4j.bus.external.CtaBustimeResponse; +import com.cta4j.bus.external.CtaDirection; +import com.cta4j.bus.external.CtaError; +import com.cta4j.bus.external.CtaResponse; +import com.cta4j.exception.Cta4jException; +import com.cta4j.util.HttpUtils; +import org.apache.hc.core5.net.URIBuilder; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +import tools.jackson.core.JacksonException; +import tools.jackson.core.type.TypeReference; +import tools.jackson.databind.ObjectMapper; + +import java.util.List; + +@NullMarked +public final class DirectionsApiImpl implements DirectionsApi { + private static final String DIRECTIONS_ENDPOINT = String.format("%s/getdirections", ApiUtils.API_PREFIX); + + private final String host; + private final String apiKey; + private final ObjectMapper objectMapper; + + public DirectionsApiImpl( + @Nullable String host, + @Nullable String apiKey, + @Nullable ObjectMapper objectMapper + ) { + if (host == null) { + throw new IllegalArgumentException("host must not be null"); + } + + if (apiKey == null) { + throw new IllegalArgumentException("apiKey must not be null"); + } + + if (objectMapper == null) { + throw new IllegalArgumentException("objectMapper must not be null"); + } + + this.host = host; + this.apiKey = apiKey; + this.objectMapper = objectMapper; + } + + @Override + public List findDirectionsByRouteId(@Nullable String routeId) { + if (routeId == null) { + throw new IllegalArgumentException("routeId must not be null"); + } + + String url = new URIBuilder() + .setScheme(ApiUtils.SCHEME) + .setHost(this.host) + .setPath(DIRECTIONS_ENDPOINT) + .addParameter("rt", routeId) + .addParameter("key", this.apiKey) + .addParameter("format", "json") + .toString(); + + String response = HttpUtils.get(url); + + TypeReference>> typeReference = new TypeReference<>() {}; + CtaResponse> directionsResponse; + + try { + directionsResponse = this.objectMapper.readValue(response, typeReference); + } catch (JacksonException e) { + String message = String.format("Failed to parse response from %s", DIRECTIONS_ENDPOINT); + + throw new Cta4jException(message, e); + } + + CtaBustimeResponse> bustimeResponse = directionsResponse.bustimeResponse(); + + List errors = bustimeResponse.error(); + List directions = bustimeResponse.data(); + + if ((errors != null) && !errors.isEmpty()) { + String message = ApiUtils.buildErrorMessage(DIRECTIONS_ENDPOINT, errors); + + throw new Cta4jException(message); + } + + if ((directions == null) || directions.isEmpty()) { + return List.of(); + } + + return directions.stream() + .map(CtaDirection::id) + .toList(); + } +} diff --git a/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java b/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java index 4735d0f..6eeb7af 100644 --- a/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java @@ -3,10 +3,11 @@ import com.cta4j.bus.api.ApiUtils; import com.cta4j.bus.api.BusApi; import com.cta4j.bus.api.DetoursApi; -import com.cta4j.bus.api.DirectionsApi; +import com.cta4j.bus.api.direction.DirectionsApi; import com.cta4j.bus.api.LocalesApi; import com.cta4j.bus.api.PatternsApi; import com.cta4j.bus.api.PredictionsApi; +import com.cta4j.bus.api.direction.impl.DirectionsApiImpl; import com.cta4j.bus.api.route.RoutesApi; import com.cta4j.bus.api.StopsApi; import com.cta4j.bus.api.route.impl.RoutesApiImpl; @@ -35,6 +36,9 @@ public final class BusApiImpl implements BusApi { private final String host; private final String apiKey; private final ObjectMapper objectMapper; + private final VehiclesApi vehiclesApi; + private final RoutesApi routesApi; + private final DirectionsApi directionsApi; public BusApiImpl( @Nullable String host, @@ -51,6 +55,9 @@ public BusApiImpl( this.host = host; this.apiKey = apiKey; this.objectMapper = new ObjectMapper(); + this.vehiclesApi = new VehiclesApiImpl(this.host, this.apiKey, this.objectMapper); + this.routesApi = new RoutesApiImpl(this.host, this.apiKey, this.objectMapper); + this.directionsApi = new DirectionsApiImpl(this.host, this.apiKey, this.objectMapper); } @Override @@ -107,17 +114,17 @@ public Instant systemTime() { @Override public VehiclesApi vehicles() { - return new VehiclesApiImpl(this.host, this.apiKey, this.objectMapper); + return this.vehiclesApi; } @Override public RoutesApi routes() { - return new RoutesApiImpl(this.host, this.apiKey, this.objectMapper); + return this.routesApi; } @Override public DirectionsApi directions() { - return null; + return this.directionsApi; } @Override From cf3e9567834b45c1fff947263d889f6f32c2f38f Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sat, 17 Jan 2026 16:08:51 -0600 Subject: [PATCH 36/53] API refactor --- src/main/java/com/cta4j/bus/api/BusApi.java | 1 + src/main/java/com/cta4j/bus/api/StopsApi.java | 4 - .../bus/api/direction/DirectionsApi.java | 2 +- .../api/direction/impl/DirectionsApiImpl.java | 2 +- .../com/cta4j/bus/api/impl/BusApiImpl.java | 7 +- .../com/cta4j/bus/api/route/RoutesApi.java | 2 +- .../bus/api/route/impl/RoutesApiImpl.java | 2 +- .../java/com/cta4j/bus/api/stop/StopsApi.java | 39 ++ .../cta4j/bus/api/stop/external/CtaStop.java | 85 ++++ .../cta4j/bus/api/stop/impl/StopsApiImpl.java | 151 +++++++ .../bus/{ => api/stop}/mapper/StopMapper.java | 8 +- .../com/cta4j/bus/api/stop/model/Stop.java | 90 ++++ .../bus/api/vehicle/impl/VehiclesApiImpl.java | 6 +- .../java/com/cta4j/bus/client/BusClient.java | 150 ------- .../bus/client/internal/BusClientImpl.java | 402 +----------------- .../java/com/cta4j/bus/external/CtaStop.java | 44 -- src/main/java/com/cta4j/bus/model/Stop.java | 32 -- 17 files changed, 383 insertions(+), 644 deletions(-) delete mode 100644 src/main/java/com/cta4j/bus/api/StopsApi.java create mode 100644 src/main/java/com/cta4j/bus/api/stop/StopsApi.java create mode 100644 src/main/java/com/cta4j/bus/api/stop/external/CtaStop.java create mode 100644 src/main/java/com/cta4j/bus/api/stop/impl/StopsApiImpl.java rename src/main/java/com/cta4j/bus/{ => api/stop}/mapper/StopMapper.java (82%) create mode 100644 src/main/java/com/cta4j/bus/api/stop/model/Stop.java delete mode 100644 src/main/java/com/cta4j/bus/external/CtaStop.java delete mode 100644 src/main/java/com/cta4j/bus/model/Stop.java diff --git a/src/main/java/com/cta4j/bus/api/BusApi.java b/src/main/java/com/cta4j/bus/api/BusApi.java index 8ddaac4..17d491f 100644 --- a/src/main/java/com/cta4j/bus/api/BusApi.java +++ b/src/main/java/com/cta4j/bus/api/BusApi.java @@ -3,6 +3,7 @@ import com.cta4j.bus.api.direction.DirectionsApi; import com.cta4j.bus.api.impl.BusApiImpl; import com.cta4j.bus.api.route.RoutesApi; +import com.cta4j.bus.api.stop.StopsApi; import com.cta4j.bus.api.vehicle.VehiclesApi; import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/com/cta4j/bus/api/StopsApi.java b/src/main/java/com/cta4j/bus/api/StopsApi.java deleted file mode 100644 index eb5578b..0000000 --- a/src/main/java/com/cta4j/bus/api/StopsApi.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.cta4j.bus.api; - -public interface StopsApi { -} diff --git a/src/main/java/com/cta4j/bus/api/direction/DirectionsApi.java b/src/main/java/com/cta4j/bus/api/direction/DirectionsApi.java index 2c2228e..d9eeb04 100644 --- a/src/main/java/com/cta4j/bus/api/direction/DirectionsApi.java +++ b/src/main/java/com/cta4j/bus/api/direction/DirectionsApi.java @@ -7,5 +7,5 @@ @NullMarked public interface DirectionsApi { - List findDirectionsByRouteId(@Nullable String routeId); + List findByRouteId(@Nullable String routeId); } diff --git a/src/main/java/com/cta4j/bus/api/direction/impl/DirectionsApiImpl.java b/src/main/java/com/cta4j/bus/api/direction/impl/DirectionsApiImpl.java index b0075cb..a8792c7 100644 --- a/src/main/java/com/cta4j/bus/api/direction/impl/DirectionsApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/direction/impl/DirectionsApiImpl.java @@ -48,7 +48,7 @@ public DirectionsApiImpl( } @Override - public List findDirectionsByRouteId(@Nullable String routeId) { + public List findByRouteId(@Nullable String routeId) { if (routeId == null) { throw new IllegalArgumentException("routeId must not be null"); } diff --git a/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java b/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java index 6eeb7af..29996b9 100644 --- a/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java @@ -9,8 +9,9 @@ import com.cta4j.bus.api.PredictionsApi; import com.cta4j.bus.api.direction.impl.DirectionsApiImpl; import com.cta4j.bus.api.route.RoutesApi; -import com.cta4j.bus.api.StopsApi; +import com.cta4j.bus.api.stop.StopsApi; import com.cta4j.bus.api.route.impl.RoutesApiImpl; +import com.cta4j.bus.api.stop.impl.StopsApiImpl; import com.cta4j.bus.api.vehicle.VehiclesApi; import com.cta4j.bus.api.vehicle.impl.VehiclesApiImpl; import com.cta4j.bus.external.CtaBustimeResponse; @@ -39,6 +40,7 @@ public final class BusApiImpl implements BusApi { private final VehiclesApi vehiclesApi; private final RoutesApi routesApi; private final DirectionsApi directionsApi; + private final StopsApi stopsApi; public BusApiImpl( @Nullable String host, @@ -58,6 +60,7 @@ public BusApiImpl( this.vehiclesApi = new VehiclesApiImpl(this.host, this.apiKey, this.objectMapper); this.routesApi = new RoutesApiImpl(this.host, this.apiKey, this.objectMapper); this.directionsApi = new DirectionsApiImpl(this.host, this.apiKey, this.objectMapper); + this.stopsApi = new StopsApiImpl(this.host, this.apiKey, this.objectMapper); } @Override @@ -129,7 +132,7 @@ public DirectionsApi directions() { @Override public StopsApi stops() { - return null; + return this.stopsApi; } @Override diff --git a/src/main/java/com/cta4j/bus/api/route/RoutesApi.java b/src/main/java/com/cta4j/bus/api/route/RoutesApi.java index 51c694d..242d1af 100644 --- a/src/main/java/com/cta4j/bus/api/route/RoutesApi.java +++ b/src/main/java/com/cta4j/bus/api/route/RoutesApi.java @@ -7,5 +7,5 @@ @NullMarked public interface RoutesApi { - List getRoutes(); + List list(); } diff --git a/src/main/java/com/cta4j/bus/api/route/impl/RoutesApiImpl.java b/src/main/java/com/cta4j/bus/api/route/impl/RoutesApiImpl.java index 7026d41..9fc050d 100644 --- a/src/main/java/com/cta4j/bus/api/route/impl/RoutesApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/route/impl/RoutesApiImpl.java @@ -59,7 +59,7 @@ public RoutesApiImpl( } @Override - public List getRoutes() { + public List list() { String url = new URIBuilder() .setScheme(ApiUtils.SCHEME) .setHost(this.host) diff --git a/src/main/java/com/cta4j/bus/api/stop/StopsApi.java b/src/main/java/com/cta4j/bus/api/stop/StopsApi.java new file mode 100644 index 0000000..39ae325 --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/stop/StopsApi.java @@ -0,0 +1,39 @@ +package com.cta4j.bus.api.stop; + +import com.cta4j.bus.api.stop.model.Stop; +import com.cta4j.exception.Cta4jException; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +@NullMarked +public interface StopsApi { + List findByRouteIdAndDirection(@Nullable String routeId, @Nullable String direction); + + List findByIds(@Nullable Collection<@Nullable String> stopIds); + + default Optional findById(@Nullable String stopId) { + if (stopId == null) { + throw new IllegalArgumentException("stopId must not be null"); + } + + List ids = List.of(stopId); + + List stops = this.findByIds(ids); + + if (stops.isEmpty()) { + return Optional.empty(); + } if (stops.size() > 1) { + String message = String.format("Multiple stops found for ID: %s", stopId); + + throw new Cta4jException(message); + } + + Stop stop = stops.getFirst(); + + return Optional.of(stop); + } +} diff --git a/src/main/java/com/cta4j/bus/api/stop/external/CtaStop.java b/src/main/java/com/cta4j/bus/api/stop/external/CtaStop.java new file mode 100644 index 0000000..db04d0d --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/stop/external/CtaStop.java @@ -0,0 +1,85 @@ +package com.cta4j.bus.api.stop.external; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.util.List; + +@NullMarked +@ApiStatus.Internal +@JsonIgnoreProperties(ignoreUnknown = true) +public record CtaStop( + String stpid, + + String stpnm, + + double lat, + + double lon, + + @Nullable + List dtradd, + + @Nullable + List dtrrem, + + @Nullable + Integer gtfsseq, + + @Nullable + Boolean ada +) { + public CtaStop( + @Nullable String stpid, + @Nullable String stpnm, + double lat, + double lon, + @Nullable List<@Nullable Integer> dtradd, + @Nullable List<@Nullable Integer> dtrrem, + @Nullable Integer gtfsseq, + @Nullable Boolean ada + ) { + if (stpid == null) { + throw new IllegalArgumentException("stpid must not be null"); + } + + if (stpnm == null) { + throw new IllegalArgumentException("stpnm must not be null"); + } + + List dtraddCopy = null; + + if (dtradd != null) { + for (Integer detourId : dtradd) { + if (detourId == null) { + throw new IllegalArgumentException("dtradd must not contain null values"); + } + } + + dtraddCopy = List.copyOf(dtradd); + } + + List dtrremCopy = null; + + if (dtrrem != null) { + for (Integer detourId : dtrrem) { + if (detourId == null) { + throw new IllegalArgumentException("dtrrem must not contain null values"); + } + } + + dtrremCopy = List.copyOf(dtrrem); + } + + this.stpid = stpid; + this.stpnm = stpnm; + this.lat = lat; + this.lon = lon; + this.dtradd = dtraddCopy; + this.dtrrem = dtrremCopy; + this.gtfsseq = gtfsseq; + this.ada = ada; + } +} diff --git a/src/main/java/com/cta4j/bus/api/stop/impl/StopsApiImpl.java b/src/main/java/com/cta4j/bus/api/stop/impl/StopsApiImpl.java new file mode 100644 index 0000000..699df25 --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/stop/impl/StopsApiImpl.java @@ -0,0 +1,151 @@ +package com.cta4j.bus.api.stop.impl; + +import com.cta4j.bus.api.ApiUtils; +import com.cta4j.bus.api.stop.StopsApi; +import com.cta4j.bus.api.stop.external.CtaStop; +import com.cta4j.bus.api.stop.mapper.StopMapper; +import com.cta4j.bus.api.stop.model.Stop; +import com.cta4j.bus.external.CtaBustimeResponse; +import com.cta4j.bus.external.CtaError; +import com.cta4j.bus.external.CtaResponse; +import com.cta4j.exception.Cta4jException; +import com.cta4j.util.HttpUtils; +import org.apache.hc.core5.net.URIBuilder; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +import org.mapstruct.factory.Mappers; +import tools.jackson.core.JacksonException; +import tools.jackson.core.type.TypeReference; +import tools.jackson.databind.ObjectMapper; + +import java.util.Collection; +import java.util.List; + +@NullMarked +@ApiStatus.Internal +public final class StopsApiImpl implements StopsApi { + private static final String STOPS_ENDPOINT = String.format("%s/getstops", ApiUtils.API_PREFIX); + private static final int MAX_STOP_IDS_PER_REQUEST = 10; + + private final String host; + private final String apiKey; + private final ObjectMapper objectMapper; + private final StopMapper stopMapper; + + public StopsApiImpl( + @Nullable String host, + @Nullable String apiKey, + @Nullable ObjectMapper objectMapper + ) { + if (host == null) { + throw new IllegalArgumentException("host must not be null"); + } + + if (apiKey == null) { + throw new IllegalArgumentException("apiKey must not be null"); + } + + if (objectMapper == null) { + throw new IllegalArgumentException("objectMapper must not be null"); + } + + this.host = host; + this.apiKey = apiKey; + this.objectMapper = objectMapper; + this.stopMapper = Mappers.getMapper(StopMapper.class); + } + + @Override + public List findByRouteIdAndDirection(@Nullable String routeId, @Nullable String direction) { + if (routeId == null) { + throw new IllegalArgumentException("routeId must not be null"); + } + + if (direction == null) { + throw new IllegalArgumentException("direction must not be null"); + } + + String url = new URIBuilder() + .setScheme(ApiUtils.SCHEME) + .setHost(this.host) + .setPath(STOPS_ENDPOINT) + .addParameter("rt", routeId) + .addParameter("dir", direction) + .addParameter("key", this.apiKey) + .addParameter("format", "json") + .toString(); + + return this.makeRequest(url); + } + + @Override + public List findByIds(@Nullable Collection<@Nullable String> stopIds) { + if (stopIds == null) { + throw new IllegalArgumentException("stopIds must not be null"); + } + + for (String stopId : stopIds) { + if (stopId == null) { + throw new IllegalArgumentException("stopIds must not contain null elements"); + } + } + + if (stopIds.size() > MAX_STOP_IDS_PER_REQUEST) { + String message = String.format( + "A maximum of %d stop IDs can be requested at once, but %d were provided", + MAX_STOP_IDS_PER_REQUEST, + stopIds.size() + ); + + throw new IllegalArgumentException(message); + } + + String stopIdsString = String.join(",", stopIds); + + String url = new URIBuilder() + .setScheme(ApiUtils.SCHEME) + .setHost(this.host) + .setPath(STOPS_ENDPOINT) + .addParameter("stpid", stopIdsString) + .addParameter("key", this.apiKey) + .addParameter("format", "json") + .toString(); + + return this.makeRequest(url); + } + + private List makeRequest(String url) { + String response = HttpUtils.get(url); + + TypeReference>> typeReference = new TypeReference<>() {}; + CtaResponse> stopsResponse; + + try { + stopsResponse = this.objectMapper.readValue(response, typeReference); + } catch (JacksonException e) { + String message = String.format("Failed to parse response from %s", STOPS_ENDPOINT); + + throw new Cta4jException(message, e); + } + + CtaBustimeResponse> bustimeResponse = stopsResponse.bustimeResponse(); + + List errors = bustimeResponse.error(); + List stops = bustimeResponse.data(); + + if ((errors != null) && !errors.isEmpty()) { + String message = ApiUtils.buildErrorMessage(STOPS_ENDPOINT, errors); + + throw new Cta4jException(message); + } + + if ((stops == null) || stops.isEmpty()) { + return List.of(); + } + + return stops.stream() + .map(this.stopMapper::toDomain) + .toList(); + } +} diff --git a/src/main/java/com/cta4j/bus/mapper/StopMapper.java b/src/main/java/com/cta4j/bus/api/stop/mapper/StopMapper.java similarity index 82% rename from src/main/java/com/cta4j/bus/mapper/StopMapper.java rename to src/main/java/com/cta4j/bus/api/stop/mapper/StopMapper.java index 82a23e9..a34f19f 100644 --- a/src/main/java/com/cta4j/bus/mapper/StopMapper.java +++ b/src/main/java/com/cta4j/bus/api/stop/mapper/StopMapper.java @@ -1,13 +1,13 @@ -package com.cta4j.bus.mapper; +package com.cta4j.bus.api.stop.mapper; -import com.cta4j.bus.external.CtaStop; -import com.cta4j.bus.model.Stop; +import com.cta4j.bus.api.stop.external.CtaStop; +import com.cta4j.bus.api.stop.model.Stop; import org.jetbrains.annotations.ApiStatus; import org.mapstruct.Mapper; import org.mapstruct.Mapping; -@ApiStatus.Internal @Mapper +@ApiStatus.Internal public interface StopMapper { @Mapping(source = "stpid", target = "id") @Mapping(source = "stpnm", target = "name") diff --git a/src/main/java/com/cta4j/bus/api/stop/model/Stop.java b/src/main/java/com/cta4j/bus/api/stop/model/Stop.java new file mode 100644 index 0000000..b2a2843 --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/stop/model/Stop.java @@ -0,0 +1,90 @@ +package com.cta4j.bus.api.stop.model; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.math.BigDecimal; +import java.util.List; + +@NullMarked +public record Stop( + String id, + + String name, + + BigDecimal latitude, + + BigDecimal longitude, + + @Nullable + List detoursAdded, + + @Nullable + List detoursRemoved, + + @Nullable + Integer gtfsSequence, + + @Nullable + Boolean adaAccessible +) { + public Stop( + @Nullable String id, + @Nullable String name, + @Nullable BigDecimal latitude, + @Nullable BigDecimal longitude, + @Nullable List<@Nullable Integer> detoursAdded, + @Nullable List<@Nullable Integer> detoursRemoved, + @Nullable Integer gtfsSequence, + @Nullable Boolean adaAccessible + ) { + if (id == null) { + throw new IllegalArgumentException("id must not be null"); + } + + if (name == null) { + throw new IllegalArgumentException("name must not be null"); + } + + if (latitude == null) { + throw new IllegalArgumentException("latitude must not be null"); + } + + if (longitude == null) { + throw new IllegalArgumentException("longitude must not be null"); + } + + List detoursAddedCopy = null; + + if (detoursAdded != null) { + for (Integer detourId : detoursAdded) { + if (detourId == null) { + throw new IllegalArgumentException("detoursAdded must not contain null values"); + } + } + + detoursAddedCopy = List.copyOf(detoursAdded); + } + + List detoursRemovedCopy = null; + + if (detoursRemoved != null) { + for (Integer detourId : detoursRemoved) { + if (detourId == null) { + throw new IllegalArgumentException("detoursRemoved must not contain null values"); + } + } + + detoursRemovedCopy = List.copyOf(detoursRemoved); + } + + this.id = id; + this.name = name; + this.latitude = latitude; + this.longitude = longitude; + this.detoursAdded = detoursAddedCopy; + this.detoursRemoved = detoursRemovedCopy; + this.gtfsSequence = gtfsSequence; + this.adaAccessible = adaAccessible; + } +} diff --git a/src/main/java/com/cta4j/bus/api/vehicle/impl/VehiclesApiImpl.java b/src/main/java/com/cta4j/bus/api/vehicle/impl/VehiclesApiImpl.java index 82f99b1..d4eaba0 100644 --- a/src/main/java/com/cta4j/bus/api/vehicle/impl/VehiclesApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/vehicle/impl/VehiclesApiImpl.java @@ -83,7 +83,7 @@ public List findByIds(@Nullable Collection<@Nullable String> ids) { .addParameter("format", "json") .toString(); - return this.getVehicles(url); + return this.makeRequest(url); } @Override @@ -114,10 +114,10 @@ public List findByRouteIds(@Nullable Collection<@Nullable String> route .addParameter("format", "json") .toString(); - return this.getVehicles(url); + return this.makeRequest(url); } - private List getVehicles(String url) { + private List makeRequest(String url) { String response = HttpUtils.get(url); TypeReference>> typeReference = new TypeReference<>() {}; diff --git a/src/main/java/com/cta4j/bus/client/BusClient.java b/src/main/java/com/cta4j/bus/client/BusClient.java index ee7f105..db720ce 100644 --- a/src/main/java/com/cta4j/bus/client/BusClient.java +++ b/src/main/java/com/cta4j/bus/client/BusClient.java @@ -2,17 +2,12 @@ import com.cta4j.bus.client.internal.BusClientImpl; import com.cta4j.bus.model.Arrival; -import com.cta4j.bus.api.vehicle.model.Vehicle; import com.cta4j.bus.model.Detour; -import com.cta4j.bus.api.route.model.Route; import com.cta4j.bus.model.RoutePattern; -import com.cta4j.bus.model.Stop; import com.cta4j.exception.Cta4jException; import org.jspecify.annotations.NullMarked; -import java.time.Instant; import java.util.List; -import java.util.Optional; /** * A client for interacting with the CTA Bus Tracker API. @@ -20,151 +15,6 @@ @NullMarked @SuppressWarnings("ConstantConditions") public interface BusClient { - /** - * Retrieves the current system time from the CTA Bus Tracker API. - * - * @return the current system time as an {@link Instant} - * @throws Cta4jException if an error occurs while fetching the data - */ - Instant getSystemTime(); - - /** - * Finds buses matching the specified bus IDs. - * - * @param ids an {@link Iterable} of bus IDs - * @return a {@link List} of buses corresponding to the specified IDs - * @throws IllegalArgumentException if the specified IDs are {@code null} - * @throws Cta4jException if an error occurs while fetching the data - */ - List findBusesById(Iterable ids); - - /** - * Finds buses operating on the specified route IDs. - * - * @param routeIds an {@link Iterable} of bus route IDs - * @return a {@link List} of buses corresponding to the specified route IDs - * @throws IllegalArgumentException if the specified route IDs are {@code null} - * @throws Cta4jException if an error occurs while fetching the data - */ - List findBusesByRouteId(Iterable routeIds); - - /** - * Finds buses operating on the specified route ID. - * - * @param routeId the bus route ID - * @return a {@link List} of buses corresponding to the specified route ID - * @throws IllegalArgumentException if the specified route ID is {@code null} - * @throws Cta4jException if an error occurs while fetching the data - */ - default List findBusesByRouteId(String routeId) { - if (routeId == null) { - throw new IllegalArgumentException("routeId must not be null"); - } - - List routeIds = List.of(routeId); - - return this.findBusesByRouteId(routeIds); - } - - /** - * Finds a bus by its ID. - * - * @param id the ID of the bus - * @return an {@link Optional} containing the bus information if found, or an empty {@link Optional} if not found - * @throws IllegalArgumentException if the specified bus ID is {@code null} - * @throws Cta4jException if an error occurs while fetching the data - */ - default Optional findBusById(String id) { - if (id == null) { - throw new IllegalArgumentException("id must not be null"); - } - - List ids = List.of(id); - - List vehicles = this.findBusesById(ids); - - if (vehicles.isEmpty()) { - return Optional.empty(); - } if (vehicles.size() > 1) { - String message = String.format("Multiple buses found for ID: %s", id); - - throw new Cta4jException(message); - } - - Vehicle vehicle = vehicles.getFirst(); - - return Optional.of(vehicle); - } - - /** - * Retrieves a {@link List} of all bus routes. - * - * @return a {@link List} of all bus routes - * @throws Cta4jException if an error occurs while fetching the data - */ - List getRoutes(); - - /** - * Finds directions for the specified route ID. - * - * @param routeId the ID of the bus route - * @return a {@link List} of directions for the specified bus route - * @throws IllegalArgumentException if the specified bus route is {@code null} - * @throws Cta4jException if an error occurs while fetching the data - */ - List findDirectionsByRouteId(String routeId); - - /** - * Finds stops for the specified route ID and direction. - * - * @param routeId the ID of the bus route - * @param direction the direction of the bus route - * @return a {@link List} of stops for the specified bus route and direction - * @throws IllegalArgumentException if the specified bus route or direction is {@code null} - * @throws Cta4jException if an error occurs while fetching the data - */ - List findStopsByRouteIdAndDirection(String routeId, String direction); - - /** - * Finds stops for the specified stop IDs. - * - * @param stopIds an {@link Iterable} of stop IDs - * @return a {@link List} of stops corresponding to the specified stop IDs - * @throws IllegalArgumentException if the specified stop IDs are {@code null} - * @throws Cta4jException if an error occurs while fetching the data - */ - List findStopsByStopId(Iterable stopIds); - - /** - * Finds a stop by its ID. - * - * @param stopId the ID of the stop - * @return an {@link Optional} containing the stop information if found, or an empty {@link Optional} if not found - * @throws IllegalArgumentException if the specified stop ID is {@code null} - * @throws Cta4jException if an error occurs while fetching the data - */ - default Optional findStopsByStopId(String stopId) { - if (stopId == null) { - throw new IllegalArgumentException("stopId must not be null"); - } - - List ids = List.of(stopId); - - List stops = this.findStopsByStopId(ids); - - if (stops.isEmpty()) { - return Optional.empty(); - } if (stops.size() > 1) { - String message = String.format("Multiple stops found for ID: %s", stopId); - - throw new Cta4jException(message); - } - - Stop stop = stops.getFirst(); - - return Optional.of(stop); - } - /** * Finds route patterns for the specified pattern IDs. * diff --git a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java index 284dfa7..6f3d789 100644 --- a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java +++ b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java @@ -9,22 +9,13 @@ import com.cta4j.bus.mapper.ArrivalMapper; import com.cta4j.bus.api.vehicle.mapper.VehicleMapper; import com.cta4j.bus.mapper.DetourMapper; -import com.cta4j.bus.api.route.mapper.RouteMapper; import com.cta4j.bus.mapper.RoutePatternMapper; -import com.cta4j.bus.mapper.StopMapper; -import com.cta4j.bus.mapper.util.CtaBusMappingQualifiers; +import com.cta4j.bus.api.stop.mapper.StopMapper; import com.cta4j.bus.model.Arrival; -import com.cta4j.bus.api.vehicle.model.Vehicle; import com.cta4j.bus.model.Detour; -import com.cta4j.bus.api.route.model.Route; import com.cta4j.bus.model.RoutePattern; -import com.cta4j.bus.model.Stop; import com.cta4j.exception.Cta4jException; import com.cta4j.bus.external.CtaDetour; -import com.cta4j.bus.external.CtaDirection; -import com.cta4j.bus.api.route.external.CtaRoute; -import com.cta4j.bus.external.CtaStop; -import com.cta4j.bus.api.vehicle.external.CtaVehicle; import com.cta4j.util.HttpUtils; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -37,7 +28,6 @@ import org.apache.hc.core5.net.URIBuilder; import org.jetbrains.annotations.ApiStatus; -import java.time.Instant; import java.util.List; @NullMarked @@ -49,24 +39,16 @@ public final class BusClientImpl implements BusClient { private static final String SCHEME = "https"; private static final String DEFAULT_HOST = "ctabustracker.com"; private static final String API_PREFIX = "/bustime/api/v3"; - private static final String SYSTEM_TIME_ENDPOINT = String.format("%s/gettime", API_PREFIX); - private static final String ROUTES_ENDPOINT = String.format("%s/getroutes", API_PREFIX); - private static final String DIRECTIONS_ENDPOINT = String.format("%s/getdirections", API_PREFIX); - private static final String STOPS_ENDPOINT = String.format("%s/getstops", API_PREFIX); private static final String PATTERNS_ENDPOINT = String.format("%s/getpatterns", API_PREFIX); private static final String PREDICTIONS_ENDPOINT = String.format("%s/getpredictions", API_PREFIX); private static final String DETOURS_ENDPOINT = String.format("%s/getdetours", API_PREFIX); - private static final String VEHICLES_ENDPOINT = String.format("%s/getvehicles", API_PREFIX); private final String host; private final String apiKey; private final ObjectMapper objectMapper; private final ArrivalMapper arrivalMapper; - private final RouteMapper routeMapper; - private final StopMapper stopMapper; private final RoutePatternMapper routePatternMapper; private final DetourMapper detourMapper; - private final VehicleMapper vehicleMapper; private BusClientImpl(String host, String apiKey) { if (host == null) { @@ -81,350 +63,8 @@ private BusClientImpl(String host, String apiKey) { this.apiKey = apiKey; this.objectMapper = new ObjectMapper(); this.arrivalMapper = Mappers.getMapper(ArrivalMapper.class); - this.routeMapper = Mappers.getMapper(RouteMapper.class); - this.stopMapper = Mappers.getMapper(StopMapper.class); this.routePatternMapper = Mappers.getMapper(RoutePatternMapper.class); this.detourMapper = Mappers.getMapper(DetourMapper.class); - this.vehicleMapper = Mappers.getMapper(VehicleMapper.class); - } - - @Override - public Instant getSystemTime() { - String url = new URIBuilder() - .setScheme(SCHEME) - .setHost(this.host) - .setPath(SYSTEM_TIME_ENDPOINT) - .addParameter("key", this.apiKey) - .addParameter("format", "json") - .toString(); - - String response = HttpUtils.get(url); - - TypeReference> typeReference = new TypeReference<>() {}; - CtaResponse timeResponse; - - try { - timeResponse = this.objectMapper.readValue(response, typeReference); - } catch (JacksonException e) { - String message = String.format("Failed to parse response from %s", SYSTEM_TIME_ENDPOINT); - - throw new Cta4jException(message, e); - } - - CtaBustimeResponse bustimeResponse = timeResponse.bustimeResponse(); - - List errors = bustimeResponse.error(); - String systemTime = bustimeResponse.data(); - - if ((errors == null) && (systemTime == null)) { - String message = String.format( - "System time bustime response missing both error and data from %s", - SYSTEM_TIME_ENDPOINT - ); - - throw new Cta4jException(message); - } - - if ((errors != null) && !errors.isEmpty()) { - String message = this.buildErrorMessage(SYSTEM_TIME_ENDPOINT, errors); - - throw new Cta4jException(message); - } - - if (systemTime == null) { - String message = String.format("No system time data returned from %s", SYSTEM_TIME_ENDPOINT); - - throw new Cta4jException(message); - } - - return CtaBusMappingQualifiers.mapTimestamp(systemTime); - } - - @Override - public List findBusesById(Iterable ids) { - if (ids == null) { - throw new IllegalArgumentException("ids must not be null"); - } - - for (String id : ids) { - if (id == null) { - throw new IllegalArgumentException("ids must not contain null elements"); - } - } - - String idsString = String.join(",", ids); - - String url = new URIBuilder() - .setScheme(SCHEME) - .setHost(this.host) - .setPath(VEHICLES_ENDPOINT) - .addParameter("vid", idsString) - .addParameter("tmres", "s") - .addParameter("key", this.apiKey) - .addParameter("format", "json") - .toString(); - - return this.getBuses(url); - } - - @Override - public List findBusesByRouteId(Iterable routeIds) { - if (routeIds == null) { - throw new IllegalArgumentException("routeIds must not be null"); - } - - for (String routeId : routeIds) { - if (routeId == null) { - throw new IllegalArgumentException("routeIds must not contain null elements"); - } - } - - String routeIdsString = String.join(",", routeIds); - - String url = new URIBuilder() - .setScheme(SCHEME) - .setHost(this.host) - .setPath(VEHICLES_ENDPOINT) - .addParameter("rt", routeIdsString) - .addParameter("tmres", "s") - .addParameter("key", this.apiKey) - .addParameter("format", "json") - .toString(); - - return this.getBuses(url); - } - - @Override - public List getRoutes() { - String url = new URIBuilder() - .setScheme(SCHEME) - .setHost(this.host) - .setPath(ROUTES_ENDPOINT) - .addParameter("key", this.apiKey) - .addParameter("format", "json") - .toString(); - - String response = HttpUtils.get(url); - - TypeReference>> typeReference = new TypeReference<>() {}; - CtaResponse> routesResponse; - - try { - routesResponse = this.objectMapper.readValue(response, typeReference); - } catch (JacksonException e) { - String message = String.format("Failed to parse response from %s", ROUTES_ENDPOINT); - - throw new Cta4jException(message, e); - } - - CtaBustimeResponse> bustimeResponse = routesResponse.bustimeResponse(); - - List errors = bustimeResponse.error(); - List routes = bustimeResponse.data(); - - if ((errors == null) && (routes == null)) { - log.debug("Routes bustime response missing both error and data from {}", ROUTES_ENDPOINT); - - return List.of(); - } - - if ((errors != null) && !errors.isEmpty()) { - String message = this.buildErrorMessage(ROUTES_ENDPOINT, errors); - - throw new Cta4jException(message); - } - - if ((routes == null) || routes.isEmpty()) { - return List.of(); - } - - return routes.stream() - .map(this.routeMapper::toDomain) - .toList(); - } - - @Override - public List findDirectionsByRouteId(String routeId) { - if (routeId == null) { - throw new IllegalArgumentException("routeId must not be null"); - } - - String url = new URIBuilder() - .setScheme(SCHEME) - .setHost(this.host) - .setPath(DIRECTIONS_ENDPOINT) - .addParameter("rt", routeId) - .addParameter("key", this.apiKey) - .addParameter("format", "json") - .toString(); - - String response = HttpUtils.get(url); - - TypeReference>> typeReference = new TypeReference<>() {}; - CtaResponse> directionsResponse; - - try { - directionsResponse = this.objectMapper.readValue(response, typeReference); - } catch (JacksonException e) { - String message = String.format("Failed to parse response from %s", DIRECTIONS_ENDPOINT); - - throw new Cta4jException(message, e); - } - - CtaBustimeResponse> bustimeResponse = directionsResponse.bustimeResponse(); - - List errors = bustimeResponse.error(); - List directions = bustimeResponse.data(); - - if ((errors == null) && (directions == null)) { - log.debug("Directions bustime response missing both error and data from {}", DIRECTIONS_ENDPOINT); - - return List.of(); - } - - if ((errors != null) && !errors.isEmpty()) { - String message = this.buildErrorMessage(DIRECTIONS_ENDPOINT, errors); - - throw new Cta4jException(message); - } - - if ((directions == null) || directions.isEmpty()) { - return List.of(); - } - - return directions.stream() - .map(CtaDirection::id) - .toList(); - } - - @Override - public List findStopsByRouteIdAndDirection(String routeId, String direction) { - if (routeId == null) { - throw new IllegalArgumentException("routeId must not be null"); - } - - if (direction == null) { - throw new IllegalArgumentException("direction must not be null"); - } - - String url = new URIBuilder() - .setScheme(SCHEME) - .setHost(this.host) - .setPath(STOPS_ENDPOINT) - .addParameter("rt", routeId) - .addParameter("dir", direction) - .addParameter("key", this.apiKey) - .addParameter("format", "json") - .toString(); - - String response = HttpUtils.get(url); - - TypeReference>> typeReference = new TypeReference<>() {}; - CtaResponse> stopsResponse; - - try { - stopsResponse = this.objectMapper.readValue(response, typeReference); - } catch (JacksonException e) { - String message = String.format("Failed to parse response from %s", STOPS_ENDPOINT); - - throw new Cta4jException(message, e); - } - - CtaBustimeResponse> bustimeResponse = stopsResponse.bustimeResponse(); - - List errors = bustimeResponse.error(); - List stops = bustimeResponse.data(); - - if ((errors == null) && (stops == null)) { - log.debug( - "Stops bustime response missing both error and data for route={} direction={} from {}", - routeId, - direction, - STOPS_ENDPOINT - ); - - return List.of(); - } - - if ((errors != null) && !errors.isEmpty()) { - String message = this.buildErrorMessage(STOPS_ENDPOINT, errors); - - throw new Cta4jException(message); - } - - if ((stops == null) || stops.isEmpty()) { - return List.of(); - } - - return stops.stream() - .map(this.stopMapper::toDomain) - .toList(); - } - - @Override - public List findStopsByStopId(Iterable stopIds) { - if (stopIds == null) { - throw new IllegalArgumentException("stopIds must not be null"); - } - - for (String stopId : stopIds) { - if (stopId == null) { - throw new IllegalArgumentException("stopIds must not contain null elements"); - } - } - - String stopIdsString = String.join(",", stopIds); - - String url = new URIBuilder() - .setScheme(SCHEME) - .setHost(this.host) - .setPath(STOPS_ENDPOINT) - .addParameter("stpid", stopIdsString) - .addParameter("key", this.apiKey) - .addParameter("format", "json") - .toString(); - - String response = HttpUtils.get(url); - - TypeReference>> typeReference = new TypeReference<>() {}; - CtaResponse> stopsResponse; - - try { - stopsResponse = this.objectMapper.readValue(response, typeReference); - } catch (JacksonException e) { - String message = String.format("Failed to parse response from %s", STOPS_ENDPOINT); - - throw new Cta4jException(message, e); - } - - CtaBustimeResponse> bustimeResponse = stopsResponse.bustimeResponse(); - - List errors = bustimeResponse.error(); - List stops = bustimeResponse.data(); - - if ((errors == null) && (stops == null)) { - log.debug( - "Stop bustime response missing both error and data for stopIds={} from {}", - stopIds, - STOPS_ENDPOINT - ); - - return List.of(); - } - - if ((errors != null) && !errors.isEmpty()) { - String message = this.buildErrorMessage(STOPS_ENDPOINT, errors); - - throw new Cta4jException(message); - } - - if ((stops == null) || stops.isEmpty()) { - return List.of(); - } - - return stops.stream() - .map(this.stopMapper::toDomain) - .toList(); } @Override @@ -583,46 +223,6 @@ private String buildErrorMessage(String endpoint, List errors) { return String.format("Error response from %s: %s", endpoint, message); } - private List getBuses(String url) { - String response = HttpUtils.get(url); - - TypeReference>> typeReference = new TypeReference<>() {}; - CtaResponse> vehicleResponse; - - try { - vehicleResponse = this.objectMapper.readValue(response, typeReference); - } catch (JacksonException e) { - String message = String.format("Failed to parse response from %s", VEHICLES_ENDPOINT); - - throw new Cta4jException(message, e); - } - - CtaBustimeResponse> bustimeResponse = vehicleResponse.bustimeResponse(); - - List errors = bustimeResponse.error(); - List vehicles = bustimeResponse.data(); - - if ((errors == null) && (vehicleResponse == null)) { - log.debug("Vehicles bustime response missing both error and data from {}", VEHICLES_ENDPOINT); - - return List.of(); - } - - if ((errors != null) && !errors.isEmpty()) { - String message = this.buildErrorMessage(VEHICLES_ENDPOINT, errors); - - throw new Cta4jException(message); - } - - if ((vehicles == null) || vehicles.isEmpty()) { - return List.of(); - } - - return vehicles.stream() - .map(this.vehicleMapper::toDomain) - .toList(); - } - private List getPatterns(String url) { String response = HttpUtils.get(url); diff --git a/src/main/java/com/cta4j/bus/external/CtaStop.java b/src/main/java/com/cta4j/bus/external/CtaStop.java deleted file mode 100644 index 458ad4b..0000000 --- a/src/main/java/com/cta4j/bus/external/CtaStop.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.cta4j.bus.external; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import org.jetbrains.annotations.ApiStatus; -import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; - -import java.util.List; - -@NullMarked -@ApiStatus.Internal -@SuppressWarnings("ConstantConditions") -@JsonIgnoreProperties(ignoreUnknown = true) -public record CtaStop( - String stpid, - - String stpnm, - - double lat, - - double lon, - - @Nullable - List dtradd, - - @Nullable - List dtrrem, - - @Nullable - Integer gtfsseq, - - @Nullable - Boolean ada -) { - public CtaStop { - if (stpid == null) { - throw new IllegalArgumentException("stpid must not be null"); - } - - if (stpnm == null) { - throw new IllegalArgumentException("stpnm must not be null"); - } - } -} diff --git a/src/main/java/com/cta4j/bus/model/Stop.java b/src/main/java/com/cta4j/bus/model/Stop.java deleted file mode 100644 index 7916ae5..0000000 --- a/src/main/java/com/cta4j/bus/model/Stop.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.cta4j.bus.model; - -import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; - -import java.math.BigDecimal; -import java.util.List; - -@NullMarked -@SuppressWarnings("ConstantConditions") -public record Stop( - String id, - - String name, - - BigDecimal latitude, - - BigDecimal longitude, - - @Nullable - List detoursAdded, - - @Nullable - List detoursRemoved, - - @Nullable - Integer gtfsSequence, - - @Nullable - Boolean adaAccessible -) { -} From 828fb46228db0dd5f3e44ce69bd991cdad5f5cfe Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sat, 17 Jan 2026 16:11:14 -0600 Subject: [PATCH 37/53] API refactor --- src/main/java/com/cta4j/bus/api/ApiUtils.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/cta4j/bus/api/ApiUtils.java b/src/main/java/com/cta4j/bus/api/ApiUtils.java index 4d2018c..cf9736e 100644 --- a/src/main/java/com/cta4j/bus/api/ApiUtils.java +++ b/src/main/java/com/cta4j/bus/api/ApiUtils.java @@ -2,9 +2,12 @@ import com.cta4j.bus.external.CtaError; import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.List; +@NullMarked @ApiStatus.Internal public final class ApiUtils { public static final String SCHEME = "https"; @@ -15,7 +18,7 @@ private ApiUtils() { throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); } - public static String buildErrorMessage(String endpoint, List errors) { + public static String buildErrorMessage(@Nullable String endpoint, @Nullable List<@Nullable CtaError> errors) { if (endpoint == null) { throw new IllegalArgumentException("endpoint must not be null"); } @@ -30,10 +33,12 @@ public static String buildErrorMessage(String endpoint, List errors) { } } - String message = errors.stream() - .map(CtaError::msg) - .reduce("%s; %s"::formatted) - .orElse("Unknown error"); + List errorsCopy = List.copyOf(errors); + + String message = errorsCopy.stream() + .map(CtaError::msg) + .reduce("%s; %s"::formatted) + .orElse("Unknown error"); return String.format("Error response from %s: %s", endpoint, message); } From c21bc72ee730298f49ffc27379e605bc505e794e Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sun, 18 Jan 2026 12:03:37 -0600 Subject: [PATCH 38/53] Patterns API --- src/main/java/com/cta4j/bus/api/BusApi.java | 1 + .../java/com/cta4j/bus/api/PatternsApi.java | 4 - .../com/cta4j/bus/api/impl/BusApiImpl.java | 7 +- .../cta4j/bus/api/pattern/PatternsApi.java | 25 +++ .../pattern}/external/CtaPattern.java | 31 +++- .../{ => api/pattern}/external/CtaPoint.java | 21 ++- .../bus/api/pattern/impl/PatternsApiImpl.java | 146 ++++++++++++++++++ .../pattern}/mapper/RoutePatternMapper.java | 12 +- .../{ => api/pattern}/model/PatternPoint.java | 21 ++- .../pattern}/model/PatternPointType.java | 2 +- .../bus/api/pattern/model/RoutePattern.java | 69 +++++++++ .../java/com/cta4j/bus/client/BusClient.java | 39 ----- .../bus/client/internal/BusClientImpl.java | 92 ----------- .../mapper/util/CtaBusMappingQualifiers.java | 2 +- .../com/cta4j/bus/model/RoutePattern.java | 40 ----- 15 files changed, 318 insertions(+), 194 deletions(-) delete mode 100644 src/main/java/com/cta4j/bus/api/PatternsApi.java create mode 100644 src/main/java/com/cta4j/bus/api/pattern/PatternsApi.java rename src/main/java/com/cta4j/bus/{ => api/pattern}/external/CtaPattern.java (53%) rename src/main/java/com/cta4j/bus/{ => api/pattern}/external/CtaPoint.java (56%) create mode 100644 src/main/java/com/cta4j/bus/api/pattern/impl/PatternsApiImpl.java rename src/main/java/com/cta4j/bus/{ => api/pattern}/mapper/RoutePatternMapper.java (80%) rename src/main/java/com/cta4j/bus/{ => api/pattern}/model/PatternPoint.java (55%) rename src/main/java/com/cta4j/bus/{ => api/pattern}/model/PatternPointType.java (58%) create mode 100644 src/main/java/com/cta4j/bus/api/pattern/model/RoutePattern.java delete mode 100644 src/main/java/com/cta4j/bus/model/RoutePattern.java diff --git a/src/main/java/com/cta4j/bus/api/BusApi.java b/src/main/java/com/cta4j/bus/api/BusApi.java index 17d491f..a9d36b3 100644 --- a/src/main/java/com/cta4j/bus/api/BusApi.java +++ b/src/main/java/com/cta4j/bus/api/BusApi.java @@ -2,6 +2,7 @@ import com.cta4j.bus.api.direction.DirectionsApi; import com.cta4j.bus.api.impl.BusApiImpl; +import com.cta4j.bus.api.pattern.PatternsApi; import com.cta4j.bus.api.route.RoutesApi; import com.cta4j.bus.api.stop.StopsApi; import com.cta4j.bus.api.vehicle.VehiclesApi; diff --git a/src/main/java/com/cta4j/bus/api/PatternsApi.java b/src/main/java/com/cta4j/bus/api/PatternsApi.java deleted file mode 100644 index 02e75c1..0000000 --- a/src/main/java/com/cta4j/bus/api/PatternsApi.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.cta4j.bus.api; - -public interface PatternsApi { -} diff --git a/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java b/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java index 29996b9..34c3152 100644 --- a/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java @@ -5,9 +5,10 @@ import com.cta4j.bus.api.DetoursApi; import com.cta4j.bus.api.direction.DirectionsApi; import com.cta4j.bus.api.LocalesApi; -import com.cta4j.bus.api.PatternsApi; +import com.cta4j.bus.api.pattern.PatternsApi; import com.cta4j.bus.api.PredictionsApi; import com.cta4j.bus.api.direction.impl.DirectionsApiImpl; +import com.cta4j.bus.api.pattern.impl.PatternsApiImpl; import com.cta4j.bus.api.route.RoutesApi; import com.cta4j.bus.api.stop.StopsApi; import com.cta4j.bus.api.route.impl.RoutesApiImpl; @@ -41,6 +42,7 @@ public final class BusApiImpl implements BusApi { private final RoutesApi routesApi; private final DirectionsApi directionsApi; private final StopsApi stopsApi; + private final PatternsApi patternsApi; public BusApiImpl( @Nullable String host, @@ -61,6 +63,7 @@ public BusApiImpl( this.routesApi = new RoutesApiImpl(this.host, this.apiKey, this.objectMapper); this.directionsApi = new DirectionsApiImpl(this.host, this.apiKey, this.objectMapper); this.stopsApi = new StopsApiImpl(this.host, this.apiKey, this.objectMapper); + this.patternsApi = new PatternsApiImpl(this.host, this.apiKey, this.objectMapper); } @Override @@ -137,7 +140,7 @@ public StopsApi stops() { @Override public PatternsApi patterns() { - return null; + return this.patternsApi; } @Override diff --git a/src/main/java/com/cta4j/bus/api/pattern/PatternsApi.java b/src/main/java/com/cta4j/bus/api/pattern/PatternsApi.java new file mode 100644 index 0000000..4ce464e --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/pattern/PatternsApi.java @@ -0,0 +1,25 @@ +package com.cta4j.bus.api.pattern; + +import com.cta4j.bus.api.pattern.model.RoutePattern; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.util.Collection; +import java.util.List; + +@NullMarked +public interface PatternsApi { + List findByIds(@Nullable Collection<@Nullable String> patternIds); + + default List findById(@Nullable String patternId) { + if (patternId == null) { + throw new IllegalArgumentException("patternId must not be null"); + } + + List ids = List.of(patternId); + + return this.findByIds(ids); + } + + List findByRouteId(@Nullable String routeId); +} diff --git a/src/main/java/com/cta4j/bus/external/CtaPattern.java b/src/main/java/com/cta4j/bus/api/pattern/external/CtaPattern.java similarity index 53% rename from src/main/java/com/cta4j/bus/external/CtaPattern.java rename to src/main/java/com/cta4j/bus/api/pattern/external/CtaPattern.java index c233cb0..fab89a4 100644 --- a/src/main/java/com/cta4j/bus/external/CtaPattern.java +++ b/src/main/java/com/cta4j/bus/api/pattern/external/CtaPattern.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.external; +package com.cta4j.bus.api.pattern.external; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.jetbrains.annotations.ApiStatus; @@ -9,7 +9,6 @@ @NullMarked @ApiStatus.Internal -@SuppressWarnings("ConstantConditions") @JsonIgnoreProperties(ignoreUnknown = true) public record CtaPattern( int pid, @@ -26,7 +25,14 @@ public record CtaPattern( @Nullable List dtrpt ) { - public CtaPattern { + public CtaPattern( + int pid, + int ln, + @Nullable String rtdir, + @Nullable List<@Nullable CtaPoint> pt, + @Nullable String dtrid, + @Nullable List<@Nullable CtaPoint> dtrpt + ) { if (rtdir == null) { throw new IllegalArgumentException("rtdir must not be null"); } @@ -40,5 +46,24 @@ public record CtaPattern( throw new IllegalArgumentException("pt must not contain null elements"); } } + + List dtrptCopy = null; + + if (dtrpt != null) { + for (CtaPoint point : dtrpt) { + if (point == null) { + throw new IllegalArgumentException("dtrpt must not contain null elements"); + } + } + + dtrptCopy = List.copyOf(dtrpt); + } + + this.pid = pid; + this.ln = ln; + this.rtdir = rtdir; + this.pt = List.copyOf(pt); + this.dtrid = dtrid; + this.dtrpt = dtrptCopy; } } diff --git a/src/main/java/com/cta4j/bus/external/CtaPoint.java b/src/main/java/com/cta4j/bus/api/pattern/external/CtaPoint.java similarity index 56% rename from src/main/java/com/cta4j/bus/external/CtaPoint.java rename to src/main/java/com/cta4j/bus/api/pattern/external/CtaPoint.java index a563c30..8a55f02 100644 --- a/src/main/java/com/cta4j/bus/external/CtaPoint.java +++ b/src/main/java/com/cta4j/bus/api/pattern/external/CtaPoint.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.external; +package com.cta4j.bus.api.pattern.external; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.jetbrains.annotations.ApiStatus; @@ -7,7 +7,6 @@ @NullMarked @ApiStatus.Internal -@SuppressWarnings("ConstantConditions") @JsonIgnoreProperties(ignoreUnknown = true) public record CtaPoint( int seq, @@ -27,9 +26,25 @@ public record CtaPoint( double lon ) { - public CtaPoint { + public CtaPoint( + int seq, + @Nullable String typ, + @Nullable String stpid, + @Nullable String stpnm, + @Nullable Float pdist, + double lat, + double lon + ) { if (typ == null) { throw new IllegalArgumentException("typ must not be null"); } + + this.seq = seq; + this.typ = typ; + this.stpid = stpid; + this.stpnm = stpnm; + this.pdist = pdist; + this.lat = lat; + this.lon = lon; } } diff --git a/src/main/java/com/cta4j/bus/api/pattern/impl/PatternsApiImpl.java b/src/main/java/com/cta4j/bus/api/pattern/impl/PatternsApiImpl.java new file mode 100644 index 0000000..14fb4a9 --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/pattern/impl/PatternsApiImpl.java @@ -0,0 +1,146 @@ +package com.cta4j.bus.api.pattern.impl; + +import com.cta4j.bus.api.ApiUtils; +import com.cta4j.bus.api.pattern.PatternsApi; +import com.cta4j.bus.api.pattern.external.CtaPattern; +import com.cta4j.bus.api.pattern.mapper.RoutePatternMapper; +import com.cta4j.bus.api.pattern.model.RoutePattern; +import com.cta4j.bus.external.CtaBustimeResponse; +import com.cta4j.bus.external.CtaError; +import com.cta4j.bus.external.CtaResponse; +import com.cta4j.exception.Cta4jException; +import com.cta4j.util.HttpUtils; +import org.apache.hc.core5.net.URIBuilder; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +import org.mapstruct.factory.Mappers; +import tools.jackson.core.JacksonException; +import tools.jackson.core.type.TypeReference; +import tools.jackson.databind.ObjectMapper; + +import java.util.Collection; +import java.util.List; + +@NullMarked +@ApiStatus.Internal +public final class PatternsApiImpl implements PatternsApi { + private static final String PATTERNS_ENDPOINT = String.format("%s/getpatterns", ApiUtils.API_PREFIX); + private static final int MAX_PATTERN_IDS_PER_REQUEST = 10; + + private final String host; + private final String apiKey; + private final ObjectMapper objectMapper; + private final RoutePatternMapper routePatternMapper; + + public PatternsApiImpl( + @Nullable String host, + @Nullable String apiKey, + @Nullable ObjectMapper objectMapper + ) { + if (host == null) { + throw new IllegalArgumentException("host must not be null"); + } + + if (apiKey == null) { + throw new IllegalArgumentException("apiKey must not be null"); + } + + if (objectMapper == null) { + throw new IllegalArgumentException("objectMapper must not be null"); + } + + this.host = host; + this.apiKey = apiKey; + this.objectMapper = objectMapper; + this.routePatternMapper = Mappers.getMapper(RoutePatternMapper.class); + } + + @Override + public List findByIds(@Nullable Collection<@Nullable String> patternIds) { + if (patternIds == null) { + throw new IllegalArgumentException("patternIds must not be null"); + } + + for (String patternId : patternIds) { + if (patternId == null) { + throw new IllegalArgumentException("patternIds must not contain null elements"); + } + } + + if (patternIds.size() > MAX_PATTERN_IDS_PER_REQUEST) { + String message = String.format( + "A maximum of %d pattern IDs can be requested at once, but %d were provided", + MAX_PATTERN_IDS_PER_REQUEST, + patternIds.size() + ); + + throw new IllegalArgumentException(message); + } + + String patternIdsString = String.join(",", patternIds); + + String url = new URIBuilder() + .setScheme(ApiUtils.SCHEME) + .setHost(this.host) + .setPath(PATTERNS_ENDPOINT) + .addParameter("pid", patternIdsString) + .addParameter("key", this.apiKey) + .addParameter("format", "json") + .toString(); + + return this.makeRequest(url); + } + + @Override + public List findByRouteId(@Nullable String routeId) { + if (routeId == null) { + throw new IllegalArgumentException("routeId must not be null"); + } + + String url = new URIBuilder() + .setScheme(ApiUtils.SCHEME) + .setHost(this.host) + .setPath(PATTERNS_ENDPOINT) + .addParameter("rt", routeId) + .addParameter("key", this.apiKey) + .addParameter("format", "json") + .toString(); + + return this.makeRequest(url); + } + + private List makeRequest(String url) { + String response = HttpUtils.get(url); + + TypeReference>> typeReference = new TypeReference<>() {}; + CtaResponse> patternsResponse; + + try { + patternsResponse = this.objectMapper.readValue(response, typeReference); + } catch (JacksonException e) { + String message = String.format("Failed to parse response from %s", PATTERNS_ENDPOINT); + + throw new Cta4jException(message, e); + } + + CtaBustimeResponse> bustimeResponse = patternsResponse.bustimeResponse(); + + List errors = bustimeResponse.error(); + List patterns = bustimeResponse.data(); + + if ((errors != null) && !errors.isEmpty()) { + String message = ApiUtils.buildErrorMessage(PATTERNS_ENDPOINT, errors); + + throw new Cta4jException(message); + } + + if ((patterns == null) || patterns.isEmpty()) { + return List.of(); + } + + return patterns.stream() + .map(this.routePatternMapper::toDomain) + .toList(); + } +} diff --git a/src/main/java/com/cta4j/bus/mapper/RoutePatternMapper.java b/src/main/java/com/cta4j/bus/api/pattern/mapper/RoutePatternMapper.java similarity index 80% rename from src/main/java/com/cta4j/bus/mapper/RoutePatternMapper.java rename to src/main/java/com/cta4j/bus/api/pattern/mapper/RoutePatternMapper.java index d1febdb..3439c9c 100644 --- a/src/main/java/com/cta4j/bus/mapper/RoutePatternMapper.java +++ b/src/main/java/com/cta4j/bus/api/pattern/mapper/RoutePatternMapper.java @@ -1,16 +1,16 @@ -package com.cta4j.bus.mapper; +package com.cta4j.bus.api.pattern.mapper; -import com.cta4j.bus.external.CtaPattern; -import com.cta4j.bus.external.CtaPoint; +import com.cta4j.bus.api.pattern.external.CtaPattern; +import com.cta4j.bus.api.pattern.external.CtaPoint; import com.cta4j.bus.mapper.util.CtaBusMappingQualifiers; -import com.cta4j.bus.model.PatternPoint; -import com.cta4j.bus.model.RoutePattern; +import com.cta4j.bus.api.pattern.model.PatternPoint; +import com.cta4j.bus.api.pattern.model.RoutePattern; import org.jetbrains.annotations.ApiStatus; import org.mapstruct.Mapper; import org.mapstruct.Mapping; -@ApiStatus.Internal @Mapper(uses = CtaBusMappingQualifiers.class) +@ApiStatus.Internal public interface RoutePatternMapper { @Mapping(source = "pid", target = "patternId") @Mapping(source = "ln", target = "patternCount") diff --git a/src/main/java/com/cta4j/bus/model/PatternPoint.java b/src/main/java/com/cta4j/bus/api/pattern/model/PatternPoint.java similarity index 55% rename from src/main/java/com/cta4j/bus/model/PatternPoint.java rename to src/main/java/com/cta4j/bus/api/pattern/model/PatternPoint.java index 3ffa75b..edf0224 100644 --- a/src/main/java/com/cta4j/bus/model/PatternPoint.java +++ b/src/main/java/com/cta4j/bus/api/pattern/model/PatternPoint.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.model; +package com.cta4j.bus.api.pattern.model; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -6,7 +6,6 @@ import java.math.BigDecimal; @NullMarked -@SuppressWarnings("ConstantConditions") public record PatternPoint( int sequence, @@ -25,7 +24,15 @@ public record PatternPoint( BigDecimal longitude ) { - public PatternPoint { + public PatternPoint( + int sequence, + @Nullable PatternPointType type, + @Nullable String stopId, + @Nullable String stopName, + @Nullable BigDecimal distanceToPatternPoint, + @Nullable BigDecimal latitude, + @Nullable BigDecimal longitude + ) { if (type == null) { throw new IllegalArgumentException("type must not be null"); } @@ -37,5 +44,13 @@ public record PatternPoint( if (longitude == null) { throw new IllegalArgumentException("longitude must not be null"); } + + this.sequence = sequence; + this.type = type; + this.stopId = stopId; + this.stopName = stopName; + this.distanceToPatternPoint = distanceToPatternPoint; + this.latitude = latitude; + this.longitude = longitude; } } diff --git a/src/main/java/com/cta4j/bus/model/PatternPointType.java b/src/main/java/com/cta4j/bus/api/pattern/model/PatternPointType.java similarity index 58% rename from src/main/java/com/cta4j/bus/model/PatternPointType.java rename to src/main/java/com/cta4j/bus/api/pattern/model/PatternPointType.java index f2bf938..3a28efa 100644 --- a/src/main/java/com/cta4j/bus/model/PatternPointType.java +++ b/src/main/java/com/cta4j/bus/api/pattern/model/PatternPointType.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.model; +package com.cta4j.bus.api.pattern.model; public enum PatternPointType { STOP, diff --git a/src/main/java/com/cta4j/bus/api/pattern/model/RoutePattern.java b/src/main/java/com/cta4j/bus/api/pattern/model/RoutePattern.java new file mode 100644 index 0000000..0fc1883 --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/pattern/model/RoutePattern.java @@ -0,0 +1,69 @@ +package com.cta4j.bus.api.pattern.model; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.util.List; + +@NullMarked +public record RoutePattern( + String patternId, + + int patternCount, + + String direction, + + List points, + + @Nullable + String detourId, + + @Nullable + List detourPoints +) { + public RoutePattern( + @Nullable String patternId, + int patternCount, + @Nullable String direction, + @Nullable List<@Nullable PatternPoint> points, + @Nullable String detourId, + @Nullable List<@Nullable PatternPoint> detourPoints + ) { + if (patternId == null) { + throw new IllegalArgumentException("patternId must not be null"); + } + + if (direction == null) { + throw new IllegalArgumentException("direction must not be null"); + } + + if (points == null) { + throw new IllegalArgumentException("points must not be null"); + } + + for (PatternPoint point : points) { + if (point == null) { + throw new IllegalArgumentException("points must not contain null elements"); + } + } + + List detourPointsCopy = null; + + if (detourPoints != null) { + for (PatternPoint detourPoint : detourPoints) { + if (detourPoint == null) { + throw new IllegalArgumentException("detourPoints must not contain null elements"); + } + } + + detourPointsCopy = List.copyOf(detourPoints); + } + + this.patternId = patternId; + this.patternCount = patternCount; + this.direction = direction; + this.points = List.copyOf(points); + this.detourId = detourId; + this.detourPoints = detourPointsCopy; + } +} diff --git a/src/main/java/com/cta4j/bus/client/BusClient.java b/src/main/java/com/cta4j/bus/client/BusClient.java index db720ce..5dbdeca 100644 --- a/src/main/java/com/cta4j/bus/client/BusClient.java +++ b/src/main/java/com/cta4j/bus/client/BusClient.java @@ -3,7 +3,6 @@ import com.cta4j.bus.client.internal.BusClientImpl; import com.cta4j.bus.model.Arrival; import com.cta4j.bus.model.Detour; -import com.cta4j.bus.model.RoutePattern; import com.cta4j.exception.Cta4jException; import org.jspecify.annotations.NullMarked; @@ -15,44 +14,6 @@ @NullMarked @SuppressWarnings("ConstantConditions") public interface BusClient { - /** - * Finds route patterns for the specified pattern IDs. - * - * @param patternIds an {@link Iterable} of route pattern IDs - * @return a {@link List} of route patterns corresponding to the specified pattern IDs - * @throws IllegalArgumentException if the specified pattern IDs are {@code null} - * @throws Cta4jException if an error occurs while fetching the data - */ - List findPatternsByPatternId(Iterable patternIds); - - /** - * Finds route patterns for the specified pattern ID. - * - * @param patternId the ID of the route pattern - * @return a {@link List} of route patterns corresponding to the specified pattern ID - * @throws IllegalArgumentException if the specified pattern ID is {@code null} - * @throws Cta4jException if an error occurs while fetching the data - */ - default List findPatternsByPatternId(String patternId) { - if (patternId == null) { - throw new IllegalArgumentException("patternId must not be null"); - } - - List ids = List.of(patternId); - - return this.findPatternsByPatternId(ids); - } - - /** - * Finds route patterns for the specified route ID. - * - * @param routeId the ID of the bus route - * @return a {@link List} of route patterns for the specified bus route - * @throws IllegalArgumentException if the specified bus route is {@code null} - * @throws Cta4jException if an error occurs while fetching the data - */ - List findPatternsByRouteId(String routeId); - /** * Finds arrivals for the specified route ID and stop ID. * diff --git a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java index 6f3d789..603e28f 100644 --- a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java +++ b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java @@ -3,17 +3,12 @@ import com.cta4j.bus.client.BusClient; import com.cta4j.bus.external.CtaBustimeResponse; import com.cta4j.bus.external.CtaError; -import com.cta4j.bus.external.CtaPattern; import com.cta4j.bus.external.CtaResponse; import com.cta4j.bus.external.CtaPrediction; import com.cta4j.bus.mapper.ArrivalMapper; -import com.cta4j.bus.api.vehicle.mapper.VehicleMapper; import com.cta4j.bus.mapper.DetourMapper; -import com.cta4j.bus.mapper.RoutePatternMapper; -import com.cta4j.bus.api.stop.mapper.StopMapper; import com.cta4j.bus.model.Arrival; import com.cta4j.bus.model.Detour; -import com.cta4j.bus.model.RoutePattern; import com.cta4j.exception.Cta4jException; import com.cta4j.bus.external.CtaDetour; import com.cta4j.util.HttpUtils; @@ -39,7 +34,6 @@ public final class BusClientImpl implements BusClient { private static final String SCHEME = "https"; private static final String DEFAULT_HOST = "ctabustracker.com"; private static final String API_PREFIX = "/bustime/api/v3"; - private static final String PATTERNS_ENDPOINT = String.format("%s/getpatterns", API_PREFIX); private static final String PREDICTIONS_ENDPOINT = String.format("%s/getpredictions", API_PREFIX); private static final String DETOURS_ENDPOINT = String.format("%s/getdetours", API_PREFIX); @@ -47,7 +41,6 @@ public final class BusClientImpl implements BusClient { private final String apiKey; private final ObjectMapper objectMapper; private final ArrivalMapper arrivalMapper; - private final RoutePatternMapper routePatternMapper; private final DetourMapper detourMapper; private BusClientImpl(String host, String apiKey) { @@ -63,54 +56,9 @@ private BusClientImpl(String host, String apiKey) { this.apiKey = apiKey; this.objectMapper = new ObjectMapper(); this.arrivalMapper = Mappers.getMapper(ArrivalMapper.class); - this.routePatternMapper = Mappers.getMapper(RoutePatternMapper.class); this.detourMapper = Mappers.getMapper(DetourMapper.class); } - @Override - public List findPatternsByPatternId(Iterable patternIds) { - if (patternIds == null) { - throw new IllegalArgumentException("patternIds must not be null"); - } - - for (String patternId : patternIds) { - if (patternId == null) { - throw new IllegalArgumentException("patternIds must not contain null elements"); - } - } - - String patternIdsString = String.join(",", patternIds); - - String url = new URIBuilder() - .setScheme(SCHEME) - .setHost(this.host) - .setPath(PATTERNS_ENDPOINT) - .addParameter("pid", patternIdsString) - .addParameter("key", this.apiKey) - .addParameter("format", "json") - .toString(); - - return this.getPatterns(url); - } - - @Override - public List findPatternsByRouteId(String routeId) { - if (routeId == null) { - throw new IllegalArgumentException("routeId must not be null"); - } - - String url = new URIBuilder() - .setScheme(SCHEME) - .setHost(this.host) - .setPath(PATTERNS_ENDPOINT) - .addParameter("rt", routeId) - .addParameter("key", this.apiKey) - .addParameter("format", "json") - .toString(); - - return this.getPatterns(url); - } - @Override public List findArrivalsByRouteIdAndStopId(String routeId, String stopId) { if (routeId == null) { @@ -223,46 +171,6 @@ private String buildErrorMessage(String endpoint, List errors) { return String.format("Error response from %s: %s", endpoint, message); } - private List getPatterns(String url) { - String response = HttpUtils.get(url); - - TypeReference>> typeReference = new TypeReference<>() {}; - CtaResponse> patternsResponse; - - try { - patternsResponse = this.objectMapper.readValue(response, typeReference); - } catch (JacksonException e) { - String message = String.format("Failed to parse response from %s", PATTERNS_ENDPOINT); - - throw new Cta4jException(message, e); - } - - CtaBustimeResponse> bustimeResponse = patternsResponse.bustimeResponse(); - - List errors = bustimeResponse.error(); - List patterns = bustimeResponse.data(); - - if ((errors == null) && (patterns == null)) { - log.debug("Pattern bustime response missing both error and data from {}", PATTERNS_ENDPOINT); - - return List.of(); - } - - if ((errors != null) && !errors.isEmpty()) { - String message = this.buildErrorMessage(PATTERNS_ENDPOINT, errors); - - throw new Cta4jException(message); - } - - if ((patterns == null) || patterns.isEmpty()) { - return List.of(); - } - - return patterns.stream() - .map(this.routePatternMapper::toDomain) - .toList(); - } - private List getArrivals(String url) { String response = HttpUtils.get(url); diff --git a/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java b/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java index e4f7c78..83fbb79 100644 --- a/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java +++ b/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java @@ -3,7 +3,7 @@ import com.cta4j.bus.model.DynamicAction; import com.cta4j.bus.model.FlagStop; import com.cta4j.bus.model.PassengerLoad; -import com.cta4j.bus.model.PatternPointType; +import com.cta4j.bus.api.pattern.model.PatternPointType; import com.cta4j.bus.model.PredictionType; import com.cta4j.bus.model.TransitMode; import org.jetbrains.annotations.ApiStatus; diff --git a/src/main/java/com/cta4j/bus/model/RoutePattern.java b/src/main/java/com/cta4j/bus/model/RoutePattern.java deleted file mode 100644 index 5703da4..0000000 --- a/src/main/java/com/cta4j/bus/model/RoutePattern.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.cta4j.bus.model; - -import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; - -import java.util.List; - -@NullMarked -@SuppressWarnings("ConstantConditions") -public record RoutePattern( - String patternId, - - int patternCount, - - String direction, - - List points, - - @Nullable - String detourId, - - @Nullable - List detourPoints -) { - public RoutePattern { - if (direction == null) { - throw new IllegalArgumentException("direction must not be null"); - } - - if (points == null) { - throw new IllegalArgumentException("points must not be null"); - } - - for (PatternPoint point : points) { - if (point == null) { - throw new IllegalArgumentException("points must not contain null elements"); - } - } - } -} From 73c1d1de30973bf3bdb48071cffa658235fccdb6 Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sun, 18 Jan 2026 14:14:01 -0600 Subject: [PATCH 39/53] Patterns API --- src/main/java/com/cta4j/bus/api/ApiUtils.java | 29 ++--- src/main/java/com/cta4j/bus/api/BusApi.java | 5 +- .../com/cta4j/bus/api/PredictionsApi.java | 4 - .../bus/api/direction/DirectionsApi.java | 3 +- .../direction}/external/CtaDirection.java | 14 +- .../api/direction/impl/DirectionsApiImpl.java | 30 ++--- .../com/cta4j/bus/api/impl/BusApiImpl.java | 39 +++--- .../cta4j/bus/api/pattern/PatternsApi.java | 12 +- .../bus/api/pattern/external/CtaPattern.java | 42 ++---- .../bus/api/pattern/external/CtaPoint.java | 24 +--- .../bus/api/pattern/impl/PatternsApiImpl.java | 40 ++---- .../bus/api/pattern/model/PatternPoint.java | 33 +---- .../bus/api/pattern/model/RoutePattern.java | 47 ++----- .../bus/api/prediction/PredictionsApi.java | 41 ++++++ .../prediction/external/CtaPrediction.java | 81 ++++++++++++ .../prediction/mapper/PredictionMapper.java} | 12 +- .../prediction}/model/DynamicAction.java | 2 +- .../{ => api/prediction}/model/FlagStop.java | 2 +- .../prediction}/model/PassengerLoad.java | 2 +- .../bus/api/prediction/model/Prediction.java | 60 +++++++++ .../prediction/model/PredictionMetadata.java | 49 +++++++ .../prediction}/model/PredictionType.java | 2 +- .../query/StopsPredictionsQuery.java | 89 +++++++++++++ .../query/VehiclesPredictionsQuery.java | 66 ++++++++++ .../bus/api/route/external/CtaRoute.java | 35 +---- .../bus/api/route/impl/RoutesApiImpl.java | 22 +--- .../com/cta4j/bus/api/route/model/Route.java | 35 +---- .../java/com/cta4j/bus/api/stop/StopsApi.java | 12 +- .../cta4j/bus/api/stop/external/CtaStop.java | 50 ++------ .../cta4j/bus/api/stop/impl/StopsApiImpl.java | 45 ++----- .../com/cta4j/bus/api/stop/model/Stop.java | 60 ++------- .../cta4j/bus/api/vehicle/VehiclesApi.java | 18 +-- .../bus/api/vehicle/external/CtaVehicle.java | 99 ++------------ .../bus/api/vehicle/impl/VehiclesApiImpl.java | 50 +++----- .../{ => api/vehicle}/model/TransitMode.java | 2 +- .../cta4j/bus/api/vehicle/model/Vehicle.java | 43 ++----- .../api/vehicle/model/VehicleCoordinates.java | 35 ++--- .../api/vehicle/model/VehicleMetadata.java | 77 ++--------- .../java/com/cta4j/bus/client/BusClient.java | 22 ---- .../bus/client/internal/BusClientImpl.java | 90 +------------ .../com/cta4j/bus/external/CtaPrediction.java | 121 ------------------ .../mapper/util/CtaBusMappingQualifiers.java | 10 +- .../java/com/cta4j/bus/model/Arrival.java | 88 ------------- .../com/cta4j/bus/model/ArrivalMetadata.java | 64 --------- 44 files changed, 606 insertions(+), 1100 deletions(-) delete mode 100644 src/main/java/com/cta4j/bus/api/PredictionsApi.java rename src/main/java/com/cta4j/bus/{ => api/direction}/external/CtaDirection.java (52%) create mode 100644 src/main/java/com/cta4j/bus/api/prediction/PredictionsApi.java create mode 100644 src/main/java/com/cta4j/bus/api/prediction/external/CtaPrediction.java rename src/main/java/com/cta4j/bus/{mapper/ArrivalMapper.java => api/prediction/mapper/PredictionMapper.java} (88%) rename src/main/java/com/cta4j/bus/{ => api/prediction}/model/DynamicAction.java (92%) rename src/main/java/com/cta4j/bus/{ => api/prediction}/model/FlagStop.java (85%) rename src/main/java/com/cta4j/bus/{ => api/prediction}/model/PassengerLoad.java (65%) create mode 100644 src/main/java/com/cta4j/bus/api/prediction/model/Prediction.java create mode 100644 src/main/java/com/cta4j/bus/api/prediction/model/PredictionMetadata.java rename src/main/java/com/cta4j/bus/{ => api/prediction}/model/PredictionType.java (57%) create mode 100644 src/main/java/com/cta4j/bus/api/prediction/query/StopsPredictionsQuery.java create mode 100644 src/main/java/com/cta4j/bus/api/prediction/query/VehiclesPredictionsQuery.java rename src/main/java/com/cta4j/bus/{ => api/vehicle}/model/TransitMode.java (86%) delete mode 100644 src/main/java/com/cta4j/bus/external/CtaPrediction.java delete mode 100644 src/main/java/com/cta4j/bus/model/Arrival.java delete mode 100644 src/main/java/com/cta4j/bus/model/ArrivalMetadata.java diff --git a/src/main/java/com/cta4j/bus/api/ApiUtils.java b/src/main/java/com/cta4j/bus/api/ApiUtils.java index cf9736e..754f74a 100644 --- a/src/main/java/com/cta4j/bus/api/ApiUtils.java +++ b/src/main/java/com/cta4j/bus/api/ApiUtils.java @@ -3,9 +3,9 @@ import com.cta4j.bus.external.CtaError; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; import java.util.List; +import java.util.Objects; @NullMarked @ApiStatus.Internal @@ -18,27 +18,16 @@ private ApiUtils() { throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); } - public static String buildErrorMessage(@Nullable String endpoint, @Nullable List<@Nullable CtaError> errors) { - if (endpoint == null) { - throw new IllegalArgumentException("endpoint must not be null"); - } + public static String buildErrorMessage(String endpoint, List errors) { + Objects.requireNonNull(endpoint); + Objects.requireNonNull(errors); - if (errors == null) { - throw new IllegalArgumentException("errors must not be null"); - } + errors.forEach(Objects::requireNonNull); - for (CtaError error : errors) { - if (error == null) { - throw new IllegalArgumentException("errors must not contain null elements"); - } - } - - List errorsCopy = List.copyOf(errors); - - String message = errorsCopy.stream() - .map(CtaError::msg) - .reduce("%s; %s"::formatted) - .orElse("Unknown error"); + String message = errors.stream() + .map(CtaError::msg) + .reduce("%s; %s"::formatted) + .orElse("Unknown error"); return String.format("Error response from %s: %s", endpoint, message); } diff --git a/src/main/java/com/cta4j/bus/api/BusApi.java b/src/main/java/com/cta4j/bus/api/BusApi.java index a9d36b3..deef86c 100644 --- a/src/main/java/com/cta4j/bus/api/BusApi.java +++ b/src/main/java/com/cta4j/bus/api/BusApi.java @@ -3,6 +3,7 @@ import com.cta4j.bus.api.direction.DirectionsApi; import com.cta4j.bus.api.impl.BusApiImpl; import com.cta4j.bus.api.pattern.PatternsApi; +import com.cta4j.bus.api.prediction.PredictionsApi; import com.cta4j.bus.api.route.RoutesApi; import com.cta4j.bus.api.stop.StopsApi; import com.cta4j.bus.api.vehicle.VehiclesApi; @@ -41,7 +42,7 @@ interface Builder { * * @param host the host * @return this {@link Builder} for method chaining - * @throws IllegalArgumentException if {@code host} is {@code null} + * @throws NullPointerException if {@code host} is {@code null} */ Builder host(String host); @@ -50,7 +51,7 @@ interface Builder { * * @param apiKey the API key * @return this {@link Builder} for method chaining - * @throws IllegalArgumentException if {@code apiKey} is {@code null} + * @throws NullPointerException if {@code apiKey} is {@code null} */ Builder apiKey(String apiKey); diff --git a/src/main/java/com/cta4j/bus/api/PredictionsApi.java b/src/main/java/com/cta4j/bus/api/PredictionsApi.java deleted file mode 100644 index a7e6738..0000000 --- a/src/main/java/com/cta4j/bus/api/PredictionsApi.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.cta4j.bus.api; - -public interface PredictionsApi { -} diff --git a/src/main/java/com/cta4j/bus/api/direction/DirectionsApi.java b/src/main/java/com/cta4j/bus/api/direction/DirectionsApi.java index d9eeb04..c65bf42 100644 --- a/src/main/java/com/cta4j/bus/api/direction/DirectionsApi.java +++ b/src/main/java/com/cta4j/bus/api/direction/DirectionsApi.java @@ -1,11 +1,10 @@ package com.cta4j.bus.api.direction; import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; import java.util.List; @NullMarked public interface DirectionsApi { - List findByRouteId(@Nullable String routeId); + List findByRouteId(String routeId); } diff --git a/src/main/java/com/cta4j/bus/external/CtaDirection.java b/src/main/java/com/cta4j/bus/api/direction/external/CtaDirection.java similarity index 52% rename from src/main/java/com/cta4j/bus/external/CtaDirection.java rename to src/main/java/com/cta4j/bus/api/direction/external/CtaDirection.java index 05d0729..4dcfd8b 100644 --- a/src/main/java/com/cta4j/bus/external/CtaDirection.java +++ b/src/main/java/com/cta4j/bus/api/direction/external/CtaDirection.java @@ -1,12 +1,13 @@ -package com.cta4j.bus.external; +package com.cta4j.bus.api.direction.external; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; +import java.util.Objects; + @NullMarked @ApiStatus.Internal -@SuppressWarnings("ConstantConditions") @JsonIgnoreProperties(ignoreUnknown = true) public record CtaDirection( String id, @@ -14,12 +15,7 @@ public record CtaDirection( String name ) { public CtaDirection { - if (id == null) { - throw new IllegalArgumentException("id must not be null"); - } - - if (name == null) { - throw new IllegalArgumentException("name must not be null"); - } + Objects.requireNonNull(id); + Objects.requireNonNull(name); } } diff --git a/src/main/java/com/cta4j/bus/api/direction/impl/DirectionsApiImpl.java b/src/main/java/com/cta4j/bus/api/direction/impl/DirectionsApiImpl.java index a8792c7..af19062 100644 --- a/src/main/java/com/cta4j/bus/api/direction/impl/DirectionsApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/direction/impl/DirectionsApiImpl.java @@ -3,19 +3,19 @@ import com.cta4j.bus.api.ApiUtils; import com.cta4j.bus.api.direction.DirectionsApi; import com.cta4j.bus.external.CtaBustimeResponse; -import com.cta4j.bus.external.CtaDirection; +import com.cta4j.bus.api.direction.external.CtaDirection; import com.cta4j.bus.external.CtaError; import com.cta4j.bus.external.CtaResponse; import com.cta4j.exception.Cta4jException; import com.cta4j.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; import tools.jackson.core.JacksonException; import tools.jackson.core.type.TypeReference; import tools.jackson.databind.ObjectMapper; import java.util.List; +import java.util.Objects; @NullMarked public final class DirectionsApiImpl implements DirectionsApi { @@ -26,21 +26,13 @@ public final class DirectionsApiImpl implements DirectionsApi { private final ObjectMapper objectMapper; public DirectionsApiImpl( - @Nullable String host, - @Nullable String apiKey, - @Nullable ObjectMapper objectMapper + String host, + String apiKey, + ObjectMapper objectMapper ) { - if (host == null) { - throw new IllegalArgumentException("host must not be null"); - } - - if (apiKey == null) { - throw new IllegalArgumentException("apiKey must not be null"); - } - - if (objectMapper == null) { - throw new IllegalArgumentException("objectMapper must not be null"); - } + Objects.requireNonNull(host); + Objects.requireNonNull(apiKey); + Objects.requireNonNull(objectMapper); this.host = host; this.apiKey = apiKey; @@ -48,10 +40,8 @@ public DirectionsApiImpl( } @Override - public List findByRouteId(@Nullable String routeId) { - if (routeId == null) { - throw new IllegalArgumentException("routeId must not be null"); - } + public List findByRouteId(String routeId) { + Objects.requireNonNull(routeId); String url = new URIBuilder() .setScheme(ApiUtils.SCHEME) diff --git a/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java b/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java index 34c3152..5b2f630 100644 --- a/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java @@ -6,7 +6,7 @@ import com.cta4j.bus.api.direction.DirectionsApi; import com.cta4j.bus.api.LocalesApi; import com.cta4j.bus.api.pattern.PatternsApi; -import com.cta4j.bus.api.PredictionsApi; +import com.cta4j.bus.api.prediction.PredictionsApi; import com.cta4j.bus.api.direction.impl.DirectionsApiImpl; import com.cta4j.bus.api.pattern.impl.PatternsApiImpl; import com.cta4j.bus.api.route.RoutesApi; @@ -30,6 +30,7 @@ import java.time.Instant; import java.util.List; +import java.util.Objects; @NullMarked public final class BusApiImpl implements BusApi { @@ -45,16 +46,11 @@ public final class BusApiImpl implements BusApi { private final PatternsApi patternsApi; public BusApiImpl( - @Nullable String host, - @Nullable String apiKey + String host, + String apiKey ) { - if (host == null) { - throw new IllegalArgumentException("host must not be null"); - } - - if (apiKey == null) { - throw new IllegalArgumentException("apiKey must not be null"); - } + Objects.requireNonNull(host); + Objects.requireNonNull(apiKey); this.host = host; this.apiKey = apiKey; @@ -159,8 +155,11 @@ public DetoursApi detours() { } public static final class BuilderImpl implements BusApi.Builder { - private @Nullable String host; - private @Nullable String apiKey; + @Nullable + private String host; + + @Nullable + private String apiKey; public BuilderImpl() { this.host = null; @@ -168,23 +167,15 @@ public BuilderImpl() { } @Override - public Builder host(@Nullable String host) { - if (host == null) { - throw new IllegalArgumentException("host must not be null"); - } - - this.host = host; + public Builder host(String host) { + this.host = Objects.requireNonNull(host); return this; } @Override - public Builder apiKey(@Nullable String apiKey) { - if (apiKey == null) { - throw new IllegalArgumentException("apiKey must not be null"); - } - - this.apiKey = apiKey; + public Builder apiKey(String apiKey) { + this.apiKey = Objects.requireNonNull(apiKey); return this; } diff --git a/src/main/java/com/cta4j/bus/api/pattern/PatternsApi.java b/src/main/java/com/cta4j/bus/api/pattern/PatternsApi.java index 4ce464e..b49ad01 100644 --- a/src/main/java/com/cta4j/bus/api/pattern/PatternsApi.java +++ b/src/main/java/com/cta4j/bus/api/pattern/PatternsApi.java @@ -2,24 +2,22 @@ import com.cta4j.bus.api.pattern.model.RoutePattern; import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; import java.util.Collection; import java.util.List; +import java.util.Objects; @NullMarked public interface PatternsApi { - List findByIds(@Nullable Collection<@Nullable String> patternIds); + List findByIds(Collection patternIds); - default List findById(@Nullable String patternId) { - if (patternId == null) { - throw new IllegalArgumentException("patternId must not be null"); - } + default List findById(String patternId) { + Objects.requireNonNull(patternId); List ids = List.of(patternId); return this.findByIds(ids); } - List findByRouteId(@Nullable String routeId); + List findByRouteId(String routeId); } diff --git a/src/main/java/com/cta4j/bus/api/pattern/external/CtaPattern.java b/src/main/java/com/cta4j/bus/api/pattern/external/CtaPattern.java index fab89a4..c7e027c 100644 --- a/src/main/java/com/cta4j/bus/api/pattern/external/CtaPattern.java +++ b/src/main/java/com/cta4j/bus/api/pattern/external/CtaPattern.java @@ -6,6 +6,7 @@ import org.jspecify.annotations.Nullable; import java.util.List; +import java.util.Objects; @NullMarked @ApiStatus.Internal @@ -25,45 +26,18 @@ public record CtaPattern( @Nullable List dtrpt ) { - public CtaPattern( - int pid, - int ln, - @Nullable String rtdir, - @Nullable List<@Nullable CtaPoint> pt, - @Nullable String dtrid, - @Nullable List<@Nullable CtaPoint> dtrpt - ) { - if (rtdir == null) { - throw new IllegalArgumentException("rtdir must not be null"); - } + public CtaPattern { + Objects.requireNonNull(rtdir); + Objects.requireNonNull(pt); - if (pt == null) { - throw new IllegalArgumentException("pt must not be null"); - } + pt.forEach(Objects::requireNonNull); - for (CtaPoint point : pt) { - if (point == null) { - throw new IllegalArgumentException("pt must not contain null elements"); - } - } - - List dtrptCopy = null; + pt = List.copyOf(pt); if (dtrpt != null) { - for (CtaPoint point : dtrpt) { - if (point == null) { - throw new IllegalArgumentException("dtrpt must not contain null elements"); - } - } + dtrpt.forEach(Objects::requireNonNull); - dtrptCopy = List.copyOf(dtrpt); + dtrpt = List.copyOf(dtrpt); } - - this.pid = pid; - this.ln = ln; - this.rtdir = rtdir; - this.pt = List.copyOf(pt); - this.dtrid = dtrid; - this.dtrpt = dtrptCopy; } } diff --git a/src/main/java/com/cta4j/bus/api/pattern/external/CtaPoint.java b/src/main/java/com/cta4j/bus/api/pattern/external/CtaPoint.java index 8a55f02..c49c634 100644 --- a/src/main/java/com/cta4j/bus/api/pattern/external/CtaPoint.java +++ b/src/main/java/com/cta4j/bus/api/pattern/external/CtaPoint.java @@ -5,6 +5,8 @@ import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; +import java.util.Objects; + @NullMarked @ApiStatus.Internal @JsonIgnoreProperties(ignoreUnknown = true) @@ -26,25 +28,7 @@ public record CtaPoint( double lon ) { - public CtaPoint( - int seq, - @Nullable String typ, - @Nullable String stpid, - @Nullable String stpnm, - @Nullable Float pdist, - double lat, - double lon - ) { - if (typ == null) { - throw new IllegalArgumentException("typ must not be null"); - } - - this.seq = seq; - this.typ = typ; - this.stpid = stpid; - this.stpnm = stpnm; - this.pdist = pdist; - this.lat = lat; - this.lon = lon; + public CtaPoint { + Objects.requireNonNull(typ); } } diff --git a/src/main/java/com/cta4j/bus/api/pattern/impl/PatternsApiImpl.java b/src/main/java/com/cta4j/bus/api/pattern/impl/PatternsApiImpl.java index 14fb4a9..e2d7073 100644 --- a/src/main/java/com/cta4j/bus/api/pattern/impl/PatternsApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/pattern/impl/PatternsApiImpl.java @@ -13,7 +13,6 @@ import org.apache.hc.core5.net.URIBuilder; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; import org.mapstruct.factory.Mappers; import tools.jackson.core.JacksonException; import tools.jackson.core.type.TypeReference; @@ -21,6 +20,7 @@ import java.util.Collection; import java.util.List; +import java.util.Objects; @NullMarked @ApiStatus.Internal @@ -34,21 +34,13 @@ public final class PatternsApiImpl implements PatternsApi { private final RoutePatternMapper routePatternMapper; public PatternsApiImpl( - @Nullable String host, - @Nullable String apiKey, - @Nullable ObjectMapper objectMapper + String host, + String apiKey, + ObjectMapper objectMapper ) { - if (host == null) { - throw new IllegalArgumentException("host must not be null"); - } - - if (apiKey == null) { - throw new IllegalArgumentException("apiKey must not be null"); - } - - if (objectMapper == null) { - throw new IllegalArgumentException("objectMapper must not be null"); - } + Objects.requireNonNull(host); + Objects.requireNonNull(apiKey); + Objects.requireNonNull(objectMapper); this.host = host; this.apiKey = apiKey; @@ -57,16 +49,10 @@ public PatternsApiImpl( } @Override - public List findByIds(@Nullable Collection<@Nullable String> patternIds) { - if (patternIds == null) { - throw new IllegalArgumentException("patternIds must not be null"); - } + public List findByIds(Collection patternIds) { + Objects.requireNonNull(patternIds); - for (String patternId : patternIds) { - if (patternId == null) { - throw new IllegalArgumentException("patternIds must not contain null elements"); - } - } + patternIds.forEach(Objects::requireNonNull); if (patternIds.size() > MAX_PATTERN_IDS_PER_REQUEST) { String message = String.format( @@ -93,10 +79,8 @@ public List findByIds(@Nullable Collection<@Nullable String> patte } @Override - public List findByRouteId(@Nullable String routeId) { - if (routeId == null) { - throw new IllegalArgumentException("routeId must not be null"); - } + public List findByRouteId(String routeId) { + Objects.requireNonNull(routeId); String url = new URIBuilder() .setScheme(ApiUtils.SCHEME) diff --git a/src/main/java/com/cta4j/bus/api/pattern/model/PatternPoint.java b/src/main/java/com/cta4j/bus/api/pattern/model/PatternPoint.java index edf0224..0a9244e 100644 --- a/src/main/java/com/cta4j/bus/api/pattern/model/PatternPoint.java +++ b/src/main/java/com/cta4j/bus/api/pattern/model/PatternPoint.java @@ -4,6 +4,7 @@ import org.jspecify.annotations.Nullable; import java.math.BigDecimal; +import java.util.Objects; @NullMarked public record PatternPoint( @@ -24,33 +25,9 @@ public record PatternPoint( BigDecimal longitude ) { - public PatternPoint( - int sequence, - @Nullable PatternPointType type, - @Nullable String stopId, - @Nullable String stopName, - @Nullable BigDecimal distanceToPatternPoint, - @Nullable BigDecimal latitude, - @Nullable BigDecimal longitude - ) { - if (type == null) { - throw new IllegalArgumentException("type must not be null"); - } - - if (latitude == null) { - throw new IllegalArgumentException("latitude must not be null"); - } - - if (longitude == null) { - throw new IllegalArgumentException("longitude must not be null"); - } - - this.sequence = sequence; - this.type = type; - this.stopId = stopId; - this.stopName = stopName; - this.distanceToPatternPoint = distanceToPatternPoint; - this.latitude = latitude; - this.longitude = longitude; + public PatternPoint { + Objects.requireNonNull(type); + Objects.requireNonNull(latitude); + Objects.requireNonNull(longitude); } } diff --git a/src/main/java/com/cta4j/bus/api/pattern/model/RoutePattern.java b/src/main/java/com/cta4j/bus/api/pattern/model/RoutePattern.java index 0fc1883..ed08840 100644 --- a/src/main/java/com/cta4j/bus/api/pattern/model/RoutePattern.java +++ b/src/main/java/com/cta4j/bus/api/pattern/model/RoutePattern.java @@ -4,6 +4,7 @@ import org.jspecify.annotations.Nullable; import java.util.List; +import java.util.Objects; @NullMarked public record RoutePattern( @@ -21,49 +22,19 @@ public record RoutePattern( @Nullable List detourPoints ) { - public RoutePattern( - @Nullable String patternId, - int patternCount, - @Nullable String direction, - @Nullable List<@Nullable PatternPoint> points, - @Nullable String detourId, - @Nullable List<@Nullable PatternPoint> detourPoints - ) { - if (patternId == null) { - throw new IllegalArgumentException("patternId must not be null"); - } + public RoutePattern { + Objects.requireNonNull(patternId); + Objects.requireNonNull(direction); + Objects.requireNonNull(points); - if (direction == null) { - throw new IllegalArgumentException("direction must not be null"); - } + points.forEach(Objects::requireNonNull); - if (points == null) { - throw new IllegalArgumentException("points must not be null"); - } - - for (PatternPoint point : points) { - if (point == null) { - throw new IllegalArgumentException("points must not contain null elements"); - } - } - - List detourPointsCopy = null; + points = List.copyOf(points); if (detourPoints != null) { - for (PatternPoint detourPoint : detourPoints) { - if (detourPoint == null) { - throw new IllegalArgumentException("detourPoints must not contain null elements"); - } - } + detourPoints.forEach(Objects::requireNonNull); - detourPointsCopy = List.copyOf(detourPoints); + detourPoints = List.copyOf(detourPoints); } - - this.patternId = patternId; - this.patternCount = patternCount; - this.direction = direction; - this.points = List.copyOf(points); - this.detourId = detourId; - this.detourPoints = detourPointsCopy; } } diff --git a/src/main/java/com/cta4j/bus/api/prediction/PredictionsApi.java b/src/main/java/com/cta4j/bus/api/prediction/PredictionsApi.java new file mode 100644 index 0000000..ee66711 --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/prediction/PredictionsApi.java @@ -0,0 +1,41 @@ +package com.cta4j.bus.api.prediction; + +import com.cta4j.bus.api.prediction.model.Prediction; +import com.cta4j.bus.api.prediction.query.StopsPredictionsQuery; +import com.cta4j.bus.api.prediction.query.VehiclesPredictionsQuery; +import org.jspecify.annotations.NullMarked; + +import java.util.List; +import java.util.Objects; + +@NullMarked +public interface PredictionsApi { + List findByStopIds(StopsPredictionsQuery query); + + List findByVehicleIds(VehiclesPredictionsQuery query); + + default List findByRouteIdAndStopId(String routeId, String stopId) { + Objects.requireNonNull(routeId); + Objects.requireNonNull(stopId); + + List stopIds = List.of(stopId); + List routeIds = List.of(routeId); + + StopsPredictionsQuery query = StopsPredictionsQuery.builder(stopIds) + .routeIds(routeIds) + .build(); + + return this.findByStopIds(query); + } + + default List findByVehicleId(String vehicleId) { + Objects.requireNonNull(vehicleId); + + List vehicleIds = List.of(vehicleId); + + VehiclesPredictionsQuery query = VehiclesPredictionsQuery.builder(vehicleIds) + .build(); + + return this.findByVehicleIds(query); + } +} diff --git a/src/main/java/com/cta4j/bus/api/prediction/external/CtaPrediction.java b/src/main/java/com/cta4j/bus/api/prediction/external/CtaPrediction.java new file mode 100644 index 0000000..0da78a5 --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/prediction/external/CtaPrediction.java @@ -0,0 +1,81 @@ +package com.cta4j.bus.api.prediction.external; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.util.Objects; + +@NullMarked +@ApiStatus.Internal +@JsonIgnoreProperties(ignoreUnknown = true) +public record CtaPrediction( + String tmstmp, + + String typ, + + String stpid, + + String stpnm, + + int vid, + + int dstp, + + String rt, + + String rtdd, + + String rtdir, + + String des, + + String prdtm, + + @Nullable + Boolean dly, + + int dyn, + + String tablockid, + + String tatripid, + + String origtatripno, + + String zone, + + String psgld, + + @Nullable + Integer gtfsseq, + + @Nullable + String nbus, + + @Nullable + Integer stst, + + @Nullable + String stsd, + + int flagstop +) { + public CtaPrediction { + Objects.requireNonNull(tmstmp); + Objects.requireNonNull(typ); + Objects.requireNonNull(stpid); + Objects.requireNonNull(stpnm); + Objects.requireNonNull(rt); + Objects.requireNonNull(rtdd); + Objects.requireNonNull(rtdir); + Objects.requireNonNull(des); + Objects.requireNonNull(prdtm); + Objects.requireNonNull(tablockid); + Objects.requireNonNull(tatripid); + Objects.requireNonNull(origtatripno); + Objects.requireNonNull(zone); + Objects.requireNonNull(psgld); + } +} diff --git a/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java b/src/main/java/com/cta4j/bus/api/prediction/mapper/PredictionMapper.java similarity index 88% rename from src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java rename to src/main/java/com/cta4j/bus/api/prediction/mapper/PredictionMapper.java index 57ea2e1..6a60817 100644 --- a/src/main/java/com/cta4j/bus/mapper/ArrivalMapper.java +++ b/src/main/java/com/cta4j/bus/api/prediction/mapper/PredictionMapper.java @@ -1,15 +1,15 @@ -package com.cta4j.bus.mapper; +package com.cta4j.bus.api.prediction.mapper; -import com.cta4j.bus.external.CtaPrediction; +import com.cta4j.bus.api.prediction.external.CtaPrediction; import com.cta4j.bus.mapper.util.CtaBusMappingQualifiers; -import com.cta4j.bus.model.Arrival; +import com.cta4j.bus.api.prediction.model.Prediction; import org.jetbrains.annotations.ApiStatus; import org.mapstruct.Mapper; import org.mapstruct.Mapping; -@ApiStatus.Internal @Mapper(uses = CtaBusMappingQualifiers.class) -public interface ArrivalMapper { +@ApiStatus.Internal +public interface PredictionMapper { @Mapping(source = "typ", target = "predictionType", qualifiedByName = "mapPredictionType") @Mapping(source = "stpid", target = "stopId") @Mapping(source = "stpnm", target = "stopName") @@ -33,5 +33,5 @@ public interface ArrivalMapper { @Mapping(source = "stst", target = "metadata.scheduledStartSeconds") @Mapping(source = "stsd", target = "metadata.scheduledStartDate") @Mapping(source = "flagstop", target = "metadata.flagStop", qualifiedByName = "mapFlagStop") - Arrival toDomain(CtaPrediction prediction); + Prediction toDomain(CtaPrediction prediction); } diff --git a/src/main/java/com/cta4j/bus/model/DynamicAction.java b/src/main/java/com/cta4j/bus/api/prediction/model/DynamicAction.java similarity index 92% rename from src/main/java/com/cta4j/bus/model/DynamicAction.java rename to src/main/java/com/cta4j/bus/api/prediction/model/DynamicAction.java index c46fb4c..509054a 100644 --- a/src/main/java/com/cta4j/bus/model/DynamicAction.java +++ b/src/main/java/com/cta4j/bus/api/prediction/model/DynamicAction.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.model; +package com.cta4j.bus.api.prediction.model; public enum DynamicAction { NONE(0), diff --git a/src/main/java/com/cta4j/bus/model/FlagStop.java b/src/main/java/com/cta4j/bus/api/prediction/model/FlagStop.java similarity index 85% rename from src/main/java/com/cta4j/bus/model/FlagStop.java rename to src/main/java/com/cta4j/bus/api/prediction/model/FlagStop.java index fc038c7..c4cadeb 100644 --- a/src/main/java/com/cta4j/bus/model/FlagStop.java +++ b/src/main/java/com/cta4j/bus/api/prediction/model/FlagStop.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.model; +package com.cta4j.bus.api.prediction.model; public enum FlagStop { UNDEFINED(-1), diff --git a/src/main/java/com/cta4j/bus/model/PassengerLoad.java b/src/main/java/com/cta4j/bus/api/prediction/model/PassengerLoad.java similarity index 65% rename from src/main/java/com/cta4j/bus/model/PassengerLoad.java rename to src/main/java/com/cta4j/bus/api/prediction/model/PassengerLoad.java index 4893a64..c05e3f6 100644 --- a/src/main/java/com/cta4j/bus/model/PassengerLoad.java +++ b/src/main/java/com/cta4j/bus/api/prediction/model/PassengerLoad.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.model; +package com.cta4j.bus.api.prediction.model; public enum PassengerLoad { FULL, diff --git a/src/main/java/com/cta4j/bus/api/prediction/model/Prediction.java b/src/main/java/com/cta4j/bus/api/prediction/model/Prediction.java new file mode 100644 index 0000000..f71bba9 --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/prediction/model/Prediction.java @@ -0,0 +1,60 @@ +package com.cta4j.bus.api.prediction.model; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.math.BigInteger; +import java.time.Duration; +import java.time.Instant; +import java.util.Objects; + +@NullMarked +public record Prediction( + PredictionType predictionType, + + String stopId, + + String stopName, + + String vehicleId, + + BigInteger distanceToStop, + + String route, + + String routeDesignator, + + String routeDirection, + + String destination, + + Instant arrivalTime, + + @Nullable + Boolean delayed, + + PredictionMetadata metadata +) { + public Prediction { + Objects.requireNonNull(predictionType); + Objects.requireNonNull(stopId); + Objects.requireNonNull(stopName); + Objects.requireNonNull(vehicleId); + Objects.requireNonNull(distanceToStop); + Objects.requireNonNull(route); + Objects.requireNonNull(routeDesignator); + Objects.requireNonNull(routeDirection); + Objects.requireNonNull(destination); + Objects.requireNonNull(arrivalTime); + Objects.requireNonNull(metadata); + } + + public long etaMinutes() { + Instant now = Instant.now(); + + long minutes = Duration.between(now, this.arrivalTime) + .toMinutes(); + + return Math.max(minutes, 0L); + } +} diff --git a/src/main/java/com/cta4j/bus/api/prediction/model/PredictionMetadata.java b/src/main/java/com/cta4j/bus/api/prediction/model/PredictionMetadata.java new file mode 100644 index 0000000..0c38a6b --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/prediction/model/PredictionMetadata.java @@ -0,0 +1,49 @@ +package com.cta4j.bus.api.prediction.model; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.time.Instant; +import java.util.Objects; + +@NullMarked +public record PredictionMetadata( + Instant timestamp, + + DynamicAction dynamicAction, + + String blockId, + + String tripId, + + String originalTripNumber, + + String zone, + + PassengerLoad passengerLoad, + + @Nullable + Integer gtfsSequence, + + @Nullable + String nextBus, + + @Nullable + Integer scheduledStartSeconds, + + @Nullable + String scheduledStartDate, + + FlagStop flagStop +) { + public PredictionMetadata { + Objects.requireNonNull(timestamp); + Objects.requireNonNull(dynamicAction); + Objects.requireNonNull(blockId); + Objects.requireNonNull(tripId); + Objects.requireNonNull(originalTripNumber); + Objects.requireNonNull(zone); + Objects.requireNonNull(passengerLoad); + Objects.requireNonNull(flagStop); + } +} diff --git a/src/main/java/com/cta4j/bus/model/PredictionType.java b/src/main/java/com/cta4j/bus/api/prediction/model/PredictionType.java similarity index 57% rename from src/main/java/com/cta4j/bus/model/PredictionType.java rename to src/main/java/com/cta4j/bus/api/prediction/model/PredictionType.java index b99d6df..53d447c 100644 --- a/src/main/java/com/cta4j/bus/model/PredictionType.java +++ b/src/main/java/com/cta4j/bus/api/prediction/model/PredictionType.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.model; +package com.cta4j.bus.api.prediction.model; public enum PredictionType { ARRIVAL, diff --git a/src/main/java/com/cta4j/bus/api/prediction/query/StopsPredictionsQuery.java b/src/main/java/com/cta4j/bus/api/prediction/query/StopsPredictionsQuery.java new file mode 100644 index 0000000..de59b4d --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/prediction/query/StopsPredictionsQuery.java @@ -0,0 +1,89 @@ +package com.cta4j.bus.api.prediction.query; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +@NullMarked +public record StopsPredictionsQuery( + Collection stopIds, + + @Nullable + Collection routeIds, + + @Nullable + Integer maxResults +) { + public StopsPredictionsQuery { + Objects.requireNonNull(stopIds); + + stopIds.forEach(Objects::requireNonNull); + + stopIds = List.copyOf(stopIds); + + if (routeIds != null) { + routeIds.forEach(Objects::requireNonNull); + + routeIds = List.copyOf(routeIds); + } + + if ((maxResults != null) && (maxResults <= 0)) { + throw new IllegalArgumentException("maxResults must be positive"); + } + } + + public static Builder builder(Collection stopIds) { + return new Builder(stopIds); + } + + public static final class Builder { + private final Collection stopIds; + + @Nullable + private Collection routeIds; + + @Nullable + private Integer maxResults; + + public Builder(Collection stopIds) { + Objects.requireNonNull(stopIds); + + stopIds.forEach(Objects::requireNonNull); + + this.stopIds = List.copyOf(stopIds); + } + + public Builder routeIds(Collection routeIds) { + Objects.requireNonNull(routeIds); + + routeIds.forEach(Objects::requireNonNull); + + this.routeIds = List.copyOf(routeIds); + + return this; + } + + public Builder maxResults(Integer maxResults) { + Objects.requireNonNull(maxResults); + + if (maxResults <= 0) { + throw new IllegalArgumentException("maxResults must be positive"); + } + + this.maxResults = maxResults; + + return this; + } + + public StopsPredictionsQuery build() { + return new StopsPredictionsQuery( + this.stopIds, + this.routeIds, + this.maxResults + ); + } + } +} diff --git a/src/main/java/com/cta4j/bus/api/prediction/query/VehiclesPredictionsQuery.java b/src/main/java/com/cta4j/bus/api/prediction/query/VehiclesPredictionsQuery.java new file mode 100644 index 0000000..bb6e28f --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/prediction/query/VehiclesPredictionsQuery.java @@ -0,0 +1,66 @@ +package com.cta4j.bus.api.prediction.query; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +@NullMarked +public record VehiclesPredictionsQuery( + Collection vehicleIds, + + @Nullable + Integer maxResults +) { + public VehiclesPredictionsQuery { + Objects.requireNonNull(vehicleIds); + + vehicleIds.forEach(Objects::requireNonNull); + + vehicleIds = List.copyOf(vehicleIds); + + if ((maxResults != null) && (maxResults <= 0)) { + throw new IllegalArgumentException("maxResults must be positive"); + } + } + + public static Builder builder(Collection stopIds) { + return new Builder(stopIds); + } + + public static final class Builder { + private final Collection vehicleIds; + + @Nullable + private Integer maxResults; + + public Builder(Collection vehicleIds) { + Objects.requireNonNull(vehicleIds); + + vehicleIds.forEach(Objects::requireNonNull); + + this.vehicleIds = List.copyOf(vehicleIds); + } + + public Builder maxResults(Integer maxResults) { + Objects.requireNonNull(maxResults); + + if (maxResults <= 0) { + throw new IllegalArgumentException("maxResults must be positive"); + } + + this.maxResults = maxResults; + + return this; + } + + public VehiclesPredictionsQuery build() { + return new VehiclesPredictionsQuery( + this.vehicleIds, + this.maxResults + ); + } + } +} diff --git a/src/main/java/com/cta4j/bus/api/route/external/CtaRoute.java b/src/main/java/com/cta4j/bus/api/route/external/CtaRoute.java index b8089a1..d9e4bec 100644 --- a/src/main/java/com/cta4j/bus/api/route/external/CtaRoute.java +++ b/src/main/java/com/cta4j/bus/api/route/external/CtaRoute.java @@ -5,6 +5,8 @@ import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; +import java.util.Objects; + @NullMarked @ApiStatus.Internal @JsonIgnoreProperties(ignoreUnknown = true) @@ -20,33 +22,10 @@ public record CtaRoute( @Nullable String rtpidatafeed ) { - public CtaRoute( - @Nullable String rt, - @Nullable String rtnm, - @Nullable String rtclr, - @Nullable String rtdd, - @Nullable String rtpidatafeed - ) { - if (rt == null) { - throw new IllegalArgumentException("rt must not be null"); - } - - if (rtnm == null) { - throw new IllegalArgumentException("rtnm must not be null"); - } - - if (rtclr == null) { - throw new IllegalArgumentException("rtclr must not be null"); - } - - if (rtdd == null) { - throw new IllegalArgumentException("rtdd must not be null"); - } - - this.rt = rt; - this.rtnm = rtnm; - this.rtclr = rtclr; - this.rtdd = rtdd; - this.rtpidatafeed = rtpidatafeed; + public CtaRoute { + Objects.requireNonNull(rt); + Objects.requireNonNull(rtnm); + Objects.requireNonNull(rtclr); + Objects.requireNonNull(rtdd); } } diff --git a/src/main/java/com/cta4j/bus/api/route/impl/RoutesApiImpl.java b/src/main/java/com/cta4j/bus/api/route/impl/RoutesApiImpl.java index 9fc050d..90b2000 100644 --- a/src/main/java/com/cta4j/bus/api/route/impl/RoutesApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/route/impl/RoutesApiImpl.java @@ -13,7 +13,6 @@ import org.apache.hc.core5.net.URIBuilder; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; import org.mapstruct.factory.Mappers; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,6 +21,7 @@ import tools.jackson.databind.ObjectMapper; import java.util.List; +import java.util.Objects; @NullMarked @ApiStatus.Internal @@ -36,21 +36,13 @@ public final class RoutesApiImpl implements RoutesApi { private final RouteMapper routeMapper; public RoutesApiImpl( - @Nullable String host, - @Nullable String apiKey, - @Nullable ObjectMapper objectMapper + String host, + String apiKey, + ObjectMapper objectMapper ) { - if (host == null) { - throw new IllegalArgumentException("host must not be null"); - } - - if (apiKey == null) { - throw new IllegalArgumentException("apiKey must not be null"); - } - - if (objectMapper == null) { - throw new IllegalArgumentException("objectMapper must not be null"); - } + Objects.requireNonNull(host); + Objects.requireNonNull(apiKey); + Objects.requireNonNull(objectMapper); this.host = host; this.apiKey = apiKey; diff --git a/src/main/java/com/cta4j/bus/api/route/model/Route.java b/src/main/java/com/cta4j/bus/api/route/model/Route.java index a3ca068..655b780 100644 --- a/src/main/java/com/cta4j/bus/api/route/model/Route.java +++ b/src/main/java/com/cta4j/bus/api/route/model/Route.java @@ -3,6 +3,8 @@ import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; +import java.util.Objects; + @NullMarked public record Route( String id, @@ -16,33 +18,10 @@ public record Route( @Nullable String dataFeed ) { - public Route( - @Nullable String id, - @Nullable String name, - @Nullable String color, - @Nullable String designator, - @Nullable String dataFeed - ) { - if (id == null) { - throw new IllegalArgumentException("id must not be null"); - } - - if (name == null) { - throw new IllegalArgumentException("name must not be null"); - } - - if (designator == null) { - throw new IllegalArgumentException("designator must not be null"); - } - - if (color == null) { - throw new IllegalArgumentException("color must not be null"); - } - - this.id = id; - this.name = name; - this.color = color; - this.designator = designator; - this.dataFeed = dataFeed; + public Route { + Objects.requireNonNull(id); + Objects.requireNonNull(name); + Objects.requireNonNull(color); + Objects.requireNonNull(designator); } } diff --git a/src/main/java/com/cta4j/bus/api/stop/StopsApi.java b/src/main/java/com/cta4j/bus/api/stop/StopsApi.java index 39ae325..89a6e70 100644 --- a/src/main/java/com/cta4j/bus/api/stop/StopsApi.java +++ b/src/main/java/com/cta4j/bus/api/stop/StopsApi.java @@ -3,22 +3,20 @@ import com.cta4j.bus.api.stop.model.Stop; import com.cta4j.exception.Cta4jException; import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; import java.util.Collection; import java.util.List; +import java.util.Objects; import java.util.Optional; @NullMarked public interface StopsApi { - List findByRouteIdAndDirection(@Nullable String routeId, @Nullable String direction); + List findByRouteIdAndDirection(String routeId, String direction); - List findByIds(@Nullable Collection<@Nullable String> stopIds); + List findByIds(Collection stopIds); - default Optional findById(@Nullable String stopId) { - if (stopId == null) { - throw new IllegalArgumentException("stopId must not be null"); - } + default Optional findById(String stopId) { + Objects.requireNonNull(stopId); List ids = List.of(stopId); diff --git a/src/main/java/com/cta4j/bus/api/stop/external/CtaStop.java b/src/main/java/com/cta4j/bus/api/stop/external/CtaStop.java index db04d0d..6efa702 100644 --- a/src/main/java/com/cta4j/bus/api/stop/external/CtaStop.java +++ b/src/main/java/com/cta4j/bus/api/stop/external/CtaStop.java @@ -6,6 +6,7 @@ import org.jspecify.annotations.Nullable; import java.util.List; +import java.util.Objects; @NullMarked @ApiStatus.Internal @@ -31,55 +32,20 @@ public record CtaStop( @Nullable Boolean ada ) { - public CtaStop( - @Nullable String stpid, - @Nullable String stpnm, - double lat, - double lon, - @Nullable List<@Nullable Integer> dtradd, - @Nullable List<@Nullable Integer> dtrrem, - @Nullable Integer gtfsseq, - @Nullable Boolean ada - ) { - if (stpid == null) { - throw new IllegalArgumentException("stpid must not be null"); - } - - if (stpnm == null) { - throw new IllegalArgumentException("stpnm must not be null"); - } - - List dtraddCopy = null; + public CtaStop { + Objects.requireNonNull(stpid); + Objects.requireNonNull(stpnm); if (dtradd != null) { - for (Integer detourId : dtradd) { - if (detourId == null) { - throw new IllegalArgumentException("dtradd must not contain null values"); - } - } + dtradd.forEach(Objects::requireNonNull); - dtraddCopy = List.copyOf(dtradd); + dtradd = List.copyOf(dtradd); } - List dtrremCopy = null; - if (dtrrem != null) { - for (Integer detourId : dtrrem) { - if (detourId == null) { - throw new IllegalArgumentException("dtrrem must not contain null values"); - } - } + dtrrem.forEach(Objects::requireNonNull); - dtrremCopy = List.copyOf(dtrrem); + dtrrem = List.copyOf(dtrrem); } - - this.stpid = stpid; - this.stpnm = stpnm; - this.lat = lat; - this.lon = lon; - this.dtradd = dtraddCopy; - this.dtrrem = dtrremCopy; - this.gtfsseq = gtfsseq; - this.ada = ada; } } diff --git a/src/main/java/com/cta4j/bus/api/stop/impl/StopsApiImpl.java b/src/main/java/com/cta4j/bus/api/stop/impl/StopsApiImpl.java index 699df25..6a1e1e7 100644 --- a/src/main/java/com/cta4j/bus/api/stop/impl/StopsApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/stop/impl/StopsApiImpl.java @@ -13,7 +13,6 @@ import org.apache.hc.core5.net.URIBuilder; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; import org.mapstruct.factory.Mappers; import tools.jackson.core.JacksonException; import tools.jackson.core.type.TypeReference; @@ -21,6 +20,7 @@ import java.util.Collection; import java.util.List; +import java.util.Objects; @NullMarked @ApiStatus.Internal @@ -34,21 +34,13 @@ public final class StopsApiImpl implements StopsApi { private final StopMapper stopMapper; public StopsApiImpl( - @Nullable String host, - @Nullable String apiKey, - @Nullable ObjectMapper objectMapper + String host, + String apiKey, + ObjectMapper objectMapper ) { - if (host == null) { - throw new IllegalArgumentException("host must not be null"); - } - - if (apiKey == null) { - throw new IllegalArgumentException("apiKey must not be null"); - } - - if (objectMapper == null) { - throw new IllegalArgumentException("objectMapper must not be null"); - } + Objects.requireNonNull(host); + Objects.requireNonNull(apiKey); + Objects.requireNonNull(objectMapper); this.host = host; this.apiKey = apiKey; @@ -57,14 +49,9 @@ public StopsApiImpl( } @Override - public List findByRouteIdAndDirection(@Nullable String routeId, @Nullable String direction) { - if (routeId == null) { - throw new IllegalArgumentException("routeId must not be null"); - } - - if (direction == null) { - throw new IllegalArgumentException("direction must not be null"); - } + public List findByRouteIdAndDirection(String routeId, String direction) { + Objects.requireNonNull(routeId); + Objects.requireNonNull(direction); String url = new URIBuilder() .setScheme(ApiUtils.SCHEME) @@ -80,16 +67,10 @@ public List findByRouteIdAndDirection(@Nullable String routeId, @Nullable } @Override - public List findByIds(@Nullable Collection<@Nullable String> stopIds) { - if (stopIds == null) { - throw new IllegalArgumentException("stopIds must not be null"); - } + public List findByIds(Collection stopIds) { + Objects.requireNonNull(stopIds); - for (String stopId : stopIds) { - if (stopId == null) { - throw new IllegalArgumentException("stopIds must not contain null elements"); - } - } + stopIds.forEach(Objects::requireNonNull); if (stopIds.size() > MAX_STOP_IDS_PER_REQUEST) { String message = String.format( diff --git a/src/main/java/com/cta4j/bus/api/stop/model/Stop.java b/src/main/java/com/cta4j/bus/api/stop/model/Stop.java index b2a2843..d9bdf5f 100644 --- a/src/main/java/com/cta4j/bus/api/stop/model/Stop.java +++ b/src/main/java/com/cta4j/bus/api/stop/model/Stop.java @@ -5,6 +5,7 @@ import java.math.BigDecimal; import java.util.List; +import java.util.Objects; @NullMarked public record Stop( @@ -28,63 +29,22 @@ public record Stop( @Nullable Boolean adaAccessible ) { - public Stop( - @Nullable String id, - @Nullable String name, - @Nullable BigDecimal latitude, - @Nullable BigDecimal longitude, - @Nullable List<@Nullable Integer> detoursAdded, - @Nullable List<@Nullable Integer> detoursRemoved, - @Nullable Integer gtfsSequence, - @Nullable Boolean adaAccessible - ) { - if (id == null) { - throw new IllegalArgumentException("id must not be null"); - } - - if (name == null) { - throw new IllegalArgumentException("name must not be null"); - } - - if (latitude == null) { - throw new IllegalArgumentException("latitude must not be null"); - } - - if (longitude == null) { - throw new IllegalArgumentException("longitude must not be null"); - } - - List detoursAddedCopy = null; + public Stop { + Objects.requireNonNull(id); + Objects.requireNonNull(name); + Objects.requireNonNull(latitude); + Objects.requireNonNull(longitude); if (detoursAdded != null) { - for (Integer detourId : detoursAdded) { - if (detourId == null) { - throw new IllegalArgumentException("detoursAdded must not contain null values"); - } - } + detoursAdded.forEach(Objects::requireNonNull); - detoursAddedCopy = List.copyOf(detoursAdded); + detoursAdded = List.copyOf(detoursAdded); } - List detoursRemovedCopy = null; - if (detoursRemoved != null) { - for (Integer detourId : detoursRemoved) { - if (detourId == null) { - throw new IllegalArgumentException("detoursRemoved must not contain null values"); - } - } + detoursRemoved.forEach(Objects::requireNonNull); - detoursRemovedCopy = List.copyOf(detoursRemoved); + detoursRemoved = List.copyOf(detoursRemoved); } - - this.id = id; - this.name = name; - this.latitude = latitude; - this.longitude = longitude; - this.detoursAdded = detoursAddedCopy; - this.detoursRemoved = detoursRemovedCopy; - this.gtfsSequence = gtfsSequence; - this.adaAccessible = adaAccessible; } } diff --git a/src/main/java/com/cta4j/bus/api/vehicle/VehiclesApi.java b/src/main/java/com/cta4j/bus/api/vehicle/VehiclesApi.java index 5bb8a6c..607e033 100644 --- a/src/main/java/com/cta4j/bus/api/vehicle/VehiclesApi.java +++ b/src/main/java/com/cta4j/bus/api/vehicle/VehiclesApi.java @@ -3,20 +3,18 @@ import com.cta4j.bus.api.vehicle.model.Vehicle; import com.cta4j.exception.Cta4jException; import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; import java.util.Collection; import java.util.List; +import java.util.Objects; import java.util.Optional; @NullMarked public interface VehiclesApi { - List findByIds(@Nullable Collection<@Nullable String> ids); + List findByIds(Collection ids); - default Optional findById(@Nullable String id) { - if (id == null) { - throw new IllegalArgumentException("id must not be null"); - } + default Optional findById(String id) { + Objects.requireNonNull(id); List ids = List.of(id); @@ -35,12 +33,10 @@ default Optional findById(@Nullable String id) { return Optional.of(vehicle); } - List findByRouteIds(@Nullable Collection<@Nullable String> routeIds); + List findByRouteIds(Collection routeIds); - default List findByRouteId(@Nullable String routeId) { - if (routeId == null) { - throw new IllegalArgumentException("routeId must not be null"); - } + default List findByRouteId(String routeId) { + Objects.requireNonNull(routeId); List routeIds = List.of(routeId); diff --git a/src/main/java/com/cta4j/bus/api/vehicle/external/CtaVehicle.java b/src/main/java/com/cta4j/bus/api/vehicle/external/CtaVehicle.java index 06b67b2..d8b1ad6 100644 --- a/src/main/java/com/cta4j/bus/api/vehicle/external/CtaVehicle.java +++ b/src/main/java/com/cta4j/bus/api/vehicle/external/CtaVehicle.java @@ -5,6 +5,8 @@ import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; +import java.util.Objects; + @NullMarked @ApiStatus.Internal @JsonIgnoreProperties(ignoreUnknown = true) @@ -75,93 +77,14 @@ public record CtaVehicle( @Nullable String stsd ) { - public CtaVehicle( - @Nullable String vid, - @Nullable String rtpidatafeed, - @Nullable String tmpstmp, - double lat, - double lon, - int hdg, - int pid, - @Nullable String rt, - @Nullable String des, - int pdist, - @Nullable Integer stopstatus, - @Nullable Integer timepointid, - @Nullable String stopid, - @Nullable Integer sequence, - @Nullable Integer gtfsseq, - boolean dly, - @Nullable String srvtmstmp, - @Nullable Integer spd, - @Nullable Integer blk, - @Nullable String tablockid, - @Nullable String tatripid, - @Nullable String origtatripno, - @Nullable String zone, - int mode, - @Nullable String psgld, - @Nullable Integer stst, - @Nullable String stsd - ) { - if (vid == null) { - throw new IllegalArgumentException("vid must not be null"); - } - - if (rt == null) { - throw new IllegalArgumentException("rt must not be null"); - } - - if (des == null) { - throw new IllegalArgumentException("des must not be null"); - } - - if (tablockid == null) { - throw new IllegalArgumentException("tablockid must not be null"); - } - - if (tatripid == null) { - throw new IllegalArgumentException("tatripid must not be null"); - } - - if (origtatripno == null) { - throw new IllegalArgumentException("origtatripno must not be null"); - } - - if (zone == null) { - throw new IllegalArgumentException("zone must not be null"); - } - - if (psgld == null) { - throw new IllegalArgumentException("psgld must not be null"); - } - - this.vid = vid; - this.rtpidatafeed = rtpidatafeed; - this.tmpstmp = tmpstmp; - this.lat = lat; - this.lon = lon; - this.hdg = hdg; - this.pid = pid; - this.rt = rt; - this.des = des; - this.pdist = pdist; - this.stopstatus = stopstatus; - this.timepointid = timepointid; - this.stopid = stopid; - this.sequence = sequence; - this.gtfsseq = gtfsseq; - this.dly = dly; - this.srvtmstmp = srvtmstmp; - this.spd = spd; - this.blk = blk; - this.tablockid = tablockid; - this.tatripid = tatripid; - this.origtatripno = origtatripno; - this.zone = zone; - this.mode = mode; - this.psgld = psgld; - this.stst = stst; - this.stsd = stsd; + public CtaVehicle { + Objects.requireNonNull(vid); + Objects.requireNonNull(rt); + Objects.requireNonNull(des); + Objects.requireNonNull(tablockid); + Objects.requireNonNull(tatripid); + Objects.requireNonNull(origtatripno); + Objects.requireNonNull(zone); + Objects.requireNonNull(psgld); } } diff --git a/src/main/java/com/cta4j/bus/api/vehicle/impl/VehiclesApiImpl.java b/src/main/java/com/cta4j/bus/api/vehicle/impl/VehiclesApiImpl.java index d4eaba0..b1b4438 100644 --- a/src/main/java/com/cta4j/bus/api/vehicle/impl/VehiclesApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/vehicle/impl/VehiclesApiImpl.java @@ -13,7 +13,6 @@ import org.apache.hc.core5.net.URIBuilder; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; import org.mapstruct.factory.Mappers; import tools.jackson.core.JacksonException; import tools.jackson.core.type.TypeReference; @@ -21,6 +20,7 @@ import java.util.Collection; import java.util.List; +import java.util.Objects; @NullMarked @ApiStatus.Internal @@ -33,21 +33,13 @@ public final class VehiclesApiImpl implements VehiclesApi { private final VehicleMapper vehicleMapper; public VehiclesApiImpl( - @Nullable String host, - @Nullable String apiKey, - @Nullable ObjectMapper objectMapper + String host, + String apiKey, + ObjectMapper objectMapper ) { - if (host == null) { - throw new IllegalArgumentException("host must not be null"); - } - - if (apiKey == null) { - throw new IllegalArgumentException("apiKey must not be null"); - } - - if (objectMapper == null) { - throw new IllegalArgumentException("objectMapper must not be null"); - } + Objects.requireNonNull(host); + Objects.requireNonNull(apiKey); + Objects.requireNonNull(objectMapper); this.host = host; this.apiKey = apiKey; @@ -56,21 +48,15 @@ public VehiclesApiImpl( } @Override - public List findByIds(@Nullable Collection<@Nullable String> ids) { - if (ids == null) { - throw new IllegalArgumentException("ids must not be null"); - } + public List findByIds(Collection ids) { + Objects.requireNonNull(ids); + + ids.forEach(Objects::requireNonNull); if (ids.isEmpty()) { return List.of(); } - for (String id : ids) { - if (id == null) { - throw new IllegalArgumentException("ids must not contain null elements"); - } - } - String idsString = String.join(",", ids); String url = new URIBuilder() @@ -87,21 +73,15 @@ public List findByIds(@Nullable Collection<@Nullable String> ids) { } @Override - public List findByRouteIds(@Nullable Collection<@Nullable String> routeIds) { - if (routeIds == null) { - throw new IllegalArgumentException("routeIds must not be null"); - } + public List findByRouteIds(Collection routeIds) { + Objects.requireNonNull(routeIds); + + routeIds.forEach(Objects::requireNonNull); if (routeIds.isEmpty()) { return List.of(); } - for (String routeId : routeIds) { - if (routeId == null) { - throw new IllegalArgumentException("routeIds must not contain null elements"); - } - } - String routeIdsString = String.join(",", routeIds); String url = new URIBuilder() diff --git a/src/main/java/com/cta4j/bus/model/TransitMode.java b/src/main/java/com/cta4j/bus/api/vehicle/model/TransitMode.java similarity index 86% rename from src/main/java/com/cta4j/bus/model/TransitMode.java rename to src/main/java/com/cta4j/bus/api/vehicle/model/TransitMode.java index ad2aeba..015efad 100644 --- a/src/main/java/com/cta4j/bus/model/TransitMode.java +++ b/src/main/java/com/cta4j/bus/api/vehicle/model/TransitMode.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.model; +package com.cta4j.bus.api.vehicle.model; public enum TransitMode { NONE(0), diff --git a/src/main/java/com/cta4j/bus/api/vehicle/model/Vehicle.java b/src/main/java/com/cta4j/bus/api/vehicle/model/Vehicle.java index c200202..5a004c7 100644 --- a/src/main/java/com/cta4j/bus/api/vehicle/model/Vehicle.java +++ b/src/main/java/com/cta4j/bus/api/vehicle/model/Vehicle.java @@ -1,7 +1,8 @@ package com.cta4j.bus.api.vehicle.model; import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; + +import java.util.Objects; @NullMarked public record Vehicle( @@ -17,39 +18,11 @@ public record Vehicle( VehicleMetadata metadata ) { - public Vehicle( - @Nullable String id, - @Nullable String route, - @Nullable String destination, - @Nullable VehicleCoordinates coordinates, - boolean delayed, - @Nullable VehicleMetadata metadata - ) { - if (id == null) { - throw new IllegalArgumentException("id must not be null"); - } - - if (route == null) { - throw new IllegalArgumentException("route must not be null"); - } - - if (destination == null) { - throw new IllegalArgumentException("destination must not be null"); - } - - if (coordinates == null) { - throw new IllegalArgumentException("coordinates must not be null"); - } - - if (metadata == null) { - throw new IllegalArgumentException("metadata must not be null"); - } - - this.id = id; - this.route = route; - this.destination = destination; - this.coordinates = coordinates; - this.delayed = delayed; - this.metadata = metadata; + public Vehicle { + Objects.requireNonNull(id); + Objects.requireNonNull(route); + Objects.requireNonNull(destination); + Objects.requireNonNull(coordinates); + Objects.requireNonNull(metadata); } } diff --git a/src/main/java/com/cta4j/bus/api/vehicle/model/VehicleCoordinates.java b/src/main/java/com/cta4j/bus/api/vehicle/model/VehicleCoordinates.java index 4bd18e2..2e13d64 100644 --- a/src/main/java/com/cta4j/bus/api/vehicle/model/VehicleCoordinates.java +++ b/src/main/java/com/cta4j/bus/api/vehicle/model/VehicleCoordinates.java @@ -1,17 +1,10 @@ package com.cta4j.bus.api.vehicle.model; import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; import java.math.BigDecimal; +import java.util.Objects; -/** - * The coordinates and heading of a bus. - * - * @param latitude the latitude of the bus's current location - * @param longitude the longitude of the bus's current location - * @param heading the heading of the bus in degrees (0-359) - */ @NullMarked public record VehicleCoordinates( BigDecimal latitude, @@ -20,25 +13,17 @@ public record VehicleCoordinates( int heading ) { - public VehicleCoordinates( - @Nullable BigDecimal latitude, - @Nullable BigDecimal longitude, - int heading - ) { - if (latitude == null) { - throw new IllegalArgumentException("latitude must not be null"); - } + private static final int MIN_HEADING = 0; + private static final int MAX_HEADING = 359; - if (longitude == null) { - throw new IllegalArgumentException("longitude must not be null"); - } + public VehicleCoordinates { + Objects.requireNonNull(latitude); + Objects.requireNonNull(longitude); - if ((heading < 0) || (heading > 359)) { - throw new IllegalArgumentException("heading must be between 0 and 359 (inclusive)"); - } + if ((heading < MIN_HEADING) || (heading > MAX_HEADING)) { + String message = String.format("heading must be between %d and %d (inclusive)", MIN_HEADING, MAX_HEADING); - this.latitude = latitude; - this.longitude = longitude; - this.heading = heading; + throw new IllegalArgumentException(message); + } } } diff --git a/src/main/java/com/cta4j/bus/api/vehicle/model/VehicleMetadata.java b/src/main/java/com/cta4j/bus/api/vehicle/model/VehicleMetadata.java index fef9a88..c47d09a 100644 --- a/src/main/java/com/cta4j/bus/api/vehicle/model/VehicleMetadata.java +++ b/src/main/java/com/cta4j/bus/api/vehicle/model/VehicleMetadata.java @@ -1,12 +1,12 @@ package com.cta4j.bus.api.vehicle.model; -import com.cta4j.bus.model.PassengerLoad; -import com.cta4j.bus.model.TransitMode; +import com.cta4j.bus.api.prediction.model.PassengerLoad; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import java.time.Instant; import java.time.LocalDate; +import java.util.Objects; @NullMarked public record VehicleMetadata( @@ -62,71 +62,12 @@ public record VehicleMetadata( @Nullable LocalDate scheduledStartDate ) { - public VehicleMetadata( - @Nullable String dataFeed, - @Nullable Instant lastUpdated, - int patternId, - int distanceToPatternPoint, - @Nullable Integer stopStatus, - @Nullable Integer timepointId, - @Nullable String stopId, - @Nullable Integer sequence, - @Nullable Integer gtfsSequence, - @Nullable Instant serverTimestamp, - @Nullable Integer speed, - @Nullable Integer block, - @Nullable String blockId, - @Nullable String tripId, - @Nullable String originalTripNumber, - @Nullable String zone, - @Nullable TransitMode mode, - @Nullable PassengerLoad passengerLoad, - @Nullable Integer scheduledStartSeconds, - @Nullable LocalDate scheduledStartDate - ) { - if (blockId == null) { - throw new IllegalArgumentException("blockId must not be null"); - } - - if (tripId == null) { - throw new IllegalArgumentException("tripId must not be null"); - } - - if (originalTripNumber == null) { - throw new IllegalArgumentException("originalTripNumber must not be null"); - } - - if (zone == null) { - throw new IllegalArgumentException("zone must not be null"); - } - - if (mode == null) { - throw new IllegalArgumentException("mode must not be null"); - } - - if (passengerLoad == null) { - throw new IllegalArgumentException("passengerLoad must not be null"); - } - - this.dataFeed = dataFeed; - this.lastUpdated = lastUpdated; - this.patternId = patternId; - this.distanceToPatternPoint = distanceToPatternPoint; - this.stopStatus = stopStatus; - this.timepointId = timepointId; - this.stopId = stopId; - this.sequence = sequence; - this.gtfsSequence = gtfsSequence; - this.serverTimestamp = serverTimestamp; - this.speed = speed; - this.block = block; - this.blockId = blockId; - this.tripId = tripId; - this.originalTripNumber = originalTripNumber; - this.zone = zone; - this.mode = mode; - this.passengerLoad = passengerLoad; - this.scheduledStartSeconds = scheduledStartSeconds; - this.scheduledStartDate = scheduledStartDate; + public VehicleMetadata { + Objects.requireNonNull(blockId); + Objects.requireNonNull(tripId); + Objects.requireNonNull(originalTripNumber); + Objects.requireNonNull(zone); + Objects.requireNonNull(mode); + Objects.requireNonNull(passengerLoad); } } diff --git a/src/main/java/com/cta4j/bus/client/BusClient.java b/src/main/java/com/cta4j/bus/client/BusClient.java index 5dbdeca..fa7ed41 100644 --- a/src/main/java/com/cta4j/bus/client/BusClient.java +++ b/src/main/java/com/cta4j/bus/client/BusClient.java @@ -1,7 +1,6 @@ package com.cta4j.bus.client; import com.cta4j.bus.client.internal.BusClientImpl; -import com.cta4j.bus.model.Arrival; import com.cta4j.bus.model.Detour; import com.cta4j.exception.Cta4jException; import org.jspecify.annotations.NullMarked; @@ -14,27 +13,6 @@ @NullMarked @SuppressWarnings("ConstantConditions") public interface BusClient { - /** - * Finds arrivals for the specified route ID and stop ID. - * - * @param routeId the ID of the bus route - * @param stopId the ID of the bus stop - * @return a {@link List} of arrivals for the specified bus route and stop - * @throws IllegalArgumentException if the specified bus route or stop is {@code null} - * @throws Cta4jException if an error occurs while fetching the data - */ - List findArrivalsByRouteIdAndStopId(String routeId, String stopId); - - /** - * Finds arrivals for the specified bus ID. - * - * @param busId the ID of the bus - * @return a {@link List} of arrivals for the specified bus - * @throws IllegalArgumentException if the specified bus ID is {@code null} - * @throws Cta4jException if an error occurs while fetching the data - */ - List findArrivalsByBusId(String busId); - /** * Finds detours for the specified route ID and direction. * diff --git a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java index 603e28f..82e6f6c 100644 --- a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java +++ b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java @@ -4,10 +4,8 @@ import com.cta4j.bus.external.CtaBustimeResponse; import com.cta4j.bus.external.CtaError; import com.cta4j.bus.external.CtaResponse; -import com.cta4j.bus.external.CtaPrediction; -import com.cta4j.bus.mapper.ArrivalMapper; +import com.cta4j.bus.api.prediction.mapper.PredictionMapper; import com.cta4j.bus.mapper.DetourMapper; -import com.cta4j.bus.model.Arrival; import com.cta4j.bus.model.Detour; import com.cta4j.exception.Cta4jException; import com.cta4j.bus.external.CtaDetour; @@ -34,13 +32,11 @@ public final class BusClientImpl implements BusClient { private static final String SCHEME = "https"; private static final String DEFAULT_HOST = "ctabustracker.com"; private static final String API_PREFIX = "/bustime/api/v3"; - private static final String PREDICTIONS_ENDPOINT = String.format("%s/getpredictions", API_PREFIX); private static final String DETOURS_ENDPOINT = String.format("%s/getdetours", API_PREFIX); private final String host; private final String apiKey; private final ObjectMapper objectMapper; - private final ArrivalMapper arrivalMapper; private final DetourMapper detourMapper; private BusClientImpl(String host, String apiKey) { @@ -55,53 +51,9 @@ private BusClientImpl(String host, String apiKey) { this.host = host; this.apiKey = apiKey; this.objectMapper = new ObjectMapper(); - this.arrivalMapper = Mappers.getMapper(ArrivalMapper.class); this.detourMapper = Mappers.getMapper(DetourMapper.class); } - @Override - public List findArrivalsByRouteIdAndStopId(String routeId, String stopId) { - if (routeId == null) { - throw new IllegalArgumentException("routeId must not be null"); - } - - if (stopId == null) { - throw new IllegalArgumentException("stopId must not be null"); - } - - String url = new URIBuilder() - .setScheme(SCHEME) - .setHost(this.host) - .setPath(PREDICTIONS_ENDPOINT) - .addParameter("rt", routeId) - .addParameter("stpid", stopId) - .addParameter("tmres", "s") - .addParameter("key", this.apiKey) - .addParameter("format", "json") - .toString(); - - return this.getArrivals(url); - } - - @Override - public List findArrivalsByBusId(String busId) { - if (busId == null) { - throw new IllegalArgumentException("busId must not be null"); - } - - String url = new URIBuilder() - .setScheme(SCHEME) - .setHost(this.host) - .setPath(PREDICTIONS_ENDPOINT) - .addParameter("vid", busId) - .addParameter("tmres", "s") - .addParameter("key", this.apiKey) - .addParameter("format", "json") - .toString(); - - return this.getArrivals(url); - } - @Override public List findDetoursByRouteIdAndDirection(String routeId, String direction) { if (routeId == null) { @@ -171,46 +123,6 @@ private String buildErrorMessage(String endpoint, List errors) { return String.format("Error response from %s: %s", endpoint, message); } - private List getArrivals(String url) { - String response = HttpUtils.get(url); - - TypeReference>> typeReference = new TypeReference<>() {}; - CtaResponse> predictionsResponse; - - try { - predictionsResponse = this.objectMapper.readValue(response, typeReference); - } catch (JacksonException e) { - String message = String.format("Failed to parse response from %s", PREDICTIONS_ENDPOINT); - - throw new Cta4jException(message, e); - } - - CtaBustimeResponse> bustimeResponse = predictionsResponse.bustimeResponse(); - - List errors = bustimeResponse.error(); - List predictions = bustimeResponse.data(); - - if ((errors == null) && (predictions == null)) { - log.debug("Predictions bustime response missing both error and data from {}", PREDICTIONS_ENDPOINT); - - return List.of(); - } - - if ((errors != null) && !errors.isEmpty()) { - String message = this.buildErrorMessage(PREDICTIONS_ENDPOINT, errors); - - throw new Cta4jException(message); - } - - if ((predictions == null) || predictions.isEmpty()) { - return List.of(); - } - - return predictions.stream() - .map(this.arrivalMapper::toDomain) - .toList(); - } - public static final class BuilderImpl implements BusClient.Builder { @Nullable private String host; diff --git a/src/main/java/com/cta4j/bus/external/CtaPrediction.java b/src/main/java/com/cta4j/bus/external/CtaPrediction.java deleted file mode 100644 index 16019a0..0000000 --- a/src/main/java/com/cta4j/bus/external/CtaPrediction.java +++ /dev/null @@ -1,121 +0,0 @@ -package com.cta4j.bus.external; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import org.jetbrains.annotations.ApiStatus; -import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; - -@NullMarked -@ApiStatus.Internal -@SuppressWarnings("ConstantConditions") -@JsonIgnoreProperties(ignoreUnknown = true) -public record CtaPrediction( - String tmstmp, - - String typ, - - String stpid, - - String stpnm, - - int vid, - - int dstp, - - String rt, - - String rtdd, - - String rtdir, - - String des, - - String prdtm, - - @Nullable - Boolean dly, - - int dyn, - - String tablockid, - - String tatripid, - - String origtatripno, - - String zone, - - String psgld, - - @Nullable - Integer gtfsseq, - - @Nullable - String nbus, - - @Nullable - Integer stst, - - @Nullable - String stsd, - - int flagstop -) { - public CtaPrediction { - if (tmstmp == null) { - throw new IllegalArgumentException("tmstmp must not be null"); - } - - if (typ == null) { - throw new IllegalArgumentException("typ must not be null"); - } - - if (stpid == null) { - throw new IllegalArgumentException("stpid must not be null"); - } - - if (stpnm == null) { - throw new IllegalArgumentException("stpnm must not be null"); - } - - if (rt == null) { - throw new IllegalArgumentException("rt must not be null"); - } - - if (rtdd == null) { - throw new IllegalArgumentException("rtdd must not be null"); - } - - if (rtdir == null) { - throw new IllegalArgumentException("rtdir must not be null"); - } - - if (des == null) { - throw new IllegalArgumentException("des must not be null"); - } - - if (prdtm == null) { - throw new IllegalArgumentException("prdtm must not be null"); - } - - if (tablockid == null) { - throw new IllegalArgumentException("tablockid must not be null"); - } - - if (tatripid == null) { - throw new IllegalArgumentException("tatripid must not be null"); - } - - if (origtatripno == null) { - throw new IllegalArgumentException("origtatripno must not be null"); - } - - if (zone == null) { - throw new IllegalArgumentException("zone must not be null"); - } - - if (psgld == null) { - throw new IllegalArgumentException("psgld must not be null"); - } - } -} diff --git a/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java b/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java index 83fbb79..ff44f68 100644 --- a/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java +++ b/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java @@ -1,11 +1,11 @@ package com.cta4j.bus.mapper.util; -import com.cta4j.bus.model.DynamicAction; -import com.cta4j.bus.model.FlagStop; -import com.cta4j.bus.model.PassengerLoad; +import com.cta4j.bus.api.prediction.model.DynamicAction; +import com.cta4j.bus.api.prediction.model.FlagStop; +import com.cta4j.bus.api.prediction.model.PassengerLoad; import com.cta4j.bus.api.pattern.model.PatternPointType; -import com.cta4j.bus.model.PredictionType; -import com.cta4j.bus.model.TransitMode; +import com.cta4j.bus.api.prediction.model.PredictionType; +import com.cta4j.bus.api.vehicle.model.TransitMode; import org.jetbrains.annotations.ApiStatus; import org.mapstruct.Named; diff --git a/src/main/java/com/cta4j/bus/model/Arrival.java b/src/main/java/com/cta4j/bus/model/Arrival.java deleted file mode 100644 index e8f4947..0000000 --- a/src/main/java/com/cta4j/bus/model/Arrival.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.cta4j.bus.model; - -import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; - -import java.math.BigInteger; -import java.time.Duration; -import java.time.Instant; - -@NullMarked -@SuppressWarnings("ConstantConditions") -public record Arrival( - PredictionType predictionType, - - String stopId, - - String stopName, - - String vehicleId, - - BigInteger distanceToStop, - - String route, - - String routeDesignator, - - String routeDirection, - - String destination, - - Instant arrivalTime, - - @Nullable - Boolean delayed, - - ArrivalMetadata metadata -) { - public Arrival { - if (stopId == null) { - throw new IllegalArgumentException("stopId must not be null"); - } - - if (stopName == null) { - throw new IllegalArgumentException("stopName must not be null"); - } - - if (vehicleId == null) { - throw new IllegalArgumentException("vehicleId must not be null"); - } - - if (distanceToStop == null) { - throw new IllegalArgumentException("distanceToStop must not be null"); - } - - if (route == null) { - throw new IllegalArgumentException("route must not be null"); - } - - if (routeDesignator == null) { - throw new IllegalArgumentException("routeDesignator must not be null"); - } - - if (routeDirection == null) { - throw new IllegalArgumentException("routeDirection must not be null"); - } - - if (destination == null) { - throw new IllegalArgumentException("destination must not be null"); - } - - if (arrivalTime == null) { - throw new IllegalArgumentException("arrivalTime must not be null"); - } - - if (metadata == null) { - throw new IllegalArgumentException("metadata must not be null"); - } - } - - public long etaMinutes() { - Instant now = Instant.now(); - - long minutes = Duration.between(now, this.arrivalTime) - .toMinutes(); - - return Math.max(minutes, 0L); - } -} diff --git a/src/main/java/com/cta4j/bus/model/ArrivalMetadata.java b/src/main/java/com/cta4j/bus/model/ArrivalMetadata.java deleted file mode 100644 index 86b768a..0000000 --- a/src/main/java/com/cta4j/bus/model/ArrivalMetadata.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.cta4j.bus.model; - -import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; - -import java.time.Instant; - -@NullMarked -@SuppressWarnings("ConstantConditions") -public record ArrivalMetadata( - Instant timestamp, - - DynamicAction dynamicAction, - - String blockId, - - String tripId, - - String originalTripNumber, - - String zone, - - PassengerLoad passengerLoad, - - @Nullable - Integer gtfsSequence, - - @Nullable - String nextBus, - - @Nullable - Integer scheduledStartSeconds, - - @Nullable - String scheduledStartDate, - - FlagStop flagStop -) { - public ArrivalMetadata { - if (timestamp == null) { - throw new IllegalArgumentException("timestamp must not be null"); - } - - if (blockId == null) { - throw new IllegalArgumentException("blockId must not be null"); - } - - if (tripId == null) { - throw new IllegalArgumentException("tripId must not be null"); - } - - if (originalTripNumber == null) { - throw new IllegalArgumentException("originalTripNumber must not be null"); - } - - if (zone == null) { - throw new IllegalArgumentException("zone must not be null"); - } - - if (passengerLoad == null) { - throw new IllegalArgumentException("passengerLoad must not be null"); - } - } -} From ab595bab6a68884c3acd586d5a0648b8fe245ee0 Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Mon, 19 Jan 2026 11:48:42 -0600 Subject: [PATCH 40/53] API refactor --- src/main/java/com/cta4j/bus/api/BusApi.java | 24 ++- .../java/com/cta4j/bus/api/DetoursApi.java | 4 - .../java/com/cta4j/bus/api/LocalesApi.java | 4 - .../common}/external/CtaBustimeResponse.java | 4 +- .../{ => api/common}/external/CtaError.java | 11 +- .../common}/external/CtaResponse.java | 15 +- .../bus/api/{ => common/util}/ApiUtils.java | 4 +- .../common}/util/CtaBusMappingQualifiers.java | 4 +- .../com/cta4j/bus/api/detour/DetoursApi.java | 15 ++ .../bus/api/detour/external/CtaDetour.java | 41 +++++ .../external/CtaDetoursRouteDirection.java | 14 +- .../bus/api/detour/impl/DetoursApiImpl.java | 119 ++++++++++++ .../{ => api/detour}/mapper/DetourMapper.java | 17 +- .../cta4j/bus/api/detour/model/Detour.java | 39 ++++ .../detour/model/DetourRouteDirection.java | 17 ++ .../api/direction/impl/DirectionsApiImpl.java | 20 +- .../com/cta4j/bus/api/impl/BusApiImpl.java | 58 +++--- .../com/cta4j/bus/api/locale/LocalesApi.java | 7 + .../bus/api/locale/impl/LocalesApiImpl.java | 22 +++ .../bus/api/pattern/impl/PatternsApiImpl.java | 23 +-- .../pattern/mapper/RoutePatternMapper.java | 5 +- .../prediction/impl/PredictionsApiImpl.java | 161 +++++++++++++++++ .../prediction/mapper/PredictionMapper.java | 5 +- .../query/StopsPredictionsQuery.java | 21 ++- .../query/VehiclesPredictionsQuery.java | 13 +- .../bus/api/route/impl/RoutesApiImpl.java | 23 +-- .../bus/api/route/mapper/RouteMapper.java | 3 + .../cta4j/bus/api/stop/impl/StopsApiImpl.java | 23 +-- .../cta4j/bus/api/stop/mapper/StopMapper.java | 3 + .../bus/api/vehicle/impl/VehiclesApiImpl.java | 23 +-- .../bus/api/vehicle/mapper/VehicleMapper.java | 5 +- .../java/com/cta4j/bus/client/BusClient.java | 67 ------- .../bus/client/internal/BusClientImpl.java | 171 ------------------ .../com/cta4j/bus/external/CtaDetour.java | 59 ------ src/main/java/com/cta4j/bus/model/Detour.java | 60 ------ .../cta4j/bus/model/DetourRouteDirection.java | 8 - 36 files changed, 574 insertions(+), 538 deletions(-) delete mode 100644 src/main/java/com/cta4j/bus/api/DetoursApi.java delete mode 100644 src/main/java/com/cta4j/bus/api/LocalesApi.java rename src/main/java/com/cta4j/bus/{ => api/common}/external/CtaBustimeResponse.java (83%) rename src/main/java/com/cta4j/bus/{ => api/common}/external/CtaError.java (56%) rename src/main/java/com/cta4j/bus/{ => api/common}/external/CtaResponse.java (50%) rename src/main/java/com/cta4j/bus/api/{ => common/util}/ApiUtils.java (91%) rename src/main/java/com/cta4j/bus/{mapper => api/common}/util/CtaBusMappingQualifiers.java (97%) create mode 100644 src/main/java/com/cta4j/bus/api/detour/DetoursApi.java create mode 100644 src/main/java/com/cta4j/bus/api/detour/external/CtaDetour.java rename src/main/java/com/cta4j/bus/{ => api/detour}/external/CtaDetoursRouteDirection.java (54%) create mode 100644 src/main/java/com/cta4j/bus/api/detour/impl/DetoursApiImpl.java rename src/main/java/com/cta4j/bus/{ => api/detour}/mapper/DetourMapper.java (67%) create mode 100644 src/main/java/com/cta4j/bus/api/detour/model/Detour.java create mode 100644 src/main/java/com/cta4j/bus/api/detour/model/DetourRouteDirection.java create mode 100644 src/main/java/com/cta4j/bus/api/locale/LocalesApi.java create mode 100644 src/main/java/com/cta4j/bus/api/locale/impl/LocalesApiImpl.java create mode 100644 src/main/java/com/cta4j/bus/api/prediction/impl/PredictionsApiImpl.java delete mode 100644 src/main/java/com/cta4j/bus/client/BusClient.java delete mode 100644 src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java delete mode 100644 src/main/java/com/cta4j/bus/external/CtaDetour.java delete mode 100644 src/main/java/com/cta4j/bus/model/Detour.java delete mode 100644 src/main/java/com/cta4j/bus/model/DetourRouteDirection.java diff --git a/src/main/java/com/cta4j/bus/api/BusApi.java b/src/main/java/com/cta4j/bus/api/BusApi.java index deef86c..331b6fa 100644 --- a/src/main/java/com/cta4j/bus/api/BusApi.java +++ b/src/main/java/com/cta4j/bus/api/BusApi.java @@ -1,7 +1,9 @@ package com.cta4j.bus.api; +import com.cta4j.bus.api.detour.DetoursApi; import com.cta4j.bus.api.direction.DirectionsApi; import com.cta4j.bus.api.impl.BusApiImpl; +import com.cta4j.bus.api.locale.LocalesApi; import com.cta4j.bus.api.pattern.PatternsApi; import com.cta4j.bus.api.prediction.PredictionsApi; import com.cta4j.bus.api.route.RoutesApi; @@ -10,6 +12,7 @@ import org.jspecify.annotations.NullMarked; import java.time.Instant; +import java.util.Objects; @NullMarked public interface BusApi { @@ -46,15 +49,6 @@ interface Builder { */ Builder host(String host); - /** - * Sets the API key used for authentication. - * - * @param apiKey the API key - * @return this {@link Builder} for method chaining - * @throws NullPointerException if {@code apiKey} is {@code null} - */ - Builder apiKey(String apiKey); - /** * Builds a configured {@link BusApi} instance. * @@ -64,11 +58,15 @@ interface Builder { } /** - * Creates a new {@link Builder} for configuring and building a {@link BusApi}. + * Creates a new {@link Builder} for configuring a {@link BusApi} instance. * - * @return a new {@link Builder} instance + * @param apiKey the API key + * @return a new {@link Builder} + * @throws NullPointerException if {@code apiKey} is {@code null} */ - static Builder builder() { - return new BusApiImpl.BuilderImpl(); + static Builder builder(String apiKey) { + Objects.requireNonNull(apiKey); + + return new BusApiImpl.BuilderImpl(apiKey); } } diff --git a/src/main/java/com/cta4j/bus/api/DetoursApi.java b/src/main/java/com/cta4j/bus/api/DetoursApi.java deleted file mode 100644 index b458a0f..0000000 --- a/src/main/java/com/cta4j/bus/api/DetoursApi.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.cta4j.bus.api; - -public interface DetoursApi { -} diff --git a/src/main/java/com/cta4j/bus/api/LocalesApi.java b/src/main/java/com/cta4j/bus/api/LocalesApi.java deleted file mode 100644 index 64f90f5..0000000 --- a/src/main/java/com/cta4j/bus/api/LocalesApi.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.cta4j.bus.api; - -public interface LocalesApi { -} diff --git a/src/main/java/com/cta4j/bus/external/CtaBustimeResponse.java b/src/main/java/com/cta4j/bus/api/common/external/CtaBustimeResponse.java similarity index 83% rename from src/main/java/com/cta4j/bus/external/CtaBustimeResponse.java rename to src/main/java/com/cta4j/bus/api/common/external/CtaBustimeResponse.java index 97dc2d4..36a36e4 100644 --- a/src/main/java/com/cta4j/bus/external/CtaBustimeResponse.java +++ b/src/main/java/com/cta4j/bus/api/common/external/CtaBustimeResponse.java @@ -1,13 +1,15 @@ -package com.cta4j.bus.external; +package com.cta4j.bus.api.common.external; import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import java.util.List; @NullMarked +@ApiStatus.Internal @JsonIgnoreProperties(ignoreUnknown = true) public record CtaBustimeResponse( @Nullable diff --git a/src/main/java/com/cta4j/bus/external/CtaError.java b/src/main/java/com/cta4j/bus/api/common/external/CtaError.java similarity index 56% rename from src/main/java/com/cta4j/bus/external/CtaError.java rename to src/main/java/com/cta4j/bus/api/common/external/CtaError.java index 168634c..9a784ff 100644 --- a/src/main/java/com/cta4j/bus/external/CtaError.java +++ b/src/main/java/com/cta4j/bus/api/common/external/CtaError.java @@ -1,17 +1,18 @@ -package com.cta4j.bus.external; +package com.cta4j.bus.api.common.external; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; +import java.util.Objects; + @NullMarked -@SuppressWarnings("ConstantConditions") +@ApiStatus.Internal @JsonIgnoreProperties(ignoreUnknown = true) public record CtaError( String msg ) { public CtaError { - if (msg == null) { - throw new IllegalArgumentException("msg must not be null"); - } + Objects.requireNonNull(msg); } } diff --git a/src/main/java/com/cta4j/bus/external/CtaResponse.java b/src/main/java/com/cta4j/bus/api/common/external/CtaResponse.java similarity index 50% rename from src/main/java/com/cta4j/bus/external/CtaResponse.java rename to src/main/java/com/cta4j/bus/api/common/external/CtaResponse.java index 12a805c..8dc888a 100644 --- a/src/main/java/com/cta4j/bus/external/CtaResponse.java +++ b/src/main/java/com/cta4j/bus/api/common/external/CtaResponse.java @@ -1,21 +1,20 @@ -package com.cta4j.bus.external; +package com.cta4j.bus.api.common.external; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; + +import java.util.Objects; @NullMarked +@ApiStatus.Internal @JsonIgnoreProperties(ignoreUnknown = true) public record CtaResponse( @JsonProperty("bustime-response") CtaBustimeResponse bustimeResponse ) { - public CtaResponse(@Nullable CtaBustimeResponse bustimeResponse) { - if (bustimeResponse == null) { - throw new IllegalArgumentException("bustimeResponse must not be null"); - } - - this.bustimeResponse = bustimeResponse; + public CtaResponse { + Objects.requireNonNull(bustimeResponse); } } diff --git a/src/main/java/com/cta4j/bus/api/ApiUtils.java b/src/main/java/com/cta4j/bus/api/common/util/ApiUtils.java similarity index 91% rename from src/main/java/com/cta4j/bus/api/ApiUtils.java rename to src/main/java/com/cta4j/bus/api/common/util/ApiUtils.java index 754f74a..a5fb550 100644 --- a/src/main/java/com/cta4j/bus/api/ApiUtils.java +++ b/src/main/java/com/cta4j/bus/api/common/util/ApiUtils.java @@ -1,6 +1,6 @@ -package com.cta4j.bus.api; +package com.cta4j.bus.api.common.util; -import com.cta4j.bus.external.CtaError; +import com.cta4j.bus.api.common.external.CtaError; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java b/src/main/java/com/cta4j/bus/api/common/util/CtaBusMappingQualifiers.java similarity index 97% rename from src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java rename to src/main/java/com/cta4j/bus/api/common/util/CtaBusMappingQualifiers.java index ff44f68..68d4e22 100644 --- a/src/main/java/com/cta4j/bus/mapper/util/CtaBusMappingQualifiers.java +++ b/src/main/java/com/cta4j/bus/api/common/util/CtaBusMappingQualifiers.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.mapper.util; +package com.cta4j.bus.api.common.util; import com.cta4j.bus.api.prediction.model.DynamicAction; import com.cta4j.bus.api.prediction.model.FlagStop; @@ -16,7 +16,7 @@ @ApiStatus.Internal public final class CtaBusMappingQualifiers { - private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm:ss"); + private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm[:ss]"); private static final ZoneId CHICAGO_ZONE_ID = ZoneId.of("America/Chicago"); private CtaBusMappingQualifiers() { diff --git a/src/main/java/com/cta4j/bus/api/detour/DetoursApi.java b/src/main/java/com/cta4j/bus/api/detour/DetoursApi.java new file mode 100644 index 0000000..ffb98d1 --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/detour/DetoursApi.java @@ -0,0 +1,15 @@ +package com.cta4j.bus.api.detour; + +import com.cta4j.bus.api.detour.model.Detour; +import org.jspecify.annotations.NullMarked; + +import java.util.List; + +@NullMarked +public interface DetoursApi { + List list(); + + List findByRouteId(String routeId); + + List findByRouteIdAndDirection(String routeId, String direction); +} diff --git a/src/main/java/com/cta4j/bus/api/detour/external/CtaDetour.java b/src/main/java/com/cta4j/bus/api/detour/external/CtaDetour.java new file mode 100644 index 0000000..6b7db2b --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/detour/external/CtaDetour.java @@ -0,0 +1,41 @@ +package com.cta4j.bus.api.detour.external; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.util.List; +import java.util.Objects; + +@NullMarked +@ApiStatus.Internal +@JsonIgnoreProperties(ignoreUnknown = true) +public record CtaDetour( + String id, + + int ver, + + int st, + + String desc, + + List rtdirs, + + String startdt, + + String enddt, + + @Nullable + String rtpidatafeed +) { + public CtaDetour { + Objects.requireNonNull(id); + Objects.requireNonNull(desc); + Objects.requireNonNull(rtdirs); + Objects.requireNonNull(startdt); + Objects.requireNonNull(enddt); + + rtdirs.forEach(Objects::requireNonNull); + } +} diff --git a/src/main/java/com/cta4j/bus/external/CtaDetoursRouteDirection.java b/src/main/java/com/cta4j/bus/api/detour/external/CtaDetoursRouteDirection.java similarity index 54% rename from src/main/java/com/cta4j/bus/external/CtaDetoursRouteDirection.java rename to src/main/java/com/cta4j/bus/api/detour/external/CtaDetoursRouteDirection.java index 2c6a3c5..5c97c5f 100644 --- a/src/main/java/com/cta4j/bus/external/CtaDetoursRouteDirection.java +++ b/src/main/java/com/cta4j/bus/api/detour/external/CtaDetoursRouteDirection.java @@ -1,12 +1,13 @@ -package com.cta4j.bus.external; +package com.cta4j.bus.api.detour.external; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; +import java.util.Objects; + @NullMarked @ApiStatus.Internal -@SuppressWarnings("ConstantConditions") @JsonIgnoreProperties(ignoreUnknown = true) public record CtaDetoursRouteDirection( String rt, @@ -14,12 +15,7 @@ public record CtaDetoursRouteDirection( String dir ) { public CtaDetoursRouteDirection { - if (rt == null) { - throw new IllegalArgumentException("rt must not be null"); - } - - if (dir == null) { - throw new IllegalArgumentException("dir must not be null"); - } + Objects.requireNonNull(rt); + Objects.requireNonNull(dir); } } diff --git a/src/main/java/com/cta4j/bus/api/detour/impl/DetoursApiImpl.java b/src/main/java/com/cta4j/bus/api/detour/impl/DetoursApiImpl.java new file mode 100644 index 0000000..ada0ed4 --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/detour/impl/DetoursApiImpl.java @@ -0,0 +1,119 @@ +package com.cta4j.bus.api.detour.impl; + +import com.cta4j.bus.api.common.external.CtaBustimeResponse; +import com.cta4j.bus.api.common.external.CtaError; +import com.cta4j.bus.api.common.external.CtaResponse; +import com.cta4j.bus.api.common.util.ApiUtils; +import com.cta4j.bus.api.detour.DetoursApi; +import com.cta4j.bus.api.detour.external.CtaDetour; +import com.cta4j.bus.api.detour.mapper.DetourMapper; +import com.cta4j.bus.api.detour.model.Detour; +import com.cta4j.exception.Cta4jException; +import com.cta4j.util.HttpUtils; +import org.apache.hc.core5.net.URIBuilder; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; +import tools.jackson.core.JacksonException; +import tools.jackson.core.type.TypeReference; +import tools.jackson.databind.ObjectMapper; + +import java.util.List; +import java.util.Objects; + +@NullMarked +@ApiStatus.Internal +public final class DetoursApiImpl implements DetoursApi { + private static final String DETOURS_ENDPOINT = String.format("%s/getdetours", ApiUtils.API_PREFIX); + + private final String host; + private final String apiKey; + private final ObjectMapper objectMapper; + + public DetoursApiImpl(String host, String apiKey, ObjectMapper objectMapper) { + this.host = Objects.requireNonNull(host); + this.apiKey = Objects.requireNonNull(apiKey); + this.objectMapper = Objects.requireNonNull(objectMapper); + } + + @Override + public List list() { + String url = new URIBuilder() + .setScheme(ApiUtils.SCHEME) + .setHost(this.host) + .setPath(DETOURS_ENDPOINT) + .addParameter("key", this.apiKey) + .addParameter("format", "json") + .toString(); + + return this.makeRequest(url); + } + + @Override + public List findByRouteId(String routeId) { + Objects.requireNonNull(routeId); + + String url = new URIBuilder() + .setScheme(ApiUtils.SCHEME) + .setHost(this.host) + .setPath(DETOURS_ENDPOINT) + .addParameter("rt", routeId) + .addParameter("key", this.apiKey) + .addParameter("format", "json") + .toString(); + + return this.makeRequest(url); + } + + @Override + public List findByRouteIdAndDirection(String routeId, String direction) { + Objects.requireNonNull(routeId); + Objects.requireNonNull(direction); + + String url = new URIBuilder() + .setScheme(ApiUtils.SCHEME) + .setHost(this.host) + .setPath(DETOURS_ENDPOINT) + .addParameter("rt", routeId) + .addParameter("rtdir", direction) + .addParameter("key", this.apiKey) + .addParameter("format", "json") + .toString(); + + return this.makeRequest(url); + } + + private List makeRequest(String url) { + String response = HttpUtils.get(url); + + TypeReference>> typeReference = new TypeReference<>() {}; + CtaResponse> detoursResponse; + + try { + detoursResponse = this.objectMapper.readValue(response, typeReference); + } catch (JacksonException e) { + String message = String.format("Failed to parse response from %s", DETOURS_ENDPOINT); + + throw new Cta4jException(message, e); + } + + CtaBustimeResponse> bustimeResponse = detoursResponse.bustimeResponse(); + + List errors = bustimeResponse.error(); + List detours = bustimeResponse.data(); + + + if ((errors != null) && !errors.isEmpty()) { + String message = ApiUtils.buildErrorMessage(DETOURS_ENDPOINT, errors); + + throw new Cta4jException(message); + } + + if ((detours == null) || detours.isEmpty()) { + return List.of(); + } + + return detours.stream() + .map(DetourMapper.MAPPER::toDomain) + .toList(); + } +} diff --git a/src/main/java/com/cta4j/bus/mapper/DetourMapper.java b/src/main/java/com/cta4j/bus/api/detour/mapper/DetourMapper.java similarity index 67% rename from src/main/java/com/cta4j/bus/mapper/DetourMapper.java rename to src/main/java/com/cta4j/bus/api/detour/mapper/DetourMapper.java index 51a0bdd..1ab255c 100644 --- a/src/main/java/com/cta4j/bus/mapper/DetourMapper.java +++ b/src/main/java/com/cta4j/bus/api/detour/mapper/DetourMapper.java @@ -1,17 +1,20 @@ -package com.cta4j.bus.mapper; +package com.cta4j.bus.api.detour.mapper; -import com.cta4j.bus.external.CtaDetour; -import com.cta4j.bus.external.CtaDetoursRouteDirection; -import com.cta4j.bus.mapper.util.CtaBusMappingQualifiers; -import com.cta4j.bus.model.Detour; -import com.cta4j.bus.model.DetourRouteDirection; +import com.cta4j.bus.api.detour.external.CtaDetour; +import com.cta4j.bus.api.detour.external.CtaDetoursRouteDirection; +import com.cta4j.bus.api.common.util.CtaBusMappingQualifiers; +import com.cta4j.bus.api.detour.model.Detour; +import com.cta4j.bus.api.detour.model.DetourRouteDirection; import org.jetbrains.annotations.ApiStatus; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; -@ApiStatus.Internal @Mapper(uses = CtaBusMappingQualifiers.class) +@ApiStatus.Internal public interface DetourMapper { + DetourMapper MAPPER = Mappers.getMapper(DetourMapper.class); + @Mapping(source = "ver", target = "version") @Mapping(source = "st", target = "active", qualifiedByName = "mapActive") @Mapping(source = "desc", target = "description") diff --git a/src/main/java/com/cta4j/bus/api/detour/model/Detour.java b/src/main/java/com/cta4j/bus/api/detour/model/Detour.java new file mode 100644 index 0000000..8451458 --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/detour/model/Detour.java @@ -0,0 +1,39 @@ +package com.cta4j.bus.api.detour.model; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.time.Instant; +import java.util.List; +import java.util.Objects; + +@NullMarked +public record Detour( + String id, + + String version, + + boolean active, + + String description, + + List routeDirections, + + Instant startTime, + + Instant endTime, + + @Nullable + String dataFeed +) { + public Detour { + Objects.requireNonNull(id); + Objects.requireNonNull(version); + Objects.requireNonNull(description); + Objects.requireNonNull(routeDirections); + Objects.requireNonNull(startTime); + Objects.requireNonNull(endTime); + + routeDirections.forEach(Objects::requireNonNull); + } +} diff --git a/src/main/java/com/cta4j/bus/api/detour/model/DetourRouteDirection.java b/src/main/java/com/cta4j/bus/api/detour/model/DetourRouteDirection.java new file mode 100644 index 0000000..8122b87 --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/detour/model/DetourRouteDirection.java @@ -0,0 +1,17 @@ +package com.cta4j.bus.api.detour.model; + +import org.jspecify.annotations.NullMarked; + +import java.util.Objects; + +@NullMarked +public record DetourRouteDirection( + String route, + + String direction +) { + public DetourRouteDirection { + Objects.requireNonNull(route); + Objects.requireNonNull(direction); + } +} diff --git a/src/main/java/com/cta4j/bus/api/direction/impl/DirectionsApiImpl.java b/src/main/java/com/cta4j/bus/api/direction/impl/DirectionsApiImpl.java index af19062..26d576c 100644 --- a/src/main/java/com/cta4j/bus/api/direction/impl/DirectionsApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/direction/impl/DirectionsApiImpl.java @@ -1,14 +1,15 @@ package com.cta4j.bus.api.direction.impl; -import com.cta4j.bus.api.ApiUtils; +import com.cta4j.bus.api.common.util.ApiUtils; import com.cta4j.bus.api.direction.DirectionsApi; -import com.cta4j.bus.external.CtaBustimeResponse; +import com.cta4j.bus.api.common.external.CtaBustimeResponse; import com.cta4j.bus.api.direction.external.CtaDirection; -import com.cta4j.bus.external.CtaError; -import com.cta4j.bus.external.CtaResponse; +import com.cta4j.bus.api.common.external.CtaError; +import com.cta4j.bus.api.common.external.CtaResponse; import com.cta4j.exception.Cta4jException; import com.cta4j.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; +import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; import tools.jackson.core.JacksonException; import tools.jackson.core.type.TypeReference; @@ -18,6 +19,7 @@ import java.util.Objects; @NullMarked +@ApiStatus.Internal public final class DirectionsApiImpl implements DirectionsApi { private static final String DIRECTIONS_ENDPOINT = String.format("%s/getdirections", ApiUtils.API_PREFIX); @@ -30,13 +32,9 @@ public DirectionsApiImpl( String apiKey, ObjectMapper objectMapper ) { - Objects.requireNonNull(host); - Objects.requireNonNull(apiKey); - Objects.requireNonNull(objectMapper); - - this.host = host; - this.apiKey = apiKey; - this.objectMapper = objectMapper; + this.host = Objects.requireNonNull(host); + this.apiKey = Objects.requireNonNull(apiKey); + this.objectMapper = Objects.requireNonNull(objectMapper); } @Override diff --git a/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java b/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java index 5b2f630..7cb2eba 100644 --- a/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java @@ -1,27 +1,31 @@ package com.cta4j.bus.api.impl; -import com.cta4j.bus.api.ApiUtils; +import com.cta4j.bus.api.common.util.ApiUtils; import com.cta4j.bus.api.BusApi; -import com.cta4j.bus.api.DetoursApi; +import com.cta4j.bus.api.detour.DetoursApi; +import com.cta4j.bus.api.detour.impl.DetoursApiImpl; import com.cta4j.bus.api.direction.DirectionsApi; -import com.cta4j.bus.api.LocalesApi; +import com.cta4j.bus.api.locale.LocalesApi; +import com.cta4j.bus.api.locale.impl.LocalesApiImpl; import com.cta4j.bus.api.pattern.PatternsApi; import com.cta4j.bus.api.prediction.PredictionsApi; import com.cta4j.bus.api.direction.impl.DirectionsApiImpl; import com.cta4j.bus.api.pattern.impl.PatternsApiImpl; +import com.cta4j.bus.api.prediction.impl.PredictionsApiImpl; import com.cta4j.bus.api.route.RoutesApi; import com.cta4j.bus.api.stop.StopsApi; import com.cta4j.bus.api.route.impl.RoutesApiImpl; import com.cta4j.bus.api.stop.impl.StopsApiImpl; import com.cta4j.bus.api.vehicle.VehiclesApi; import com.cta4j.bus.api.vehicle.impl.VehiclesApiImpl; -import com.cta4j.bus.external.CtaBustimeResponse; -import com.cta4j.bus.external.CtaError; -import com.cta4j.bus.external.CtaResponse; -import com.cta4j.bus.mapper.util.CtaBusMappingQualifiers; +import com.cta4j.bus.api.common.external.CtaBustimeResponse; +import com.cta4j.bus.api.common.external.CtaError; +import com.cta4j.bus.api.common.external.CtaResponse; +import com.cta4j.bus.api.common.util.CtaBusMappingQualifiers; import com.cta4j.exception.Cta4jException; import com.cta4j.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; +import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import tools.jackson.core.JacksonException; @@ -33,6 +37,7 @@ import java.util.Objects; @NullMarked +@ApiStatus.Internal public final class BusApiImpl implements BusApi { private static final String SYSTEM_TIME_ENDPOINT = String.format("%s/gettime", ApiUtils.API_PREFIX); @@ -44,22 +49,25 @@ public final class BusApiImpl implements BusApi { private final DirectionsApi directionsApi; private final StopsApi stopsApi; private final PatternsApi patternsApi; + private final PredictionsApi predictionsApi; + private final LocalesApi localesApi; + private final DetoursApi detoursApi; public BusApiImpl( String host, String apiKey ) { - Objects.requireNonNull(host); - Objects.requireNonNull(apiKey); - - this.host = host; - this.apiKey = apiKey; + this.host = Objects.requireNonNull(host); + this.apiKey = Objects.requireNonNull(apiKey); this.objectMapper = new ObjectMapper(); this.vehiclesApi = new VehiclesApiImpl(this.host, this.apiKey, this.objectMapper); this.routesApi = new RoutesApiImpl(this.host, this.apiKey, this.objectMapper); this.directionsApi = new DirectionsApiImpl(this.host, this.apiKey, this.objectMapper); this.stopsApi = new StopsApiImpl(this.host, this.apiKey, this.objectMapper); this.patternsApi = new PatternsApiImpl(this.host, this.apiKey, this.objectMapper); + this.predictionsApi = new PredictionsApiImpl(this.host, this.apiKey, this.objectMapper); + this.localesApi = new LocalesApiImpl(this.host, this.apiKey, this.objectMapper); + this.detoursApi = new DetoursApiImpl(this.host, this.apiKey, this.objectMapper); } @Override @@ -141,29 +149,28 @@ public PatternsApi patterns() { @Override public PredictionsApi predictions() { - return null; + return this.predictionsApi; } @Override public LocalesApi locales() { - return null; + return this.localesApi; } @Override public DetoursApi detours() { - return null; + return this.detoursApi; } public static final class BuilderImpl implements BusApi.Builder { - @Nullable - private String host; + private final String apiKey; @Nullable - private String apiKey; + private String host; - public BuilderImpl() { + public BuilderImpl(String apiKey) { + this.apiKey = Objects.requireNonNull(apiKey); this.host = null; - this.apiKey = null; } @Override @@ -173,21 +180,10 @@ public Builder host(String host) { return this; } - @Override - public Builder apiKey(String apiKey) { - this.apiKey = Objects.requireNonNull(apiKey); - - return this; - } - @Override public BusApi build() { String finalHost = (this.host == null) ? ApiUtils.DEFAULT_HOST : this.host; - if (this.apiKey == null) { - throw new IllegalStateException("API key must not be null"); - } - return new BusApiImpl(finalHost, this.apiKey); } } diff --git a/src/main/java/com/cta4j/bus/api/locale/LocalesApi.java b/src/main/java/com/cta4j/bus/api/locale/LocalesApi.java new file mode 100644 index 0000000..efff31d --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/locale/LocalesApi.java @@ -0,0 +1,7 @@ +package com.cta4j.bus.api.locale; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface LocalesApi { +} diff --git a/src/main/java/com/cta4j/bus/api/locale/impl/LocalesApiImpl.java b/src/main/java/com/cta4j/bus/api/locale/impl/LocalesApiImpl.java new file mode 100644 index 0000000..aaafca1 --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/locale/impl/LocalesApiImpl.java @@ -0,0 +1,22 @@ +package com.cta4j.bus.api.locale.impl; + +import com.cta4j.bus.api.locale.LocalesApi; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; +import tools.jackson.databind.ObjectMapper; + +import java.util.Objects; + +@NullMarked +@ApiStatus.Internal +public final class LocalesApiImpl implements LocalesApi { + private final String host; + private final String apiKey; + private final ObjectMapper objectMapper; + + public LocalesApiImpl(String host, String apiKey, ObjectMapper objectMapper) { + this.host = Objects.requireNonNull(host); + this.apiKey = Objects.requireNonNull(apiKey); + this.objectMapper = Objects.requireNonNull(objectMapper); + } +} diff --git a/src/main/java/com/cta4j/bus/api/pattern/impl/PatternsApiImpl.java b/src/main/java/com/cta4j/bus/api/pattern/impl/PatternsApiImpl.java index e2d7073..b2694ec 100644 --- a/src/main/java/com/cta4j/bus/api/pattern/impl/PatternsApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/pattern/impl/PatternsApiImpl.java @@ -1,19 +1,18 @@ package com.cta4j.bus.api.pattern.impl; -import com.cta4j.bus.api.ApiUtils; +import com.cta4j.bus.api.common.util.ApiUtils; import com.cta4j.bus.api.pattern.PatternsApi; import com.cta4j.bus.api.pattern.external.CtaPattern; import com.cta4j.bus.api.pattern.mapper.RoutePatternMapper; import com.cta4j.bus.api.pattern.model.RoutePattern; -import com.cta4j.bus.external.CtaBustimeResponse; -import com.cta4j.bus.external.CtaError; -import com.cta4j.bus.external.CtaResponse; +import com.cta4j.bus.api.common.external.CtaBustimeResponse; +import com.cta4j.bus.api.common.external.CtaError; +import com.cta4j.bus.api.common.external.CtaResponse; import com.cta4j.exception.Cta4jException; import com.cta4j.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; -import org.mapstruct.factory.Mappers; import tools.jackson.core.JacksonException; import tools.jackson.core.type.TypeReference; import tools.jackson.databind.ObjectMapper; @@ -31,21 +30,15 @@ public final class PatternsApiImpl implements PatternsApi { private final String host; private final String apiKey; private final ObjectMapper objectMapper; - private final RoutePatternMapper routePatternMapper; public PatternsApiImpl( String host, String apiKey, ObjectMapper objectMapper ) { - Objects.requireNonNull(host); - Objects.requireNonNull(apiKey); - Objects.requireNonNull(objectMapper); - - this.host = host; - this.apiKey = apiKey; - this.objectMapper = objectMapper; - this.routePatternMapper = Mappers.getMapper(RoutePatternMapper.class); + this.host = Objects.requireNonNull(host); + this.apiKey = Objects.requireNonNull(apiKey); + this.objectMapper = Objects.requireNonNull(objectMapper); } @Override @@ -124,7 +117,7 @@ private List makeRequest(String url) { } return patterns.stream() - .map(this.routePatternMapper::toDomain) + .map(RoutePatternMapper.MAPPER::toDomain) .toList(); } } diff --git a/src/main/java/com/cta4j/bus/api/pattern/mapper/RoutePatternMapper.java b/src/main/java/com/cta4j/bus/api/pattern/mapper/RoutePatternMapper.java index 3439c9c..8d15eaa 100644 --- a/src/main/java/com/cta4j/bus/api/pattern/mapper/RoutePatternMapper.java +++ b/src/main/java/com/cta4j/bus/api/pattern/mapper/RoutePatternMapper.java @@ -2,16 +2,19 @@ import com.cta4j.bus.api.pattern.external.CtaPattern; import com.cta4j.bus.api.pattern.external.CtaPoint; -import com.cta4j.bus.mapper.util.CtaBusMappingQualifiers; +import com.cta4j.bus.api.common.util.CtaBusMappingQualifiers; import com.cta4j.bus.api.pattern.model.PatternPoint; import com.cta4j.bus.api.pattern.model.RoutePattern; import org.jetbrains.annotations.ApiStatus; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; @Mapper(uses = CtaBusMappingQualifiers.class) @ApiStatus.Internal public interface RoutePatternMapper { + RoutePatternMapper MAPPER = Mappers.getMapper(RoutePatternMapper.class); + @Mapping(source = "pid", target = "patternId") @Mapping(source = "ln", target = "patternCount") @Mapping(source = "rtdir", target = "direction") diff --git a/src/main/java/com/cta4j/bus/api/prediction/impl/PredictionsApiImpl.java b/src/main/java/com/cta4j/bus/api/prediction/impl/PredictionsApiImpl.java new file mode 100644 index 0000000..a1d7bc9 --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/prediction/impl/PredictionsApiImpl.java @@ -0,0 +1,161 @@ +package com.cta4j.bus.api.prediction.impl; + +import com.cta4j.bus.api.common.util.ApiUtils; +import com.cta4j.bus.api.prediction.PredictionsApi; +import com.cta4j.bus.api.prediction.external.CtaPrediction; +import com.cta4j.bus.api.prediction.mapper.PredictionMapper; +import com.cta4j.bus.api.prediction.model.Prediction; +import com.cta4j.bus.api.prediction.query.StopsPredictionsQuery; +import com.cta4j.bus.api.prediction.query.VehiclesPredictionsQuery; +import com.cta4j.bus.api.common.external.CtaBustimeResponse; +import com.cta4j.bus.api.common.external.CtaError; +import com.cta4j.bus.api.common.external.CtaResponse; +import com.cta4j.exception.Cta4jException; +import com.cta4j.util.HttpUtils; +import org.apache.hc.core5.net.URIBuilder; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; +import tools.jackson.core.JacksonException; +import tools.jackson.core.type.TypeReference; +import tools.jackson.databind.ObjectMapper; + +import java.util.List; +import java.util.Objects; + +@NullMarked +@ApiStatus.Internal +public final class PredictionsApiImpl implements PredictionsApi { + private static final String PREDICTIONS_ENDPOINT = String.format("%s/getpredictions", ApiUtils.API_PREFIX); + private static final int MAX_STOP_IDS_PER_REQUEST = 10; + private static final int MAX_VEHICLE_IDS_PER_REQUEST = 10; + + private final String host; + private final String apiKey; + private final ObjectMapper objectMapper; + + public PredictionsApiImpl( + String host, + String apiKey, + ObjectMapper objectMapper + ) { + this.host = Objects.requireNonNull(host); + this.apiKey = Objects.requireNonNull(apiKey); + this.objectMapper = Objects.requireNonNull(objectMapper); + } + + @Override + public List findByStopIds(StopsPredictionsQuery query) { + Objects.requireNonNull(query); + + List stopIds = query.stopIds(); + + if (stopIds.size() > MAX_STOP_IDS_PER_REQUEST) { + String message = String.format( + "A maximum of %d stop IDs can be requested at once, but %d were provided", + MAX_STOP_IDS_PER_REQUEST, + stopIds.size() + ); + + throw new IllegalArgumentException(message); + } + + String stopIdsString = String.join(",", stopIds); + + URIBuilder builder = new URIBuilder() + .setScheme(ApiUtils.SCHEME) + .setHost(this.host) + .setPath(PREDICTIONS_ENDPOINT) + .addParameter("stpid", stopIdsString) + .addParameter("tmres", "s") + .addParameter("key", this.apiKey) + .addParameter("format", "json"); + + if (query.routeIds() != null) { + String routeIdsString = String.join(",", query.routeIds()); + + builder.addParameter("rt", routeIdsString); + } + + if (query.maxResults() != null) { + String maxResultsString = String.valueOf(query.maxResults()); + + builder.addParameter("top", maxResultsString); + } + + String url = builder.toString(); + + return this.makeRequest(url); + } + + @Override + public List findByVehicleIds(VehiclesPredictionsQuery query) { + Objects.requireNonNull(query); + + List vehicleIds = query.vehicleIds(); + + if (vehicleIds.size() > MAX_VEHICLE_IDS_PER_REQUEST) { + String message = String.format( + "A maximum of %d vehicle IDs can be requested at once, but %d were provided", + MAX_STOP_IDS_PER_REQUEST, + vehicleIds.size() + ); + + throw new IllegalArgumentException(message); + } + + String vehicleIdsString = String.join(",", vehicleIds); + + URIBuilder builder = new URIBuilder() + .setScheme(ApiUtils.SCHEME) + .setHost(this.host) + .setPath(PREDICTIONS_ENDPOINT) + .addParameter("vid", vehicleIdsString) + .addParameter("tmres", "s") + .addParameter("key", this.apiKey) + .addParameter("format", "json"); + + if (query.maxResults() != null) { + String maxResultsString = String.valueOf(query.maxResults()); + + builder.addParameter("top", maxResultsString); + } + + String url = builder.toString(); + + return this.makeRequest(url); + } + + private List makeRequest(String url) { + String response = HttpUtils.get(url); + + TypeReference>> typeReference = new TypeReference<>() {}; + CtaResponse> predictionsResponse; + + try { + predictionsResponse = this.objectMapper.readValue(response, typeReference); + } catch (JacksonException e) { + String message = String.format("Failed to parse response from %s", PREDICTIONS_ENDPOINT); + + throw new Cta4jException(message, e); + } + + CtaBustimeResponse> bustimeResponse = predictionsResponse.bustimeResponse(); + + List errors = bustimeResponse.error(); + List predictions = bustimeResponse.data(); + + if ((errors != null) && !errors.isEmpty()) { + String message = ApiUtils.buildErrorMessage(PREDICTIONS_ENDPOINT, errors); + + throw new Cta4jException(message); + } + + if ((predictions == null) || predictions.isEmpty()) { + return List.of(); + } + + return predictions.stream() + .map(PredictionMapper.MAPPER::toDomain) + .toList(); + } +} diff --git a/src/main/java/com/cta4j/bus/api/prediction/mapper/PredictionMapper.java b/src/main/java/com/cta4j/bus/api/prediction/mapper/PredictionMapper.java index 6a60817..4a5573a 100644 --- a/src/main/java/com/cta4j/bus/api/prediction/mapper/PredictionMapper.java +++ b/src/main/java/com/cta4j/bus/api/prediction/mapper/PredictionMapper.java @@ -1,15 +1,18 @@ package com.cta4j.bus.api.prediction.mapper; import com.cta4j.bus.api.prediction.external.CtaPrediction; -import com.cta4j.bus.mapper.util.CtaBusMappingQualifiers; +import com.cta4j.bus.api.common.util.CtaBusMappingQualifiers; import com.cta4j.bus.api.prediction.model.Prediction; import org.jetbrains.annotations.ApiStatus; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; @Mapper(uses = CtaBusMappingQualifiers.class) @ApiStatus.Internal public interface PredictionMapper { + PredictionMapper MAPPER = Mappers.getMapper(PredictionMapper.class); + @Mapping(source = "typ", target = "predictionType", qualifiedByName = "mapPredictionType") @Mapping(source = "stpid", target = "stopId") @Mapping(source = "stpnm", target = "stopName") diff --git a/src/main/java/com/cta4j/bus/api/prediction/query/StopsPredictionsQuery.java b/src/main/java/com/cta4j/bus/api/prediction/query/StopsPredictionsQuery.java index de59b4d..588d5c1 100644 --- a/src/main/java/com/cta4j/bus/api/prediction/query/StopsPredictionsQuery.java +++ b/src/main/java/com/cta4j/bus/api/prediction/query/StopsPredictionsQuery.java @@ -3,16 +3,15 @@ import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; -import java.util.Collection; import java.util.List; import java.util.Objects; @NullMarked public record StopsPredictionsQuery( - Collection stopIds, + List stopIds, @Nullable - Collection routeIds, + List routeIds, @Nullable Integer maxResults @@ -35,20 +34,26 @@ public record StopsPredictionsQuery( } } - public static Builder builder(Collection stopIds) { + public static Builder builder(List stopIds) { + Objects.requireNonNull(stopIds); + + stopIds.forEach(Objects::requireNonNull); + + stopIds = List.copyOf(stopIds); + return new Builder(stopIds); } public static final class Builder { - private final Collection stopIds; + private final List stopIds; @Nullable - private Collection routeIds; + private List routeIds; @Nullable private Integer maxResults; - public Builder(Collection stopIds) { + public Builder(List stopIds) { Objects.requireNonNull(stopIds); stopIds.forEach(Objects::requireNonNull); @@ -56,7 +61,7 @@ public Builder(Collection stopIds) { this.stopIds = List.copyOf(stopIds); } - public Builder routeIds(Collection routeIds) { + public Builder routeIds(List routeIds) { Objects.requireNonNull(routeIds); routeIds.forEach(Objects::requireNonNull); diff --git a/src/main/java/com/cta4j/bus/api/prediction/query/VehiclesPredictionsQuery.java b/src/main/java/com/cta4j/bus/api/prediction/query/VehiclesPredictionsQuery.java index bb6e28f..39d6a38 100644 --- a/src/main/java/com/cta4j/bus/api/prediction/query/VehiclesPredictionsQuery.java +++ b/src/main/java/com/cta4j/bus/api/prediction/query/VehiclesPredictionsQuery.java @@ -3,13 +3,12 @@ import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; -import java.util.Collection; import java.util.List; import java.util.Objects; @NullMarked public record VehiclesPredictionsQuery( - Collection vehicleIds, + List vehicleIds, @Nullable Integer maxResults @@ -26,17 +25,21 @@ public record VehiclesPredictionsQuery( } } - public static Builder builder(Collection stopIds) { + public static Builder builder(List stopIds) { + Objects.requireNonNull(stopIds); + + stopIds.forEach(Objects::requireNonNull); + return new Builder(stopIds); } public static final class Builder { - private final Collection vehicleIds; + private final List vehicleIds; @Nullable private Integer maxResults; - public Builder(Collection vehicleIds) { + public Builder(List vehicleIds) { Objects.requireNonNull(vehicleIds); vehicleIds.forEach(Objects::requireNonNull); diff --git a/src/main/java/com/cta4j/bus/api/route/impl/RoutesApiImpl.java b/src/main/java/com/cta4j/bus/api/route/impl/RoutesApiImpl.java index 90b2000..77d0b27 100644 --- a/src/main/java/com/cta4j/bus/api/route/impl/RoutesApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/route/impl/RoutesApiImpl.java @@ -1,19 +1,18 @@ package com.cta4j.bus.api.route.impl; -import com.cta4j.bus.api.ApiUtils; +import com.cta4j.bus.api.common.util.ApiUtils; import com.cta4j.bus.api.route.RoutesApi; import com.cta4j.bus.api.route.external.CtaRoute; import com.cta4j.bus.api.route.mapper.RouteMapper; import com.cta4j.bus.api.route.model.Route; -import com.cta4j.bus.external.CtaBustimeResponse; -import com.cta4j.bus.external.CtaError; -import com.cta4j.bus.external.CtaResponse; +import com.cta4j.bus.api.common.external.CtaBustimeResponse; +import com.cta4j.bus.api.common.external.CtaError; +import com.cta4j.bus.api.common.external.CtaResponse; import com.cta4j.exception.Cta4jException; import com.cta4j.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; -import org.mapstruct.factory.Mappers; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import tools.jackson.core.JacksonException; @@ -33,21 +32,15 @@ public final class RoutesApiImpl implements RoutesApi { private final String host; private final String apiKey; private final ObjectMapper objectMapper; - private final RouteMapper routeMapper; public RoutesApiImpl( String host, String apiKey, ObjectMapper objectMapper ) { - Objects.requireNonNull(host); - Objects.requireNonNull(apiKey); - Objects.requireNonNull(objectMapper); - - this.host = host; - this.apiKey = apiKey; - this.objectMapper = objectMapper; - this.routeMapper = Mappers.getMapper(RouteMapper.class); + this.host = Objects.requireNonNull(host); + this.apiKey = Objects.requireNonNull(apiKey); + this.objectMapper = Objects.requireNonNull(objectMapper); } @Override @@ -95,7 +88,7 @@ public List list() { } return routes.stream() - .map(this.routeMapper::toDomain) + .map(RouteMapper.MAPPER::toDomain) .toList(); } } diff --git a/src/main/java/com/cta4j/bus/api/route/mapper/RouteMapper.java b/src/main/java/com/cta4j/bus/api/route/mapper/RouteMapper.java index 2b9f2d9..36545ed 100644 --- a/src/main/java/com/cta4j/bus/api/route/mapper/RouteMapper.java +++ b/src/main/java/com/cta4j/bus/api/route/mapper/RouteMapper.java @@ -5,10 +5,13 @@ import org.jetbrains.annotations.ApiStatus; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; @Mapper @ApiStatus.Internal public interface RouteMapper { + RouteMapper MAPPER = Mappers.getMapper(RouteMapper.class); + @Mapping(source = "rt", target = "id") @Mapping(source = "rtnm", target = "name") @Mapping(source = "rtclr", target = "color") diff --git a/src/main/java/com/cta4j/bus/api/stop/impl/StopsApiImpl.java b/src/main/java/com/cta4j/bus/api/stop/impl/StopsApiImpl.java index 6a1e1e7..e49f4cc 100644 --- a/src/main/java/com/cta4j/bus/api/stop/impl/StopsApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/stop/impl/StopsApiImpl.java @@ -1,19 +1,18 @@ package com.cta4j.bus.api.stop.impl; -import com.cta4j.bus.api.ApiUtils; +import com.cta4j.bus.api.common.util.ApiUtils; import com.cta4j.bus.api.stop.StopsApi; import com.cta4j.bus.api.stop.external.CtaStop; import com.cta4j.bus.api.stop.mapper.StopMapper; import com.cta4j.bus.api.stop.model.Stop; -import com.cta4j.bus.external.CtaBustimeResponse; -import com.cta4j.bus.external.CtaError; -import com.cta4j.bus.external.CtaResponse; +import com.cta4j.bus.api.common.external.CtaBustimeResponse; +import com.cta4j.bus.api.common.external.CtaError; +import com.cta4j.bus.api.common.external.CtaResponse; import com.cta4j.exception.Cta4jException; import com.cta4j.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; -import org.mapstruct.factory.Mappers; import tools.jackson.core.JacksonException; import tools.jackson.core.type.TypeReference; import tools.jackson.databind.ObjectMapper; @@ -31,21 +30,15 @@ public final class StopsApiImpl implements StopsApi { private final String host; private final String apiKey; private final ObjectMapper objectMapper; - private final StopMapper stopMapper; public StopsApiImpl( String host, String apiKey, ObjectMapper objectMapper ) { - Objects.requireNonNull(host); - Objects.requireNonNull(apiKey); - Objects.requireNonNull(objectMapper); - - this.host = host; - this.apiKey = apiKey; - this.objectMapper = objectMapper; - this.stopMapper = Mappers.getMapper(StopMapper.class); + this.host = Objects.requireNonNull(host); + this.apiKey = Objects.requireNonNull(apiKey); + this.objectMapper = Objects.requireNonNull(objectMapper); } @Override @@ -126,7 +119,7 @@ private List makeRequest(String url) { } return stops.stream() - .map(this.stopMapper::toDomain) + .map(StopMapper.MAPPER::toDomain) .toList(); } } diff --git a/src/main/java/com/cta4j/bus/api/stop/mapper/StopMapper.java b/src/main/java/com/cta4j/bus/api/stop/mapper/StopMapper.java index a34f19f..c98303f 100644 --- a/src/main/java/com/cta4j/bus/api/stop/mapper/StopMapper.java +++ b/src/main/java/com/cta4j/bus/api/stop/mapper/StopMapper.java @@ -5,10 +5,13 @@ import org.jetbrains.annotations.ApiStatus; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; @Mapper @ApiStatus.Internal public interface StopMapper { + StopMapper MAPPER = Mappers.getMapper(StopMapper.class); + @Mapping(source = "stpid", target = "id") @Mapping(source = "stpnm", target = "name") @Mapping(source = "lat", target = "latitude") diff --git a/src/main/java/com/cta4j/bus/api/vehicle/impl/VehiclesApiImpl.java b/src/main/java/com/cta4j/bus/api/vehicle/impl/VehiclesApiImpl.java index b1b4438..b19e80a 100644 --- a/src/main/java/com/cta4j/bus/api/vehicle/impl/VehiclesApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/vehicle/impl/VehiclesApiImpl.java @@ -1,19 +1,18 @@ package com.cta4j.bus.api.vehicle.impl; -import com.cta4j.bus.api.ApiUtils; +import com.cta4j.bus.api.common.util.ApiUtils; import com.cta4j.bus.api.vehicle.VehiclesApi; import com.cta4j.bus.api.vehicle.external.CtaVehicle; import com.cta4j.bus.api.vehicle.mapper.VehicleMapper; import com.cta4j.bus.api.vehicle.model.Vehicle; -import com.cta4j.bus.external.CtaBustimeResponse; -import com.cta4j.bus.external.CtaError; -import com.cta4j.bus.external.CtaResponse; +import com.cta4j.bus.api.common.external.CtaBustimeResponse; +import com.cta4j.bus.api.common.external.CtaError; +import com.cta4j.bus.api.common.external.CtaResponse; import com.cta4j.exception.Cta4jException; import com.cta4j.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; -import org.mapstruct.factory.Mappers; import tools.jackson.core.JacksonException; import tools.jackson.core.type.TypeReference; import tools.jackson.databind.ObjectMapper; @@ -30,21 +29,15 @@ public final class VehiclesApiImpl implements VehiclesApi { private final String host; private final String apiKey; private final ObjectMapper objectMapper; - private final VehicleMapper vehicleMapper; public VehiclesApiImpl( String host, String apiKey, ObjectMapper objectMapper ) { - Objects.requireNonNull(host); - Objects.requireNonNull(apiKey); - Objects.requireNonNull(objectMapper); - - this.host = host; - this.apiKey = apiKey; - this.objectMapper = objectMapper; - this.vehicleMapper = Mappers.getMapper(VehicleMapper.class); + this.host = Objects.requireNonNull(host); + this.apiKey = Objects.requireNonNull(apiKey); + this.objectMapper = Objects.requireNonNull(objectMapper); } @Override @@ -127,7 +120,7 @@ private List makeRequest(String url) { } return vehicles.stream() - .map(this.vehicleMapper::toDomain) + .map(VehicleMapper.MAPPER::toDomain) .toList(); } } diff --git a/src/main/java/com/cta4j/bus/api/vehicle/mapper/VehicleMapper.java b/src/main/java/com/cta4j/bus/api/vehicle/mapper/VehicleMapper.java index dcc030c..3026d3e 100644 --- a/src/main/java/com/cta4j/bus/api/vehicle/mapper/VehicleMapper.java +++ b/src/main/java/com/cta4j/bus/api/vehicle/mapper/VehicleMapper.java @@ -1,15 +1,18 @@ package com.cta4j.bus.api.vehicle.mapper; import com.cta4j.bus.api.vehicle.external.CtaVehicle; -import com.cta4j.bus.mapper.util.CtaBusMappingQualifiers; +import com.cta4j.bus.api.common.util.CtaBusMappingQualifiers; import com.cta4j.bus.api.vehicle.model.Vehicle; import org.jetbrains.annotations.ApiStatus; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; @Mapper(uses = CtaBusMappingQualifiers.class) @ApiStatus.Internal public interface VehicleMapper { + VehicleMapper MAPPER = Mappers.getMapper(VehicleMapper.class); + @Mapping(source = "vid", target = "id") @Mapping(source = "rtpidatafeed", target = "metadata.dataFeed") @Mapping(source = "tmpstmp", target = "metadata.lastUpdated", qualifiedByName = "mapTimestamp") diff --git a/src/main/java/com/cta4j/bus/client/BusClient.java b/src/main/java/com/cta4j/bus/client/BusClient.java deleted file mode 100644 index fa7ed41..0000000 --- a/src/main/java/com/cta4j/bus/client/BusClient.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.cta4j.bus.client; - -import com.cta4j.bus.client.internal.BusClientImpl; -import com.cta4j.bus.model.Detour; -import com.cta4j.exception.Cta4jException; -import org.jspecify.annotations.NullMarked; - -import java.util.List; - -/** - * A client for interacting with the CTA Bus Tracker API. - */ -@NullMarked -@SuppressWarnings("ConstantConditions") -public interface BusClient { - /** - * Finds detours for the specified route ID and direction. - * - * @param routeId the ID of the bus route - * @param direction the direction of the bus route - * @return a {@link List} of detours for the specified bus route and direction - * @throws IllegalArgumentException if the specified bus route or direction is {@code null} - * @throws Cta4jException if an error occurs while fetching the data - */ - List findDetoursByRouteIdAndDirection(String routeId, String direction); - - /** - * A builder for configuring and creating {@link BusClient} instances. - * - *

Fluent, non-thread-safe builder. Call {@link #build()} to obtain a configured client. - */ - interface Builder { - /** - * Sets the API host used by the client. - * - * @param host the host - * @return this {@link Builder} for method chaining - * @throws IllegalArgumentException if {@code host} is {@code null} - */ - Builder host(String host); - - /** - * Sets the API key used for authentication. - * - * @param apiKey the API key - * @return this {@link Builder} for method chaining - * @throws IllegalArgumentException if {@code apiKey} is {@code null} - */ - Builder apiKey(String apiKey); - - /** - * Builds a configured {@link BusClient} instance. - * - * @return a new {@link BusClient} - */ - BusClient build(); - } - - /** - * Creates a new {@link Builder} for configuring and building a {@link BusClient}. - * - * @return a new {@link Builder} instance - */ - static Builder builder() { - return new BusClientImpl.BuilderImpl(); - } -} diff --git a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java b/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java deleted file mode 100644 index 82e6f6c..0000000 --- a/src/main/java/com/cta4j/bus/client/internal/BusClientImpl.java +++ /dev/null @@ -1,171 +0,0 @@ -package com.cta4j.bus.client.internal; - -import com.cta4j.bus.client.BusClient; -import com.cta4j.bus.external.CtaBustimeResponse; -import com.cta4j.bus.external.CtaError; -import com.cta4j.bus.external.CtaResponse; -import com.cta4j.bus.api.prediction.mapper.PredictionMapper; -import com.cta4j.bus.mapper.DetourMapper; -import com.cta4j.bus.model.Detour; -import com.cta4j.exception.Cta4jException; -import com.cta4j.bus.external.CtaDetour; -import com.cta4j.util.HttpUtils; -import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; -import org.mapstruct.factory.Mappers; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import tools.jackson.core.JacksonException; -import tools.jackson.core.type.TypeReference; -import tools.jackson.databind.ObjectMapper; -import org.apache.hc.core5.net.URIBuilder; -import org.jetbrains.annotations.ApiStatus; - -import java.util.List; - -@NullMarked -@ApiStatus.Internal -@SuppressWarnings("ConstantConditions") -public final class BusClientImpl implements BusClient { - private static final Logger log = LoggerFactory.getLogger(BusClientImpl.class); - - private static final String SCHEME = "https"; - private static final String DEFAULT_HOST = "ctabustracker.com"; - private static final String API_PREFIX = "/bustime/api/v3"; - private static final String DETOURS_ENDPOINT = String.format("%s/getdetours", API_PREFIX); - - private final String host; - private final String apiKey; - private final ObjectMapper objectMapper; - private final DetourMapper detourMapper; - - private BusClientImpl(String host, String apiKey) { - if (host == null) { - throw new IllegalArgumentException("host must not be null"); - } - - if (apiKey == null) { - throw new IllegalArgumentException("apiKey must not be null"); - } - - this.host = host; - this.apiKey = apiKey; - this.objectMapper = new ObjectMapper(); - this.detourMapper = Mappers.getMapper(DetourMapper.class); - } - - @Override - public List findDetoursByRouteIdAndDirection(String routeId, String direction) { - if (routeId == null) { - throw new IllegalArgumentException("routeId must not be null"); - } - - if (direction == null) { - throw new IllegalArgumentException("direction must not be null"); - } - - String url = new URIBuilder() - .setScheme(SCHEME) - .setHost(this.host) - .setPath(DETOURS_ENDPOINT) - .addParameter("rt", routeId) - .addParameter("dir", direction) - .addParameter("tmres", "s") //TODO: Is tmres an option for detours? - .addParameter("key", this.apiKey) - .addParameter("format", "json") - .toString(); - - String response = HttpUtils.get(url); - - TypeReference>> typeReference = new TypeReference<>() {}; - CtaResponse> detoursResponse; - - try { - detoursResponse = this.objectMapper.readValue(response, typeReference); - } catch (JacksonException e) { - String message = String.format("Failed to parse response from %s", DETOURS_ENDPOINT); - - throw new Cta4jException(message, e); - } - - CtaBustimeResponse> bustimeResponse = detoursResponse.bustimeResponse(); - - List errors = bustimeResponse.error(); - List detours = bustimeResponse.data(); - - if ((errors == null) && (detours == null)) { - log.debug("Detours bustime response missing both error and data from {}", DETOURS_ENDPOINT); - - return List.of(); - } - - if ((errors != null) && !errors.isEmpty()) { - String message = this.buildErrorMessage(DETOURS_ENDPOINT, errors); - - throw new Cta4jException(message); - } - - if ((detours == null) || detours.isEmpty()) { - return List.of(); - } - - return detours.stream() - .map(this.detourMapper::toDomain) - .toList(); - } - - private String buildErrorMessage(String endpoint, List errors) { - String message = errors.stream() - .map(CtaError::msg) - .reduce("%s; %s"::formatted) - .orElse("Unknown error"); - - return String.format("Error response from %s: %s", endpoint, message); - } - - public static final class BuilderImpl implements BusClient.Builder { - @Nullable - private String host; - - @Nullable - private String apiKey; - - public BuilderImpl() { - this.host = null; - this.apiKey = null; - } - - @Override - public Builder host(String host) { - if (host == null) { - throw new IllegalArgumentException("host must not be null"); - } - - this.host = host; - - return this; - } - - @Override - public Builder apiKey(String apiKey) { - if (apiKey == null) { - throw new IllegalArgumentException("apiKey must not be null"); - } - - this.apiKey = apiKey; - - return this; - } - - @Override - public BusClient build() { - String finalHost = (this.host == null) ? DEFAULT_HOST : this.host; - - if (this.apiKey == null) { - throw new IllegalStateException("API key must not be null"); - } - - return new BusClientImpl(finalHost, this.apiKey); - } - } -} diff --git a/src/main/java/com/cta4j/bus/external/CtaDetour.java b/src/main/java/com/cta4j/bus/external/CtaDetour.java deleted file mode 100644 index 7af39d8..0000000 --- a/src/main/java/com/cta4j/bus/external/CtaDetour.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.cta4j.bus.external; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import org.jetbrains.annotations.ApiStatus; -import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; - -import java.util.List; - -@NullMarked -@ApiStatus.Internal -@SuppressWarnings("ConstantConditions") -@JsonIgnoreProperties(ignoreUnknown = true) -public record CtaDetour( - String id, - - int ver, - - int st, - - String desc, - - List rtdirs, - - String startdt, - - String enddt, - - @Nullable - String rtpidatafeed -) { - public CtaDetour { - if (id == null) { - throw new IllegalArgumentException("id must not be null"); - } - - if (desc == null) { - throw new IllegalArgumentException("desc must not be null"); - } - - if (rtdirs == null) { - throw new IllegalArgumentException("rtdirs must not be null"); - } - - if (startdt == null) { - throw new IllegalArgumentException("startdt must not be null"); - } - - if (enddt == null) { - throw new IllegalArgumentException("enddt must not be null"); - } - - for (CtaDetoursRouteDirection rtdir : rtdirs) { - if (rtdir == null) { - throw new IllegalArgumentException("rtdirs must not contain null elements"); - } - } - } -} diff --git a/src/main/java/com/cta4j/bus/model/Detour.java b/src/main/java/com/cta4j/bus/model/Detour.java deleted file mode 100644 index a062900..0000000 --- a/src/main/java/com/cta4j/bus/model/Detour.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.cta4j.bus.model; - -import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; - -import java.time.Instant; -import java.util.List; - -@NullMarked -@SuppressWarnings("ConstantConditions") -public record Detour( - String id, - - String version, - - boolean active, - - String description, - - List routeDirections, - - Instant startTime, - - Instant endTime, - - @Nullable - String dataFeed -) { - public Detour { - if (id == null) { - throw new IllegalArgumentException("id must not be null"); - } - - if (version == null) { - throw new IllegalArgumentException("version must not be null"); - } - - if (description == null) { - throw new IllegalArgumentException("designator must not be null"); - } - - if (routeDirections == null) { - throw new IllegalArgumentException("routeDirections must not be null"); - } - - if (startTime == null) { - throw new IllegalArgumentException("startTime must not be null"); - } - - if (endTime == null) { - throw new IllegalArgumentException("endTime must not be null"); - } - - for (DetourRouteDirection routeDirection : routeDirections) { - if (routeDirection == null) { - throw new IllegalArgumentException("routeDirections must not contain null elements"); - } - } - } -} diff --git a/src/main/java/com/cta4j/bus/model/DetourRouteDirection.java b/src/main/java/com/cta4j/bus/model/DetourRouteDirection.java deleted file mode 100644 index 692bc21..0000000 --- a/src/main/java/com/cta4j/bus/model/DetourRouteDirection.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.cta4j.bus.model; - -public record DetourRouteDirection( - String route, - - String direction -) { -} From 9baa1bc388a36e7351a6892789692f8173475211 Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Mon, 19 Jan 2026 12:23:53 -0600 Subject: [PATCH 41/53] API refactor --- pom.xml | 4 +- .../bus/api/detour/impl/DetoursApiImpl.java | 5 +- .../api/direction/impl/DirectionsApiImpl.java | 4 +- .../com/cta4j/bus/api/impl/BusApiImpl.java | 4 +- .../bus/api/pattern/impl/PatternsApiImpl.java | 4 +- .../prediction/impl/PredictionsApiImpl.java | 4 +- .../bus/api/route/impl/RoutesApiImpl.java | 4 +- .../java/com/cta4j/bus/api/stop/StopsApi.java | 2 +- .../cta4j/bus/api/stop/impl/StopsApiImpl.java | 4 +- .../cta4j/bus/api/vehicle/VehiclesApi.java | 2 +- .../bus/api/vehicle/impl/VehiclesApiImpl.java | 4 +- .../exception/Cta4jException.java | 2 +- .../{ => common}/util/DateTimeUtils.java | 17 +---- .../java/com/cta4j/common/util/HttpUtils.java | 44 ++++++++++++ .../com/cta4j/train/client/TrainClient.java | 2 +- .../client/internal/TrainClientImpl.java | 6 +- .../train/mapper/StationArrivalMapper.java | 2 +- .../mapper/UpcomingTrainArrivalMapper.java | 4 +- src/main/java/com/cta4j/util/HttpUtils.java | 71 ------------------- 19 files changed, 73 insertions(+), 116 deletions(-) rename src/main/java/com/cta4j/{ => common}/exception/Cta4jException.java (94%) rename src/main/java/com/cta4j/{ => common}/util/DateTimeUtils.java (56%) create mode 100644 src/main/java/com/cta4j/common/util/HttpUtils.java delete mode 100644 src/main/java/com/cta4j/util/HttpUtils.java diff --git a/pom.xml b/pom.xml index 8ae235f..10f4ed8 100644 --- a/pom.xml +++ b/pom.xml @@ -45,7 +45,7 @@ org.apache.httpcomponents.client5 - httpclient5 + httpclient5-fluent 5.6 @@ -116,7 +116,7 @@ - com.cta4j.vehicle.client.internal.*,com.cta4j.vehicle.external.*,com.cta4j.vehicle.mapper.*,com.cta4j.train.client.internal.*,com.cta4j.train.external.*,com.cta4j.train.mapper.*,com.cta4j.util.* + com.cta4j.vehicle.client.internal.*,com.cta4j.vehicle.external.*,com.cta4j.vehicle.mapper.*,com.cta4j.train.client.internal.*,com.cta4j.train.external.*,com.cta4j.train.mapper.*,com.cta4j.common.util.* diff --git a/src/main/java/com/cta4j/bus/api/detour/impl/DetoursApiImpl.java b/src/main/java/com/cta4j/bus/api/detour/impl/DetoursApiImpl.java index ada0ed4..a16545a 100644 --- a/src/main/java/com/cta4j/bus/api/detour/impl/DetoursApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/detour/impl/DetoursApiImpl.java @@ -8,8 +8,8 @@ import com.cta4j.bus.api.detour.external.CtaDetour; import com.cta4j.bus.api.detour.mapper.DetourMapper; import com.cta4j.bus.api.detour.model.Detour; -import com.cta4j.exception.Cta4jException; -import com.cta4j.util.HttpUtils; +import com.cta4j.common.exception.Cta4jException; +import com.cta4j.common.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; @@ -101,7 +101,6 @@ private List makeRequest(String url) { List errors = bustimeResponse.error(); List detours = bustimeResponse.data(); - if ((errors != null) && !errors.isEmpty()) { String message = ApiUtils.buildErrorMessage(DETOURS_ENDPOINT, errors); diff --git a/src/main/java/com/cta4j/bus/api/direction/impl/DirectionsApiImpl.java b/src/main/java/com/cta4j/bus/api/direction/impl/DirectionsApiImpl.java index 26d576c..19d6f3f 100644 --- a/src/main/java/com/cta4j/bus/api/direction/impl/DirectionsApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/direction/impl/DirectionsApiImpl.java @@ -6,8 +6,8 @@ import com.cta4j.bus.api.direction.external.CtaDirection; import com.cta4j.bus.api.common.external.CtaError; import com.cta4j.bus.api.common.external.CtaResponse; -import com.cta4j.exception.Cta4jException; -import com.cta4j.util.HttpUtils; +import com.cta4j.common.exception.Cta4jException; +import com.cta4j.common.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java b/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java index 7cb2eba..8f20d01 100644 --- a/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java @@ -22,8 +22,8 @@ import com.cta4j.bus.api.common.external.CtaError; import com.cta4j.bus.api.common.external.CtaResponse; import com.cta4j.bus.api.common.util.CtaBusMappingQualifiers; -import com.cta4j.exception.Cta4jException; -import com.cta4j.util.HttpUtils; +import com.cta4j.common.exception.Cta4jException; +import com.cta4j.common.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/com/cta4j/bus/api/pattern/impl/PatternsApiImpl.java b/src/main/java/com/cta4j/bus/api/pattern/impl/PatternsApiImpl.java index b2694ec..e2c0132 100644 --- a/src/main/java/com/cta4j/bus/api/pattern/impl/PatternsApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/pattern/impl/PatternsApiImpl.java @@ -8,8 +8,8 @@ import com.cta4j.bus.api.common.external.CtaBustimeResponse; import com.cta4j.bus.api.common.external.CtaError; import com.cta4j.bus.api.common.external.CtaResponse; -import com.cta4j.exception.Cta4jException; -import com.cta4j.util.HttpUtils; +import com.cta4j.common.exception.Cta4jException; +import com.cta4j.common.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/com/cta4j/bus/api/prediction/impl/PredictionsApiImpl.java b/src/main/java/com/cta4j/bus/api/prediction/impl/PredictionsApiImpl.java index a1d7bc9..99542f1 100644 --- a/src/main/java/com/cta4j/bus/api/prediction/impl/PredictionsApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/prediction/impl/PredictionsApiImpl.java @@ -10,8 +10,8 @@ import com.cta4j.bus.api.common.external.CtaBustimeResponse; import com.cta4j.bus.api.common.external.CtaError; import com.cta4j.bus.api.common.external.CtaResponse; -import com.cta4j.exception.Cta4jException; -import com.cta4j.util.HttpUtils; +import com.cta4j.common.exception.Cta4jException; +import com.cta4j.common.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/com/cta4j/bus/api/route/impl/RoutesApiImpl.java b/src/main/java/com/cta4j/bus/api/route/impl/RoutesApiImpl.java index 77d0b27..917ccea 100644 --- a/src/main/java/com/cta4j/bus/api/route/impl/RoutesApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/route/impl/RoutesApiImpl.java @@ -8,8 +8,8 @@ import com.cta4j.bus.api.common.external.CtaBustimeResponse; import com.cta4j.bus.api.common.external.CtaError; import com.cta4j.bus.api.common.external.CtaResponse; -import com.cta4j.exception.Cta4jException; -import com.cta4j.util.HttpUtils; +import com.cta4j.common.exception.Cta4jException; +import com.cta4j.common.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/com/cta4j/bus/api/stop/StopsApi.java b/src/main/java/com/cta4j/bus/api/stop/StopsApi.java index 89a6e70..03c1c99 100644 --- a/src/main/java/com/cta4j/bus/api/stop/StopsApi.java +++ b/src/main/java/com/cta4j/bus/api/stop/StopsApi.java @@ -1,7 +1,7 @@ package com.cta4j.bus.api.stop; import com.cta4j.bus.api.stop.model.Stop; -import com.cta4j.exception.Cta4jException; +import com.cta4j.common.exception.Cta4jException; import org.jspecify.annotations.NullMarked; import java.util.Collection; diff --git a/src/main/java/com/cta4j/bus/api/stop/impl/StopsApiImpl.java b/src/main/java/com/cta4j/bus/api/stop/impl/StopsApiImpl.java index e49f4cc..39e993b 100644 --- a/src/main/java/com/cta4j/bus/api/stop/impl/StopsApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/stop/impl/StopsApiImpl.java @@ -8,8 +8,8 @@ import com.cta4j.bus.api.common.external.CtaBustimeResponse; import com.cta4j.bus.api.common.external.CtaError; import com.cta4j.bus.api.common.external.CtaResponse; -import com.cta4j.exception.Cta4jException; -import com.cta4j.util.HttpUtils; +import com.cta4j.common.exception.Cta4jException; +import com.cta4j.common.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/com/cta4j/bus/api/vehicle/VehiclesApi.java b/src/main/java/com/cta4j/bus/api/vehicle/VehiclesApi.java index 607e033..973f6b0 100644 --- a/src/main/java/com/cta4j/bus/api/vehicle/VehiclesApi.java +++ b/src/main/java/com/cta4j/bus/api/vehicle/VehiclesApi.java @@ -1,7 +1,7 @@ package com.cta4j.bus.api.vehicle; import com.cta4j.bus.api.vehicle.model.Vehicle; -import com.cta4j.exception.Cta4jException; +import com.cta4j.common.exception.Cta4jException; import org.jspecify.annotations.NullMarked; import java.util.Collection; diff --git a/src/main/java/com/cta4j/bus/api/vehicle/impl/VehiclesApiImpl.java b/src/main/java/com/cta4j/bus/api/vehicle/impl/VehiclesApiImpl.java index b19e80a..e68e443 100644 --- a/src/main/java/com/cta4j/bus/api/vehicle/impl/VehiclesApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/vehicle/impl/VehiclesApiImpl.java @@ -8,8 +8,8 @@ import com.cta4j.bus.api.common.external.CtaBustimeResponse; import com.cta4j.bus.api.common.external.CtaError; import com.cta4j.bus.api.common.external.CtaResponse; -import com.cta4j.exception.Cta4jException; -import com.cta4j.util.HttpUtils; +import com.cta4j.common.exception.Cta4jException; +import com.cta4j.common.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/com/cta4j/exception/Cta4jException.java b/src/main/java/com/cta4j/common/exception/Cta4jException.java similarity index 94% rename from src/main/java/com/cta4j/exception/Cta4jException.java rename to src/main/java/com/cta4j/common/exception/Cta4jException.java index c2b832c..221f7c1 100644 --- a/src/main/java/com/cta4j/exception/Cta4jException.java +++ b/src/main/java/com/cta4j/common/exception/Cta4jException.java @@ -1,4 +1,4 @@ -package com.cta4j.exception; +package com.cta4j.common.exception; /** * A custom exception class for handling cta4j specific errors. diff --git a/src/main/java/com/cta4j/util/DateTimeUtils.java b/src/main/java/com/cta4j/common/util/DateTimeUtils.java similarity index 56% rename from src/main/java/com/cta4j/util/DateTimeUtils.java rename to src/main/java/com/cta4j/common/util/DateTimeUtils.java index 6b10fe7..d837eb5 100644 --- a/src/main/java/com/cta4j/util/DateTimeUtils.java +++ b/src/main/java/com/cta4j/common/util/DateTimeUtils.java @@ -1,11 +1,10 @@ -package com.cta4j.util; +package com.cta4j.common.util; import org.jetbrains.annotations.ApiStatus; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; -import java.time.format.DateTimeFormatter; @ApiStatus.Internal public final class DateTimeUtils { @@ -24,18 +23,4 @@ public static Instant parseTrainTimestamp(String timestamp) { .atZone(chicagoId) .toInstant(); } - - public static Instant parseBusTimestamp(String timestamp) { - if (timestamp == null) { - throw new IllegalArgumentException("timestamp must not be null"); - } - - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm"); - - ZoneId chicagoId = ZoneId.of("America/Chicago"); - - return LocalDateTime.parse(timestamp, formatter) - .atZone(chicagoId) - .toInstant(); - } } diff --git a/src/main/java/com/cta4j/common/util/HttpUtils.java b/src/main/java/com/cta4j/common/util/HttpUtils.java new file mode 100644 index 0000000..1b2f5b5 --- /dev/null +++ b/src/main/java/com/cta4j/common/util/HttpUtils.java @@ -0,0 +1,44 @@ +package com.cta4j.common.util; + +import com.cta4j.common.exception.Cta4jException; +import org.apache.hc.client5.http.fluent.Request; +import org.jetbrains.annotations.ApiStatus; + +import java.io.IOException; +import java.net.URI; + +@ApiStatus.Internal +public final class HttpUtils { + private HttpUtils() { + throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } + + public static String get(String url) { + URI uri; + + try { + uri = URI.create(url); + } catch (IllegalArgumentException e) { + String message = "Invalid URL"; + + throw new Cta4jException(message, e); + } + + String response; + + try { + response = Request.get(url) + .execute() + .returnContent() + .asString(); + } catch (IOException e) { + String path = uri.getPath(); + + String message = String.format("Request to %s failed due to an I/O error", path); + + throw new Cta4jException(message, e); + } + + return response; + } +} diff --git a/src/main/java/com/cta4j/train/client/TrainClient.java b/src/main/java/com/cta4j/train/client/TrainClient.java index 61efe4b..6542255 100644 --- a/src/main/java/com/cta4j/train/client/TrainClient.java +++ b/src/main/java/com/cta4j/train/client/TrainClient.java @@ -1,7 +1,7 @@ package com.cta4j.train.client; import com.cta4j.train.client.internal.TrainClientImpl; -import com.cta4j.exception.Cta4jException; +import com.cta4j.common.exception.Cta4jException; import com.cta4j.train.model.StationArrival; import com.cta4j.train.model.Train; import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/com/cta4j/train/client/internal/TrainClientImpl.java b/src/main/java/com/cta4j/train/client/internal/TrainClientImpl.java index bf780a1..44dc407 100644 --- a/src/main/java/com/cta4j/train/client/internal/TrainClientImpl.java +++ b/src/main/java/com/cta4j/train/client/internal/TrainClientImpl.java @@ -1,7 +1,7 @@ package com.cta4j.train.client.internal; import com.cta4j.train.client.TrainClient; -import com.cta4j.exception.Cta4jException; +import com.cta4j.common.exception.Cta4jException; import com.cta4j.train.external.arrival.CtaArrivalsCtatt; import com.cta4j.train.external.arrival.CtaArrivalsEta; import com.cta4j.train.external.arrival.CtaArrivalsResponse; @@ -16,12 +16,12 @@ import com.cta4j.train.model.Train; import com.cta4j.train.model.UpcomingTrainArrival; import com.cta4j.train.model.StationArrival; -import com.cta4j.util.HttpUtils; +import com.cta4j.common.util.HttpUtils; +import org.apache.hc.core5.net.URIBuilder; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import tools.jackson.core.JacksonException; import tools.jackson.databind.ObjectMapper; -import org.apache.hc.core5.net.URIBuilder; import org.jetbrains.annotations.ApiStatus; import java.util.List; diff --git a/src/main/java/com/cta4j/train/mapper/StationArrivalMapper.java b/src/main/java/com/cta4j/train/mapper/StationArrivalMapper.java index 6b4e332..812eaf0 100644 --- a/src/main/java/com/cta4j/train/mapper/StationArrivalMapper.java +++ b/src/main/java/com/cta4j/train/mapper/StationArrivalMapper.java @@ -3,7 +3,7 @@ import com.cta4j.train.external.arrival.CtaArrivalsEta; import com.cta4j.train.model.Route; import com.cta4j.train.model.StationArrival; -import com.cta4j.util.DateTimeUtils; +import com.cta4j.common.util.DateTimeUtils; import org.jetbrains.annotations.ApiStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/com/cta4j/train/mapper/UpcomingTrainArrivalMapper.java b/src/main/java/com/cta4j/train/mapper/UpcomingTrainArrivalMapper.java index d03b0a6..fa81ea8 100644 --- a/src/main/java/com/cta4j/train/mapper/UpcomingTrainArrivalMapper.java +++ b/src/main/java/com/cta4j/train/mapper/UpcomingTrainArrivalMapper.java @@ -1,10 +1,10 @@ package com.cta4j.train.mapper; -import com.cta4j.exception.Cta4jException; +import com.cta4j.common.exception.Cta4jException; import com.cta4j.train.external.follow.CtaFollowEta; import com.cta4j.train.model.Route; import com.cta4j.train.model.UpcomingTrainArrival; -import com.cta4j.util.DateTimeUtils; +import com.cta4j.common.util.DateTimeUtils; import org.jetbrains.annotations.ApiStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/com/cta4j/util/HttpUtils.java b/src/main/java/com/cta4j/util/HttpUtils.java deleted file mode 100644 index f71cd03..0000000 --- a/src/main/java/com/cta4j/util/HttpUtils.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.cta4j.util; - -import com.cta4j.exception.Cta4jException; -import org.apache.hc.client5.http.classic.methods.HttpGet; -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; -import org.apache.hc.client5.http.impl.classic.HttpClients; -import org.apache.hc.core5.http.ClassicHttpResponse; -import org.apache.hc.core5.http.HttpEntity; -import org.apache.hc.core5.http.ParseException; -import org.apache.hc.core5.http.io.entity.EntityUtils; -import org.jetbrains.annotations.ApiStatus; - -import java.io.IOException; -import java.net.URI; - -@ApiStatus.Internal -public final class HttpUtils { - private static final CloseableHttpClient httpClient; - - static { - httpClient = HttpClients.createDefault(); - } - - private HttpUtils() { - throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); - } - - private static String handleResponse(URI uri, ClassicHttpResponse httpResponse) throws IOException, ParseException { - int status = httpResponse.getCode(); - - if (status >= 200 && status < 300) { - HttpEntity entity = httpResponse.getEntity(); - - return EntityUtils.toString(entity); - } - - String path = uri.getPath(); - - String message = String.format("Request to %s failed with status code %d", path, status); - - throw new Cta4jException(message); - } - - public static String get(String url) { - URI uri; - - try { - uri = URI.create(url); - } catch (IllegalArgumentException e) { - String message = "Invalid URL"; - - throw new Cta4jException(message, e); - } - - HttpGet httpGet = new HttpGet(uri); - - String response; - - try { - response = httpClient.execute(httpGet, httpResponse -> HttpUtils.handleResponse(uri, httpResponse)); - } catch (IOException e) { - String path = uri.getPath(); - - String message = String.format("Request to %s failed due to an I/O error", path); - - throw new Cta4jException(message, e); - } - - return response; - } -} From 603b793ca9a1ab24fbfd3ecc51c79416ab9271bb Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Mon, 19 Jan 2026 12:37:47 -0600 Subject: [PATCH 42/53] API refactor --- src/main/java/com/cta4j/bus/api/BusApi.java | 72 ++++++++++++++++--- .../common/util/CtaBusMappingQualifiers.java | 18 +++-- .../com/cta4j/bus/api/detour/DetoursApi.java | 29 ++++++++ 3 files changed, 100 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/cta4j/bus/api/BusApi.java b/src/main/java/com/cta4j/bus/api/BusApi.java index 331b6fa..6892b05 100644 --- a/src/main/java/com/cta4j/bus/api/BusApi.java +++ b/src/main/java/com/cta4j/bus/api/BusApi.java @@ -14,38 +14,92 @@ import java.time.Instant; import java.util.Objects; +/** + * Primary entry point for interacting with the CTA Bus Tracker API. + *

+ * This interface provides access to the current system time as well as + * grouped sub-APIs for vehicles, routes, directions, stops, patterns, + * predictions, locales, and detours. + *

+ * Instances of {@code BusApi} are immutable and thread-safe once built. + * Use {@link #builder(String)} to construct a configured instance. + */ @NullMarked public interface BusApi { + /** + * Returns the current system time reported by the Bus Tracker API. + * + * @return the API system time as an {@link Instant} + */ Instant systemTime(); + /** + * Provides access to vehicle-related endpoints. + * + * @return the {@link VehiclesApi} + */ VehiclesApi vehicles(); + /** + * Provides access to route-related endpoints. + * + * @return the {@link RoutesApi} + */ RoutesApi routes(); + /** + * Provides access to direction-related endpoints. + * + * @return the {@link DirectionsApi} + */ DirectionsApi directions(); + /** + * Provides access to stop-related endpoints. + * + * @return the {@link StopsApi} + */ StopsApi stops(); + /** + * Provides access to route pattern–related endpoints. + * + * @return the {@link PatternsApi} + */ PatternsApi patterns(); + /** + * Provides access to prediction and arrival-related endpoints. + * + * @return the {@link PredictionsApi} + */ PredictionsApi predictions(); + /** + * Provides access to locale and language-related endpoints. + * + * @return the {@link LocalesApi} + */ LocalesApi locales(); + /** + * Provides access to detour-related endpoints. + * + * @return the {@link DetoursApi} + */ DetoursApi detours(); /** - * A builder for configuring and creating {@link BusApi} instances. - * - *

Fluent, non-thread-safe builder. Call {@link #build()} to obtain a configured client. + * Builder for constructing {@link BusApi} instances. */ interface Builder { /** - * Sets the API host used by the client. + * Sets the API host to use for requests. + *

+ * If not specified, the default CTA Bus Tracker API host is used. * - * @param host the host - * @return this {@link Builder} for method chaining - * @throws NullPointerException if {@code host} is {@code null} + * @param host the API host + * @return this builder instance */ Builder host(String host); @@ -58,9 +112,9 @@ interface Builder { } /** - * Creates a new {@link Builder} for configuring a {@link BusApi} instance. + * Creates a new {@link Builder} for constructing a {@link BusApi}. * - * @param apiKey the API key + * @param apiKey the CTA Bus Tracker API key * @return a new {@link Builder} * @throws NullPointerException if {@code apiKey} is {@code null} */ diff --git a/src/main/java/com/cta4j/bus/api/common/util/CtaBusMappingQualifiers.java b/src/main/java/com/cta4j/bus/api/common/util/CtaBusMappingQualifiers.java index 68d4e22..1129c87 100644 --- a/src/main/java/com/cta4j/bus/api/common/util/CtaBusMappingQualifiers.java +++ b/src/main/java/com/cta4j/bus/api/common/util/CtaBusMappingQualifiers.java @@ -7,13 +7,17 @@ import com.cta4j.bus.api.prediction.model.PredictionType; import com.cta4j.bus.api.vehicle.model.TransitMode; import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.mapstruct.Named; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; +import java.util.Objects; +@NullMarked @ApiStatus.Internal public final class CtaBusMappingQualifiers { private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm[:ss]"); @@ -25,9 +29,7 @@ private CtaBusMappingQualifiers() { @Named("mapPredictionType") public static PredictionType mapPredictionType(String typ) { - if (typ == null) { - throw new IllegalArgumentException("typ must not be null"); - } + Objects.requireNonNull(typ); return switch (typ) { case "A" -> PredictionType.ARRIVAL; @@ -41,7 +43,7 @@ public static PredictionType mapPredictionType(String typ) { } @Named("mapTimestamp") - public static Instant mapTimestamp(String timestamp) { + public static @Nullable Instant mapTimestamp(@Nullable String timestamp) { if (timestamp == null) { return null; } @@ -66,9 +68,7 @@ public static DynamicAction mapDynamicAction(int dyn) { @Named("mapPassengerLoad") public static PassengerLoad mapPassengerLoad(String psgld) { - if (psgld == null) { - throw new IllegalArgumentException("psgld must not be null"); - } + Objects.requireNonNull(psgld); return switch (psgld) { case "FULL" -> PassengerLoad.FULL; @@ -116,9 +116,7 @@ public static boolean mapActive(int st) { @Named("mapPatternPointType") public static PatternPointType mapPatternPointType(String type) { - if (type == null) { - throw new IllegalArgumentException("type must not be null"); - } + Objects.requireNonNull(type); return switch (type) { case "S" -> PatternPointType.STOP; diff --git a/src/main/java/com/cta4j/bus/api/detour/DetoursApi.java b/src/main/java/com/cta4j/bus/api/detour/DetoursApi.java index ffb98d1..421ea7d 100644 --- a/src/main/java/com/cta4j/bus/api/detour/DetoursApi.java +++ b/src/main/java/com/cta4j/bus/api/detour/DetoursApi.java @@ -5,11 +5,40 @@ import java.util.List; +/** + * Provides access to detour-related endpoints of the CTA BusTime API. + *

+ * This API allows retrieval of active service detours across all routes, + * or filtered by route and direction. + */ @NullMarked public interface DetoursApi { + /** + * Retrieves all active detours. + * + * @return a {@link List} of active {@link Detour} instances; + * an empty {@link List} if no detours are currently active + */ List list(); + /** + * Retrieves all active detours for the specified route. + * + * @param routeId the route identifier + * @return a {@link List} of {@link Detour} instances associated with the route; + * an empty {@link List} if no detours exist for the route + * @throws NullPointerException if {@code routeId} is {@code null} + */ List findByRouteId(String routeId); + /** + * Retrieves all active detours for the specified route and direction. + * + * @param routeId the route identifier + * @param direction the travel direction (e.g. Northbound, Southbound) + * @return a {@link List} of {@link Detour} instances matching the route and direction; + * an empty {@link List} if no detours match + * @throws NullPointerException if {@code routeId} or {@code direction} is {@code null} + */ List findByRouteIdAndDirection(String routeId, String direction); } From 3852a9bd3eb4ed5dcd2b214866ab8bb5a9475511 Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Mon, 19 Jan 2026 12:40:36 -0600 Subject: [PATCH 43/53] API refactor --- .../com/cta4j/bus/api/direction/DirectionsApi.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/java/com/cta4j/bus/api/direction/DirectionsApi.java b/src/main/java/com/cta4j/bus/api/direction/DirectionsApi.java index c65bf42..3bbb413 100644 --- a/src/main/java/com/cta4j/bus/api/direction/DirectionsApi.java +++ b/src/main/java/com/cta4j/bus/api/direction/DirectionsApi.java @@ -4,7 +4,20 @@ import java.util.List; +/** + * Provides access to direction-related endpoints of the CTA BusTime API. + *

+ * This API allows retrieval of available travel directions for a given route. + */ @NullMarked public interface DirectionsApi { + /** + * Retrieves the available travel directions for the specified route (e.g., Northbound, Southbound). + * + * @param routeId the route identifier + * @return a {@link List} of direction identifiers for the route; + * an empty {@link List} if the route has no associated directions + * @throws NullPointerException if {@code routeId} is {@code null} + */ List findByRouteId(String routeId); } From d2e553c0ee77cd9f2e28853809f913ad47ecbe15 Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sat, 24 Jan 2026 13:13:52 -0600 Subject: [PATCH 44/53] API refactor --- src/main/java/com/cta4j/bus/api/BusApi.java | 11 ++ .../bus/api/core/context/BusApiContext.java | 25 ++++ .../external/CtaBustimeResponse.java | 3 +- .../{common => core}/external/CtaError.java | 2 +- .../external/CtaResponse.java | 2 +- .../bus/api/core/request/RequestOptions.java | 16 +++ .../api/{common => core}/util/ApiUtils.java | 4 +- .../util/CtaBusMappingQualifiers.java | 10 +- .../bus/api/detour/impl/DetoursApiImpl.java | 37 +++--- .../bus/api/detour/mapper/DetourMapper.java | 4 +- .../api/direction/impl/DirectionsApiImpl.java | 33 +++--- .../com/cta4j/bus/api/impl/BusApiImpl.java | 93 ++++++++++----- .../com/cta4j/bus/api/locale/LocalesApi.java | 9 ++ .../bus/api/locale/external/CtaLocale.java | 21 ++++ .../bus/api/locale/impl/LocalesApiImpl.java | 110 ++++++++++++++++-- .../locale/mapper/SupportedLocaleMapper.java | 17 +++ .../bus/api/locale/model/SupportedLocale.java | 18 +++ .../bus/api/pattern/impl/PatternsApiImpl.java | 39 +++---- .../pattern/mapper/RoutePatternMapper.java | 4 +- .../prediction/impl/PredictionsApiImpl.java | 39 +++---- .../prediction/mapper/PredictionMapper.java | 4 +- .../bus/api/route/impl/RoutesApiImpl.java | 35 +++--- .../bus/api/route/mapper/RouteMapper.java | 2 +- .../cta4j/bus/api/stop/impl/StopsApiImpl.java | 39 +++---- .../cta4j/bus/api/stop/mapper/StopMapper.java | 2 +- .../bus/api/vehicle/impl/VehiclesApiImpl.java | 39 +++---- .../bus/api/vehicle/mapper/VehicleMapper.java | 4 +- 27 files changed, 418 insertions(+), 204 deletions(-) create mode 100644 src/main/java/com/cta4j/bus/api/core/context/BusApiContext.java rename src/main/java/com/cta4j/bus/api/{common => core}/external/CtaBustimeResponse.java (91%) rename src/main/java/com/cta4j/bus/api/{common => core}/external/CtaError.java (89%) rename src/main/java/com/cta4j/bus/api/{common => core}/external/CtaResponse.java (92%) create mode 100644 src/main/java/com/cta4j/bus/api/core/request/RequestOptions.java rename src/main/java/com/cta4j/bus/api/{common => core}/util/ApiUtils.java (91%) rename src/main/java/com/cta4j/bus/api/{common => core}/util/CtaBusMappingQualifiers.java (95%) create mode 100644 src/main/java/com/cta4j/bus/api/locale/external/CtaLocale.java create mode 100644 src/main/java/com/cta4j/bus/api/locale/mapper/SupportedLocaleMapper.java create mode 100644 src/main/java/com/cta4j/bus/api/locale/model/SupportedLocale.java diff --git a/src/main/java/com/cta4j/bus/api/BusApi.java b/src/main/java/com/cta4j/bus/api/BusApi.java index 6892b05..87b3810 100644 --- a/src/main/java/com/cta4j/bus/api/BusApi.java +++ b/src/main/java/com/cta4j/bus/api/BusApi.java @@ -1,5 +1,6 @@ package com.cta4j.bus.api; +import com.cta4j.bus.api.core.request.RequestOptions; import com.cta4j.bus.api.detour.DetoursApi; import com.cta4j.bus.api.direction.DirectionsApi; import com.cta4j.bus.api.impl.BusApiImpl; @@ -103,6 +104,16 @@ interface Builder { */ Builder host(String host); + /** + * Sets the default request options to use for requests. + *

+ * If not specified, default request options are used. + * + * @param requestOptions the default request options + * @return this builder instance + */ + Builder defaultRequestOptions(RequestOptions requestOptions); + /** * Builds a configured {@link BusApi} instance. * diff --git a/src/main/java/com/cta4j/bus/api/core/context/BusApiContext.java b/src/main/java/com/cta4j/bus/api/core/context/BusApiContext.java new file mode 100644 index 0000000..b8a7207 --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/core/context/BusApiContext.java @@ -0,0 +1,25 @@ +package com.cta4j.bus.api.core.context; + +import com.cta4j.bus.api.core.request.RequestOptions; +import org.jspecify.annotations.NullMarked; +import tools.jackson.databind.ObjectMapper; + +import java.util.Objects; + +@NullMarked +public record BusApiContext( + String host, + + String apiKey, + + RequestOptions defaultRequestOptions, + + ObjectMapper objectMapper +) { + public BusApiContext { + Objects.requireNonNull(host); + Objects.requireNonNull(apiKey); + Objects.requireNonNull(defaultRequestOptions); + Objects.requireNonNull(objectMapper); + } +} diff --git a/src/main/java/com/cta4j/bus/api/common/external/CtaBustimeResponse.java b/src/main/java/com/cta4j/bus/api/core/external/CtaBustimeResponse.java similarity index 91% rename from src/main/java/com/cta4j/bus/api/common/external/CtaBustimeResponse.java rename to src/main/java/com/cta4j/bus/api/core/external/CtaBustimeResponse.java index 36a36e4..1be0939 100644 --- a/src/main/java/com/cta4j/bus/api/common/external/CtaBustimeResponse.java +++ b/src/main/java/com/cta4j/bus/api/core/external/CtaBustimeResponse.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.common.external; +package com.cta4j.bus.api.core.external; import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @@ -25,6 +25,7 @@ public record CtaBustimeResponse( "prd", "dtrs", "vehicle", + "locale" }) T data ) { diff --git a/src/main/java/com/cta4j/bus/api/common/external/CtaError.java b/src/main/java/com/cta4j/bus/api/core/external/CtaError.java similarity index 89% rename from src/main/java/com/cta4j/bus/api/common/external/CtaError.java rename to src/main/java/com/cta4j/bus/api/core/external/CtaError.java index 9a784ff..822f4c9 100644 --- a/src/main/java/com/cta4j/bus/api/common/external/CtaError.java +++ b/src/main/java/com/cta4j/bus/api/core/external/CtaError.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.common.external; +package com.cta4j.bus.api.core.external; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.jetbrains.annotations.ApiStatus; diff --git a/src/main/java/com/cta4j/bus/api/common/external/CtaResponse.java b/src/main/java/com/cta4j/bus/api/core/external/CtaResponse.java similarity index 92% rename from src/main/java/com/cta4j/bus/api/common/external/CtaResponse.java rename to src/main/java/com/cta4j/bus/api/core/external/CtaResponse.java index 8dc888a..b68c8ae 100644 --- a/src/main/java/com/cta4j/bus/api/common/external/CtaResponse.java +++ b/src/main/java/com/cta4j/bus/api/core/external/CtaResponse.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.common.external; +package com.cta4j.bus.api.core.external; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/com/cta4j/bus/api/core/request/RequestOptions.java b/src/main/java/com/cta4j/bus/api/core/request/RequestOptions.java new file mode 100644 index 0000000..61c9ff7 --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/core/request/RequestOptions.java @@ -0,0 +1,16 @@ +package com.cta4j.bus.api.core.request; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.util.Locale; + +@NullMarked +public record RequestOptions( + @Nullable + Locale locale +) { + public RequestOptions() { + this(null); + } +} diff --git a/src/main/java/com/cta4j/bus/api/common/util/ApiUtils.java b/src/main/java/com/cta4j/bus/api/core/util/ApiUtils.java similarity index 91% rename from src/main/java/com/cta4j/bus/api/common/util/ApiUtils.java rename to src/main/java/com/cta4j/bus/api/core/util/ApiUtils.java index a5fb550..9e0c9a4 100644 --- a/src/main/java/com/cta4j/bus/api/common/util/ApiUtils.java +++ b/src/main/java/com/cta4j/bus/api/core/util/ApiUtils.java @@ -1,6 +1,6 @@ -package com.cta4j.bus.api.common.util; +package com.cta4j.bus.api.core.util; -import com.cta4j.bus.api.common.external.CtaError; +import com.cta4j.bus.api.core.external.CtaError; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/com/cta4j/bus/api/common/util/CtaBusMappingQualifiers.java b/src/main/java/com/cta4j/bus/api/core/util/CtaBusMappingQualifiers.java similarity index 95% rename from src/main/java/com/cta4j/bus/api/common/util/CtaBusMappingQualifiers.java rename to src/main/java/com/cta4j/bus/api/core/util/CtaBusMappingQualifiers.java index 1129c87..10d80a3 100644 --- a/src/main/java/com/cta4j/bus/api/common/util/CtaBusMappingQualifiers.java +++ b/src/main/java/com/cta4j/bus/api/core/util/CtaBusMappingQualifiers.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.common.util; +package com.cta4j.bus.api.core.util; import com.cta4j.bus.api.prediction.model.DynamicAction; import com.cta4j.bus.api.prediction.model.FlagStop; @@ -15,6 +15,7 @@ import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; +import java.util.Locale; import java.util.Objects; @NullMarked @@ -128,4 +129,11 @@ public static PatternPointType mapPatternPointType(String type) { } }; } + + @Named("mapLocale") + public static Locale mapLocale(String locale) { + Objects.requireNonNull(locale); + + return Locale.of(locale); + } } diff --git a/src/main/java/com/cta4j/bus/api/detour/impl/DetoursApiImpl.java b/src/main/java/com/cta4j/bus/api/detour/impl/DetoursApiImpl.java index a16545a..f6cf6c1 100644 --- a/src/main/java/com/cta4j/bus/api/detour/impl/DetoursApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/detour/impl/DetoursApiImpl.java @@ -1,9 +1,10 @@ package com.cta4j.bus.api.detour.impl; -import com.cta4j.bus.api.common.external.CtaBustimeResponse; -import com.cta4j.bus.api.common.external.CtaError; -import com.cta4j.bus.api.common.external.CtaResponse; -import com.cta4j.bus.api.common.util.ApiUtils; +import com.cta4j.bus.api.core.context.BusApiContext; +import com.cta4j.bus.api.core.external.CtaBustimeResponse; +import com.cta4j.bus.api.core.external.CtaError; +import com.cta4j.bus.api.core.external.CtaResponse; +import com.cta4j.bus.api.core.util.ApiUtils; import com.cta4j.bus.api.detour.DetoursApi; import com.cta4j.bus.api.detour.external.CtaDetour; import com.cta4j.bus.api.detour.mapper.DetourMapper; @@ -15,7 +16,6 @@ import org.jspecify.annotations.NullMarked; import tools.jackson.core.JacksonException; import tools.jackson.core.type.TypeReference; -import tools.jackson.databind.ObjectMapper; import java.util.List; import java.util.Objects; @@ -25,23 +25,19 @@ public final class DetoursApiImpl implements DetoursApi { private static final String DETOURS_ENDPOINT = String.format("%s/getdetours", ApiUtils.API_PREFIX); - private final String host; - private final String apiKey; - private final ObjectMapper objectMapper; + private final BusApiContext context; - public DetoursApiImpl(String host, String apiKey, ObjectMapper objectMapper) { - this.host = Objects.requireNonNull(host); - this.apiKey = Objects.requireNonNull(apiKey); - this.objectMapper = Objects.requireNonNull(objectMapper); + public DetoursApiImpl(BusApiContext context) { + this.context = Objects.requireNonNull(context); } @Override public List list() { String url = new URIBuilder() .setScheme(ApiUtils.SCHEME) - .setHost(this.host) + .setHost(this.context.host()) .setPath(DETOURS_ENDPOINT) - .addParameter("key", this.apiKey) + .addParameter("key", this.context.apiKey()) .addParameter("format", "json") .toString(); @@ -54,10 +50,10 @@ public List findByRouteId(String routeId) { String url = new URIBuilder() .setScheme(ApiUtils.SCHEME) - .setHost(this.host) + .setHost(this.context.host()) .setPath(DETOURS_ENDPOINT) .addParameter("rt", routeId) - .addParameter("key", this.apiKey) + .addParameter("key", this.context.apiKey()) .addParameter("format", "json") .toString(); @@ -71,11 +67,11 @@ public List findByRouteIdAndDirection(String routeId, String direction) String url = new URIBuilder() .setScheme(ApiUtils.SCHEME) - .setHost(this.host) + .setHost(this.context.host()) .setPath(DETOURS_ENDPOINT) .addParameter("rt", routeId) .addParameter("rtdir", direction) - .addParameter("key", this.apiKey) + .addParameter("key", this.context.apiKey()) .addParameter("format", "json") .toString(); @@ -89,7 +85,8 @@ private List makeRequest(String url) { CtaResponse> detoursResponse; try { - detoursResponse = this.objectMapper.readValue(response, typeReference); + detoursResponse = this.context.objectMapper() + .readValue(response, typeReference); } catch (JacksonException e) { String message = String.format("Failed to parse response from %s", DETOURS_ENDPOINT); @@ -112,7 +109,7 @@ private List makeRequest(String url) { } return detours.stream() - .map(DetourMapper.MAPPER::toDomain) + .map(DetourMapper.INSTANCE::toDomain) .toList(); } } diff --git a/src/main/java/com/cta4j/bus/api/detour/mapper/DetourMapper.java b/src/main/java/com/cta4j/bus/api/detour/mapper/DetourMapper.java index 1ab255c..561ef63 100644 --- a/src/main/java/com/cta4j/bus/api/detour/mapper/DetourMapper.java +++ b/src/main/java/com/cta4j/bus/api/detour/mapper/DetourMapper.java @@ -2,7 +2,7 @@ import com.cta4j.bus.api.detour.external.CtaDetour; import com.cta4j.bus.api.detour.external.CtaDetoursRouteDirection; -import com.cta4j.bus.api.common.util.CtaBusMappingQualifiers; +import com.cta4j.bus.api.core.util.CtaBusMappingQualifiers; import com.cta4j.bus.api.detour.model.Detour; import com.cta4j.bus.api.detour.model.DetourRouteDirection; import org.jetbrains.annotations.ApiStatus; @@ -13,7 +13,7 @@ @Mapper(uses = CtaBusMappingQualifiers.class) @ApiStatus.Internal public interface DetourMapper { - DetourMapper MAPPER = Mappers.getMapper(DetourMapper.class); + DetourMapper INSTANCE = Mappers.getMapper(DetourMapper.class); @Mapping(source = "ver", target = "version") @Mapping(source = "st", target = "active", qualifiedByName = "mapActive") diff --git a/src/main/java/com/cta4j/bus/api/direction/impl/DirectionsApiImpl.java b/src/main/java/com/cta4j/bus/api/direction/impl/DirectionsApiImpl.java index 19d6f3f..f87192d 100644 --- a/src/main/java/com/cta4j/bus/api/direction/impl/DirectionsApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/direction/impl/DirectionsApiImpl.java @@ -1,11 +1,12 @@ package com.cta4j.bus.api.direction.impl; -import com.cta4j.bus.api.common.util.ApiUtils; +import com.cta4j.bus.api.core.context.BusApiContext; +import com.cta4j.bus.api.core.util.ApiUtils; import com.cta4j.bus.api.direction.DirectionsApi; -import com.cta4j.bus.api.common.external.CtaBustimeResponse; +import com.cta4j.bus.api.core.external.CtaBustimeResponse; import com.cta4j.bus.api.direction.external.CtaDirection; -import com.cta4j.bus.api.common.external.CtaError; -import com.cta4j.bus.api.common.external.CtaResponse; +import com.cta4j.bus.api.core.external.CtaError; +import com.cta4j.bus.api.core.external.CtaResponse; import com.cta4j.common.exception.Cta4jException; import com.cta4j.common.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; @@ -13,7 +14,6 @@ import org.jspecify.annotations.NullMarked; import tools.jackson.core.JacksonException; import tools.jackson.core.type.TypeReference; -import tools.jackson.databind.ObjectMapper; import java.util.List; import java.util.Objects; @@ -23,18 +23,10 @@ public final class DirectionsApiImpl implements DirectionsApi { private static final String DIRECTIONS_ENDPOINT = String.format("%s/getdirections", ApiUtils.API_PREFIX); - private final String host; - private final String apiKey; - private final ObjectMapper objectMapper; - - public DirectionsApiImpl( - String host, - String apiKey, - ObjectMapper objectMapper - ) { - this.host = Objects.requireNonNull(host); - this.apiKey = Objects.requireNonNull(apiKey); - this.objectMapper = Objects.requireNonNull(objectMapper); + private final BusApiContext context; + + public DirectionsApiImpl(BusApiContext context) { + this.context = Objects.requireNonNull(context); } @Override @@ -43,10 +35,10 @@ public List findByRouteId(String routeId) { String url = new URIBuilder() .setScheme(ApiUtils.SCHEME) - .setHost(this.host) + .setHost(this.context.host()) .setPath(DIRECTIONS_ENDPOINT) .addParameter("rt", routeId) - .addParameter("key", this.apiKey) + .addParameter("key", this.context.apiKey()) .addParameter("format", "json") .toString(); @@ -56,7 +48,8 @@ public List findByRouteId(String routeId) { CtaResponse> directionsResponse; try { - directionsResponse = this.objectMapper.readValue(response, typeReference); + directionsResponse = this.context.objectMapper() + .readValue(response, typeReference); } catch (JacksonException e) { String message = String.format("Failed to parse response from %s", DIRECTIONS_ENDPOINT); diff --git a/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java b/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java index 8f20d01..5049baa 100644 --- a/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java @@ -1,6 +1,8 @@ package com.cta4j.bus.api.impl; -import com.cta4j.bus.api.common.util.ApiUtils; +import com.cta4j.bus.api.core.context.BusApiContext; +import com.cta4j.bus.api.core.request.RequestOptions; +import com.cta4j.bus.api.core.util.ApiUtils; import com.cta4j.bus.api.BusApi; import com.cta4j.bus.api.detour.DetoursApi; import com.cta4j.bus.api.detour.impl.DetoursApiImpl; @@ -18,10 +20,10 @@ import com.cta4j.bus.api.stop.impl.StopsApiImpl; import com.cta4j.bus.api.vehicle.VehiclesApi; import com.cta4j.bus.api.vehicle.impl.VehiclesApiImpl; -import com.cta4j.bus.api.common.external.CtaBustimeResponse; -import com.cta4j.bus.api.common.external.CtaError; -import com.cta4j.bus.api.common.external.CtaResponse; -import com.cta4j.bus.api.common.util.CtaBusMappingQualifiers; +import com.cta4j.bus.api.core.external.CtaBustimeResponse; +import com.cta4j.bus.api.core.external.CtaError; +import com.cta4j.bus.api.core.external.CtaResponse; +import com.cta4j.bus.api.core.util.CtaBusMappingQualifiers; import com.cta4j.common.exception.Cta4jException; import com.cta4j.common.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; @@ -41,9 +43,7 @@ public final class BusApiImpl implements BusApi { private static final String SYSTEM_TIME_ENDPOINT = String.format("%s/gettime", ApiUtils.API_PREFIX); - private final String host; - private final String apiKey; - private final ObjectMapper objectMapper; + private final BusApiContext context; private final VehiclesApi vehiclesApi; private final RoutesApi routesApi; private final DirectionsApi directionsApi; @@ -55,28 +55,38 @@ public final class BusApiImpl implements BusApi { public BusApiImpl( String host, - String apiKey + String apiKey, + RequestOptions defaultRequestOptions ) { - this.host = Objects.requireNonNull(host); - this.apiKey = Objects.requireNonNull(apiKey); - this.objectMapper = new ObjectMapper(); - this.vehiclesApi = new VehiclesApiImpl(this.host, this.apiKey, this.objectMapper); - this.routesApi = new RoutesApiImpl(this.host, this.apiKey, this.objectMapper); - this.directionsApi = new DirectionsApiImpl(this.host, this.apiKey, this.objectMapper); - this.stopsApi = new StopsApiImpl(this.host, this.apiKey, this.objectMapper); - this.patternsApi = new PatternsApiImpl(this.host, this.apiKey, this.objectMapper); - this.predictionsApi = new PredictionsApiImpl(this.host, this.apiKey, this.objectMapper); - this.localesApi = new LocalesApiImpl(this.host, this.apiKey, this.objectMapper); - this.detoursApi = new DetoursApiImpl(this.host, this.apiKey, this.objectMapper); + Objects.requireNonNull(host); + Objects.requireNonNull(apiKey); + Objects.requireNonNull(defaultRequestOptions); + + ObjectMapper objectMapper = new ObjectMapper(); + + this.context = new BusApiContext( + host, + apiKey, + defaultRequestOptions, + objectMapper + ); + this.vehiclesApi = new VehiclesApiImpl(this.context); + this.routesApi = new RoutesApiImpl(this.context); + this.directionsApi = new DirectionsApiImpl(this.context); + this.stopsApi = new StopsApiImpl(this.context); + this.patternsApi = new PatternsApiImpl(this.context); + this.predictionsApi = new PredictionsApiImpl(this.context); + this.localesApi = new LocalesApiImpl(this.context); + this.detoursApi = new DetoursApiImpl(this.context); } @Override public Instant systemTime() { String url = new URIBuilder() .setScheme(ApiUtils.SCHEME) - .setHost(this.host) + .setHost(this.context.host()) .setPath(SYSTEM_TIME_ENDPOINT) - .addParameter("key", this.apiKey) + .addParameter("key", this.context.apiKey()) .addParameter("format", "json") .toString(); @@ -86,7 +96,8 @@ public Instant systemTime() { CtaResponse timeResponse; try { - timeResponse = this.objectMapper.readValue(response, typeReference); + timeResponse = this.context.objectMapper() + .readValue(response, typeReference); } catch (JacksonException e) { String message = String.format("Failed to parse response from %s", SYSTEM_TIME_ENDPOINT); @@ -119,7 +130,19 @@ public Instant systemTime() { throw new Cta4jException(message); } - return CtaBusMappingQualifiers.mapTimestamp(systemTime); + Instant systemInstant = CtaBusMappingQualifiers.mapTimestamp(systemTime); + + if (systemInstant == null) { + String message = String.format( + "Failed to map system time '%s' to Instant from %s", + systemTime, + SYSTEM_TIME_ENDPOINT + ); + + throw new Cta4jException(message); + } + + return systemInstant; } @Override @@ -168,9 +191,13 @@ public static final class BuilderImpl implements BusApi.Builder { @Nullable private String host; + @Nullable + private RequestOptions defaultRequestOptions; + public BuilderImpl(String apiKey) { this.apiKey = Objects.requireNonNull(apiKey); this.host = null; + this.defaultRequestOptions = null; } @Override @@ -180,11 +207,25 @@ public Builder host(String host) { return this; } + @Override + public Builder defaultRequestOptions(RequestOptions requestOptions) { + this.defaultRequestOptions = Objects.requireNonNull(requestOptions); + + return this; + } + @Override public BusApi build() { - String finalHost = (this.host == null) ? ApiUtils.DEFAULT_HOST : this.host; + String finalHost = Objects.requireNonNullElse( + this.host, + ApiUtils.DEFAULT_HOST + ); + RequestOptions finalRequestOptions = Objects.requireNonNullElse( + this.defaultRequestOptions, + new RequestOptions() + ); - return new BusApiImpl(finalHost, this.apiKey); + return new BusApiImpl(finalHost, this.apiKey, finalRequestOptions); } } } diff --git a/src/main/java/com/cta4j/bus/api/locale/LocalesApi.java b/src/main/java/com/cta4j/bus/api/locale/LocalesApi.java index efff31d..f801099 100644 --- a/src/main/java/com/cta4j/bus/api/locale/LocalesApi.java +++ b/src/main/java/com/cta4j/bus/api/locale/LocalesApi.java @@ -1,7 +1,16 @@ package com.cta4j.bus.api.locale; +import com.cta4j.bus.api.locale.model.SupportedLocale; import org.jspecify.annotations.NullMarked; +import java.util.List; +import java.util.Locale; + @NullMarked public interface LocalesApi { + List getLocales(); + + List getLocales(Locale displayLocale); + + List getLocalesInNativeLanguage(); } diff --git a/src/main/java/com/cta4j/bus/api/locale/external/CtaLocale.java b/src/main/java/com/cta4j/bus/api/locale/external/CtaLocale.java new file mode 100644 index 0000000..2a81083 --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/locale/external/CtaLocale.java @@ -0,0 +1,21 @@ +package com.cta4j.bus.api.locale.external; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; + +import java.util.Objects; + +@NullMarked +@ApiStatus.Internal +@JsonIgnoreProperties(ignoreUnknown = true) +public record CtaLocale( + String localestring, + + String displayname +) { + public CtaLocale { + Objects.requireNonNull(localestring); + Objects.requireNonNull(displayname); + } +} diff --git a/src/main/java/com/cta4j/bus/api/locale/impl/LocalesApiImpl.java b/src/main/java/com/cta4j/bus/api/locale/impl/LocalesApiImpl.java index aaafca1..480a793 100644 --- a/src/main/java/com/cta4j/bus/api/locale/impl/LocalesApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/locale/impl/LocalesApiImpl.java @@ -1,22 +1,114 @@ package com.cta4j.bus.api.locale.impl; +import com.cta4j.bus.api.core.context.BusApiContext; +import com.cta4j.bus.api.core.external.CtaBustimeResponse; +import com.cta4j.bus.api.core.external.CtaError; +import com.cta4j.bus.api.core.external.CtaResponse; +import com.cta4j.bus.api.core.util.ApiUtils; import com.cta4j.bus.api.locale.LocalesApi; +import com.cta4j.bus.api.locale.external.CtaLocale; +import com.cta4j.bus.api.locale.mapper.SupportedLocaleMapper; +import com.cta4j.bus.api.locale.model.SupportedLocale; +import com.cta4j.common.exception.Cta4jException; +import com.cta4j.common.util.HttpUtils; +import org.apache.hc.core5.net.URIBuilder; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; -import tools.jackson.databind.ObjectMapper; +import tools.jackson.core.JacksonException; +import tools.jackson.core.type.TypeReference; +import java.util.List; +import java.util.Locale; import java.util.Objects; @NullMarked @ApiStatus.Internal public final class LocalesApiImpl implements LocalesApi { - private final String host; - private final String apiKey; - private final ObjectMapper objectMapper; - - public LocalesApiImpl(String host, String apiKey, ObjectMapper objectMapper) { - this.host = Objects.requireNonNull(host); - this.apiKey = Objects.requireNonNull(apiKey); - this.objectMapper = Objects.requireNonNull(objectMapper); + private static final String LOCALES_ENDPOINT = String.format("%s/getlocalelist", ApiUtils.API_PREFIX); + + private final BusApiContext context; + + public LocalesApiImpl(BusApiContext context) { + this.context = Objects.requireNonNull(context); + } + + @Override + public List getLocales() { + String uri = new URIBuilder() + .setScheme(ApiUtils.SCHEME) + .setHost(this.context.host()) + .setPath(LOCALES_ENDPOINT) + .addParameter("key", this.context.apiKey()) + .addParameter("format", "json") + .toString(); + + return this.makeRequest(uri); + } + + @Override + public List getLocales(Locale displayLocale) { + Objects.requireNonNull(displayLocale); + + String languageTag = displayLocale.toLanguageTag(); + + String uri = new URIBuilder() + .setScheme(ApiUtils.SCHEME) + .setHost(this.context.host()) + .setPath(LOCALES_ENDPOINT) + .addParameter("key", this.context.apiKey()) + .addParameter("format", "json") + .addParameter("locale", languageTag) + .toString(); + + return this.makeRequest(uri); + } + + @Override + public List getLocalesInNativeLanguage() { + String uri = new URIBuilder() + .setScheme(ApiUtils.SCHEME) + .setHost(this.context.host()) + .setPath(LOCALES_ENDPOINT) + .addParameter("key", this.context.apiKey()) + .addParameter("format", "json") + .addParameter("inLocaleLanguage", "true") + .toString(); + + return this.makeRequest(uri); + } + + private List makeRequest(String url) { + String response = HttpUtils.get(url); + + TypeReference>> typeReference = new TypeReference<>() {}; + CtaResponse> localeResponse; + + try { + localeResponse = this.context.objectMapper() + .readValue(response, typeReference); + } catch (JacksonException e) { + String message = String.format("Failed to parse response from %s", LOCALES_ENDPOINT); + + throw new Cta4jException(message, e); + } + + CtaBustimeResponse> bustimeResponse = localeResponse.bustimeResponse(); + + List errors = bustimeResponse.error(); + List locales = bustimeResponse.data(); + + if ((errors != null) && !errors.isEmpty()) { + String message = ApiUtils.buildErrorMessage(LOCALES_ENDPOINT, errors); + + throw new Cta4jException(message); + } + + if ((locales == null) || locales.isEmpty()) { + return List.of(); + } + + return locales.stream() + .map(SupportedLocaleMapper.INSTANCE::toDomain) + .toList(); } } diff --git a/src/main/java/com/cta4j/bus/api/locale/mapper/SupportedLocaleMapper.java b/src/main/java/com/cta4j/bus/api/locale/mapper/SupportedLocaleMapper.java new file mode 100644 index 0000000..939de4e --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/locale/mapper/SupportedLocaleMapper.java @@ -0,0 +1,17 @@ +package com.cta4j.bus.api.locale.mapper; + +import com.cta4j.bus.api.core.util.CtaBusMappingQualifiers; +import com.cta4j.bus.api.locale.external.CtaLocale; +import com.cta4j.bus.api.locale.model.SupportedLocale; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper(uses = CtaBusMappingQualifiers.class) +public interface SupportedLocaleMapper { + SupportedLocaleMapper INSTANCE = Mappers.getMapper(SupportedLocaleMapper.class); + + @Mapping(source = "localestring", target = "locale", qualifiedByName = "mapLocale") + @Mapping(source = "displayname", target = "displayName") + SupportedLocale toDomain(CtaLocale locale); +} diff --git a/src/main/java/com/cta4j/bus/api/locale/model/SupportedLocale.java b/src/main/java/com/cta4j/bus/api/locale/model/SupportedLocale.java new file mode 100644 index 0000000..3552e21 --- /dev/null +++ b/src/main/java/com/cta4j/bus/api/locale/model/SupportedLocale.java @@ -0,0 +1,18 @@ +package com.cta4j.bus.api.locale.model; + +import org.jspecify.annotations.NullMarked; + +import java.util.Locale; +import java.util.Objects; + +@NullMarked +public record SupportedLocale( + Locale locale, + + String displayName +) { + public SupportedLocale { + Objects.requireNonNull(locale); + Objects.requireNonNull(displayName); + } +} diff --git a/src/main/java/com/cta4j/bus/api/pattern/impl/PatternsApiImpl.java b/src/main/java/com/cta4j/bus/api/pattern/impl/PatternsApiImpl.java index e2c0132..cb4ff45 100644 --- a/src/main/java/com/cta4j/bus/api/pattern/impl/PatternsApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/pattern/impl/PatternsApiImpl.java @@ -1,13 +1,14 @@ package com.cta4j.bus.api.pattern.impl; -import com.cta4j.bus.api.common.util.ApiUtils; +import com.cta4j.bus.api.core.context.BusApiContext; +import com.cta4j.bus.api.core.util.ApiUtils; import com.cta4j.bus.api.pattern.PatternsApi; import com.cta4j.bus.api.pattern.external.CtaPattern; import com.cta4j.bus.api.pattern.mapper.RoutePatternMapper; import com.cta4j.bus.api.pattern.model.RoutePattern; -import com.cta4j.bus.api.common.external.CtaBustimeResponse; -import com.cta4j.bus.api.common.external.CtaError; -import com.cta4j.bus.api.common.external.CtaResponse; +import com.cta4j.bus.api.core.external.CtaBustimeResponse; +import com.cta4j.bus.api.core.external.CtaError; +import com.cta4j.bus.api.core.external.CtaResponse; import com.cta4j.common.exception.Cta4jException; import com.cta4j.common.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; @@ -15,7 +16,6 @@ import org.jspecify.annotations.NullMarked; import tools.jackson.core.JacksonException; import tools.jackson.core.type.TypeReference; -import tools.jackson.databind.ObjectMapper; import java.util.Collection; import java.util.List; @@ -27,18 +27,10 @@ public final class PatternsApiImpl implements PatternsApi { private static final String PATTERNS_ENDPOINT = String.format("%s/getpatterns", ApiUtils.API_PREFIX); private static final int MAX_PATTERN_IDS_PER_REQUEST = 10; - private final String host; - private final String apiKey; - private final ObjectMapper objectMapper; - - public PatternsApiImpl( - String host, - String apiKey, - ObjectMapper objectMapper - ) { - this.host = Objects.requireNonNull(host); - this.apiKey = Objects.requireNonNull(apiKey); - this.objectMapper = Objects.requireNonNull(objectMapper); + private final BusApiContext context; + + public PatternsApiImpl(BusApiContext context) { + this.context = Objects.requireNonNull(context); } @Override @@ -61,10 +53,10 @@ public List findByIds(Collection patternIds) { String url = new URIBuilder() .setScheme(ApiUtils.SCHEME) - .setHost(this.host) + .setHost(this.context.host()) .setPath(PATTERNS_ENDPOINT) .addParameter("pid", patternIdsString) - .addParameter("key", this.apiKey) + .addParameter("key", this.context.apiKey()) .addParameter("format", "json") .toString(); @@ -77,10 +69,10 @@ public List findByRouteId(String routeId) { String url = new URIBuilder() .setScheme(ApiUtils.SCHEME) - .setHost(this.host) + .setHost(this.context.host()) .setPath(PATTERNS_ENDPOINT) .addParameter("rt", routeId) - .addParameter("key", this.apiKey) + .addParameter("key", this.context.apiKey()) .addParameter("format", "json") .toString(); @@ -94,7 +86,8 @@ private List makeRequest(String url) { CtaResponse> patternsResponse; try { - patternsResponse = this.objectMapper.readValue(response, typeReference); + patternsResponse = this.context.objectMapper() + .readValue(response, typeReference); } catch (JacksonException e) { String message = String.format("Failed to parse response from %s", PATTERNS_ENDPOINT); @@ -117,7 +110,7 @@ private List makeRequest(String url) { } return patterns.stream() - .map(RoutePatternMapper.MAPPER::toDomain) + .map(RoutePatternMapper.INSTANCE::toDomain) .toList(); } } diff --git a/src/main/java/com/cta4j/bus/api/pattern/mapper/RoutePatternMapper.java b/src/main/java/com/cta4j/bus/api/pattern/mapper/RoutePatternMapper.java index 8d15eaa..006d02f 100644 --- a/src/main/java/com/cta4j/bus/api/pattern/mapper/RoutePatternMapper.java +++ b/src/main/java/com/cta4j/bus/api/pattern/mapper/RoutePatternMapper.java @@ -2,7 +2,7 @@ import com.cta4j.bus.api.pattern.external.CtaPattern; import com.cta4j.bus.api.pattern.external.CtaPoint; -import com.cta4j.bus.api.common.util.CtaBusMappingQualifiers; +import com.cta4j.bus.api.core.util.CtaBusMappingQualifiers; import com.cta4j.bus.api.pattern.model.PatternPoint; import com.cta4j.bus.api.pattern.model.RoutePattern; import org.jetbrains.annotations.ApiStatus; @@ -13,7 +13,7 @@ @Mapper(uses = CtaBusMappingQualifiers.class) @ApiStatus.Internal public interface RoutePatternMapper { - RoutePatternMapper MAPPER = Mappers.getMapper(RoutePatternMapper.class); + RoutePatternMapper INSTANCE = Mappers.getMapper(RoutePatternMapper.class); @Mapping(source = "pid", target = "patternId") @Mapping(source = "ln", target = "patternCount") diff --git a/src/main/java/com/cta4j/bus/api/prediction/impl/PredictionsApiImpl.java b/src/main/java/com/cta4j/bus/api/prediction/impl/PredictionsApiImpl.java index 99542f1..8b0a2c6 100644 --- a/src/main/java/com/cta4j/bus/api/prediction/impl/PredictionsApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/prediction/impl/PredictionsApiImpl.java @@ -1,15 +1,16 @@ package com.cta4j.bus.api.prediction.impl; -import com.cta4j.bus.api.common.util.ApiUtils; +import com.cta4j.bus.api.core.context.BusApiContext; +import com.cta4j.bus.api.core.util.ApiUtils; import com.cta4j.bus.api.prediction.PredictionsApi; import com.cta4j.bus.api.prediction.external.CtaPrediction; import com.cta4j.bus.api.prediction.mapper.PredictionMapper; import com.cta4j.bus.api.prediction.model.Prediction; import com.cta4j.bus.api.prediction.query.StopsPredictionsQuery; import com.cta4j.bus.api.prediction.query.VehiclesPredictionsQuery; -import com.cta4j.bus.api.common.external.CtaBustimeResponse; -import com.cta4j.bus.api.common.external.CtaError; -import com.cta4j.bus.api.common.external.CtaResponse; +import com.cta4j.bus.api.core.external.CtaBustimeResponse; +import com.cta4j.bus.api.core.external.CtaError; +import com.cta4j.bus.api.core.external.CtaResponse; import com.cta4j.common.exception.Cta4jException; import com.cta4j.common.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; @@ -17,7 +18,6 @@ import org.jspecify.annotations.NullMarked; import tools.jackson.core.JacksonException; import tools.jackson.core.type.TypeReference; -import tools.jackson.databind.ObjectMapper; import java.util.List; import java.util.Objects; @@ -29,18 +29,10 @@ public final class PredictionsApiImpl implements PredictionsApi { private static final int MAX_STOP_IDS_PER_REQUEST = 10; private static final int MAX_VEHICLE_IDS_PER_REQUEST = 10; - private final String host; - private final String apiKey; - private final ObjectMapper objectMapper; - - public PredictionsApiImpl( - String host, - String apiKey, - ObjectMapper objectMapper - ) { - this.host = Objects.requireNonNull(host); - this.apiKey = Objects.requireNonNull(apiKey); - this.objectMapper = Objects.requireNonNull(objectMapper); + private final BusApiContext context; + + public PredictionsApiImpl(BusApiContext context) { + this.context = Objects.requireNonNull(context); } @Override @@ -63,11 +55,11 @@ public List findByStopIds(StopsPredictionsQuery query) { URIBuilder builder = new URIBuilder() .setScheme(ApiUtils.SCHEME) - .setHost(this.host) + .setHost(this.context.host()) .setPath(PREDICTIONS_ENDPOINT) .addParameter("stpid", stopIdsString) .addParameter("tmres", "s") - .addParameter("key", this.apiKey) + .addParameter("key", this.context.apiKey()) .addParameter("format", "json"); if (query.routeIds() != null) { @@ -107,11 +99,11 @@ public List findByVehicleIds(VehiclesPredictionsQuery query) { URIBuilder builder = new URIBuilder() .setScheme(ApiUtils.SCHEME) - .setHost(this.host) + .setHost(this.context.host()) .setPath(PREDICTIONS_ENDPOINT) .addParameter("vid", vehicleIdsString) .addParameter("tmres", "s") - .addParameter("key", this.apiKey) + .addParameter("key", this.context.apiKey()) .addParameter("format", "json"); if (query.maxResults() != null) { @@ -132,7 +124,8 @@ private List makeRequest(String url) { CtaResponse> predictionsResponse; try { - predictionsResponse = this.objectMapper.readValue(response, typeReference); + predictionsResponse = this.context.objectMapper() + .readValue(response, typeReference); } catch (JacksonException e) { String message = String.format("Failed to parse response from %s", PREDICTIONS_ENDPOINT); @@ -155,7 +148,7 @@ private List makeRequest(String url) { } return predictions.stream() - .map(PredictionMapper.MAPPER::toDomain) + .map(PredictionMapper.INSTANCE::toDomain) .toList(); } } diff --git a/src/main/java/com/cta4j/bus/api/prediction/mapper/PredictionMapper.java b/src/main/java/com/cta4j/bus/api/prediction/mapper/PredictionMapper.java index 4a5573a..2d3a6da 100644 --- a/src/main/java/com/cta4j/bus/api/prediction/mapper/PredictionMapper.java +++ b/src/main/java/com/cta4j/bus/api/prediction/mapper/PredictionMapper.java @@ -1,7 +1,7 @@ package com.cta4j.bus.api.prediction.mapper; import com.cta4j.bus.api.prediction.external.CtaPrediction; -import com.cta4j.bus.api.common.util.CtaBusMappingQualifiers; +import com.cta4j.bus.api.core.util.CtaBusMappingQualifiers; import com.cta4j.bus.api.prediction.model.Prediction; import org.jetbrains.annotations.ApiStatus; import org.mapstruct.Mapper; @@ -11,7 +11,7 @@ @Mapper(uses = CtaBusMappingQualifiers.class) @ApiStatus.Internal public interface PredictionMapper { - PredictionMapper MAPPER = Mappers.getMapper(PredictionMapper.class); + PredictionMapper INSTANCE = Mappers.getMapper(PredictionMapper.class); @Mapping(source = "typ", target = "predictionType", qualifiedByName = "mapPredictionType") @Mapping(source = "stpid", target = "stopId") diff --git a/src/main/java/com/cta4j/bus/api/route/impl/RoutesApiImpl.java b/src/main/java/com/cta4j/bus/api/route/impl/RoutesApiImpl.java index 917ccea..cd8299e 100644 --- a/src/main/java/com/cta4j/bus/api/route/impl/RoutesApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/route/impl/RoutesApiImpl.java @@ -1,13 +1,14 @@ package com.cta4j.bus.api.route.impl; -import com.cta4j.bus.api.common.util.ApiUtils; +import com.cta4j.bus.api.core.context.BusApiContext; +import com.cta4j.bus.api.core.util.ApiUtils; import com.cta4j.bus.api.route.RoutesApi; import com.cta4j.bus.api.route.external.CtaRoute; import com.cta4j.bus.api.route.mapper.RouteMapper; import com.cta4j.bus.api.route.model.Route; -import com.cta4j.bus.api.common.external.CtaBustimeResponse; -import com.cta4j.bus.api.common.external.CtaError; -import com.cta4j.bus.api.common.external.CtaResponse; +import com.cta4j.bus.api.core.external.CtaBustimeResponse; +import com.cta4j.bus.api.core.external.CtaError; +import com.cta4j.bus.api.core.external.CtaResponse; import com.cta4j.common.exception.Cta4jException; import com.cta4j.common.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; @@ -17,7 +18,6 @@ import org.slf4j.LoggerFactory; import tools.jackson.core.JacksonException; import tools.jackson.core.type.TypeReference; -import tools.jackson.databind.ObjectMapper; import java.util.List; import java.util.Objects; @@ -29,27 +29,19 @@ public final class RoutesApiImpl implements RoutesApi { private static final String ROUTES_ENDPOINT = String.format("%s/getroutes", ApiUtils.API_PREFIX); - private final String host; - private final String apiKey; - private final ObjectMapper objectMapper; - - public RoutesApiImpl( - String host, - String apiKey, - ObjectMapper objectMapper - ) { - this.host = Objects.requireNonNull(host); - this.apiKey = Objects.requireNonNull(apiKey); - this.objectMapper = Objects.requireNonNull(objectMapper); + private final BusApiContext context; + + public RoutesApiImpl(BusApiContext context) { + this.context = Objects.requireNonNull(context); } @Override public List list() { String url = new URIBuilder() .setScheme(ApiUtils.SCHEME) - .setHost(this.host) + .setHost(this.context.host()) .setPath(ROUTES_ENDPOINT) - .addParameter("key", this.apiKey) + .addParameter("key", this.context.apiKey()) .addParameter("format", "json") .toString(); @@ -59,7 +51,8 @@ public List list() { CtaResponse> routesResponse; try { - routesResponse = this.objectMapper.readValue(response, typeReference); + routesResponse = this.context.objectMapper() + .readValue(response, typeReference); } catch (JacksonException e) { String message = String.format("Failed to parse response from %s", ROUTES_ENDPOINT); @@ -88,7 +81,7 @@ public List list() { } return routes.stream() - .map(RouteMapper.MAPPER::toDomain) + .map(RouteMapper.INSTANCE::toDomain) .toList(); } } diff --git a/src/main/java/com/cta4j/bus/api/route/mapper/RouteMapper.java b/src/main/java/com/cta4j/bus/api/route/mapper/RouteMapper.java index 36545ed..973653f 100644 --- a/src/main/java/com/cta4j/bus/api/route/mapper/RouteMapper.java +++ b/src/main/java/com/cta4j/bus/api/route/mapper/RouteMapper.java @@ -10,7 +10,7 @@ @Mapper @ApiStatus.Internal public interface RouteMapper { - RouteMapper MAPPER = Mappers.getMapper(RouteMapper.class); + RouteMapper INSTANCE = Mappers.getMapper(RouteMapper.class); @Mapping(source = "rt", target = "id") @Mapping(source = "rtnm", target = "name") diff --git a/src/main/java/com/cta4j/bus/api/stop/impl/StopsApiImpl.java b/src/main/java/com/cta4j/bus/api/stop/impl/StopsApiImpl.java index 39e993b..61ac523 100644 --- a/src/main/java/com/cta4j/bus/api/stop/impl/StopsApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/stop/impl/StopsApiImpl.java @@ -1,13 +1,14 @@ package com.cta4j.bus.api.stop.impl; -import com.cta4j.bus.api.common.util.ApiUtils; +import com.cta4j.bus.api.core.context.BusApiContext; +import com.cta4j.bus.api.core.util.ApiUtils; import com.cta4j.bus.api.stop.StopsApi; import com.cta4j.bus.api.stop.external.CtaStop; import com.cta4j.bus.api.stop.mapper.StopMapper; import com.cta4j.bus.api.stop.model.Stop; -import com.cta4j.bus.api.common.external.CtaBustimeResponse; -import com.cta4j.bus.api.common.external.CtaError; -import com.cta4j.bus.api.common.external.CtaResponse; +import com.cta4j.bus.api.core.external.CtaBustimeResponse; +import com.cta4j.bus.api.core.external.CtaError; +import com.cta4j.bus.api.core.external.CtaResponse; import com.cta4j.common.exception.Cta4jException; import com.cta4j.common.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; @@ -15,7 +16,6 @@ import org.jspecify.annotations.NullMarked; import tools.jackson.core.JacksonException; import tools.jackson.core.type.TypeReference; -import tools.jackson.databind.ObjectMapper; import java.util.Collection; import java.util.List; @@ -27,18 +27,10 @@ public final class StopsApiImpl implements StopsApi { private static final String STOPS_ENDPOINT = String.format("%s/getstops", ApiUtils.API_PREFIX); private static final int MAX_STOP_IDS_PER_REQUEST = 10; - private final String host; - private final String apiKey; - private final ObjectMapper objectMapper; - - public StopsApiImpl( - String host, - String apiKey, - ObjectMapper objectMapper - ) { - this.host = Objects.requireNonNull(host); - this.apiKey = Objects.requireNonNull(apiKey); - this.objectMapper = Objects.requireNonNull(objectMapper); + private final BusApiContext context; + + public StopsApiImpl(BusApiContext context) { + this.context = Objects.requireNonNull(context); } @Override @@ -48,11 +40,11 @@ public List findByRouteIdAndDirection(String routeId, String direction) { String url = new URIBuilder() .setScheme(ApiUtils.SCHEME) - .setHost(this.host) + .setHost(this.context.host()) .setPath(STOPS_ENDPOINT) .addParameter("rt", routeId) .addParameter("dir", direction) - .addParameter("key", this.apiKey) + .addParameter("key", this.context.apiKey()) .addParameter("format", "json") .toString(); @@ -79,10 +71,10 @@ public List findByIds(Collection stopIds) { String url = new URIBuilder() .setScheme(ApiUtils.SCHEME) - .setHost(this.host) + .setHost(this.context.host()) .setPath(STOPS_ENDPOINT) .addParameter("stpid", stopIdsString) - .addParameter("key", this.apiKey) + .addParameter("key", this.context.apiKey()) .addParameter("format", "json") .toString(); @@ -96,7 +88,8 @@ private List makeRequest(String url) { CtaResponse> stopsResponse; try { - stopsResponse = this.objectMapper.readValue(response, typeReference); + stopsResponse = this.context.objectMapper() + .readValue(response, typeReference); } catch (JacksonException e) { String message = String.format("Failed to parse response from %s", STOPS_ENDPOINT); @@ -119,7 +112,7 @@ private List makeRequest(String url) { } return stops.stream() - .map(StopMapper.MAPPER::toDomain) + .map(StopMapper.INSTANCE::toDomain) .toList(); } } diff --git a/src/main/java/com/cta4j/bus/api/stop/mapper/StopMapper.java b/src/main/java/com/cta4j/bus/api/stop/mapper/StopMapper.java index c98303f..2a74ae3 100644 --- a/src/main/java/com/cta4j/bus/api/stop/mapper/StopMapper.java +++ b/src/main/java/com/cta4j/bus/api/stop/mapper/StopMapper.java @@ -10,7 +10,7 @@ @Mapper @ApiStatus.Internal public interface StopMapper { - StopMapper MAPPER = Mappers.getMapper(StopMapper.class); + StopMapper INSTANCE = Mappers.getMapper(StopMapper.class); @Mapping(source = "stpid", target = "id") @Mapping(source = "stpnm", target = "name") diff --git a/src/main/java/com/cta4j/bus/api/vehicle/impl/VehiclesApiImpl.java b/src/main/java/com/cta4j/bus/api/vehicle/impl/VehiclesApiImpl.java index e68e443..6871bde 100644 --- a/src/main/java/com/cta4j/bus/api/vehicle/impl/VehiclesApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/vehicle/impl/VehiclesApiImpl.java @@ -1,13 +1,14 @@ package com.cta4j.bus.api.vehicle.impl; -import com.cta4j.bus.api.common.util.ApiUtils; +import com.cta4j.bus.api.core.context.BusApiContext; +import com.cta4j.bus.api.core.util.ApiUtils; import com.cta4j.bus.api.vehicle.VehiclesApi; import com.cta4j.bus.api.vehicle.external.CtaVehicle; import com.cta4j.bus.api.vehicle.mapper.VehicleMapper; import com.cta4j.bus.api.vehicle.model.Vehicle; -import com.cta4j.bus.api.common.external.CtaBustimeResponse; -import com.cta4j.bus.api.common.external.CtaError; -import com.cta4j.bus.api.common.external.CtaResponse; +import com.cta4j.bus.api.core.external.CtaBustimeResponse; +import com.cta4j.bus.api.core.external.CtaError; +import com.cta4j.bus.api.core.external.CtaResponse; import com.cta4j.common.exception.Cta4jException; import com.cta4j.common.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; @@ -15,7 +16,6 @@ import org.jspecify.annotations.NullMarked; import tools.jackson.core.JacksonException; import tools.jackson.core.type.TypeReference; -import tools.jackson.databind.ObjectMapper; import java.util.Collection; import java.util.List; @@ -26,18 +26,10 @@ public final class VehiclesApiImpl implements VehiclesApi { private static final String VEHICLES_ENDPOINT = String.format("%s/getvehicles", ApiUtils.API_PREFIX); - private final String host; - private final String apiKey; - private final ObjectMapper objectMapper; - - public VehiclesApiImpl( - String host, - String apiKey, - ObjectMapper objectMapper - ) { - this.host = Objects.requireNonNull(host); - this.apiKey = Objects.requireNonNull(apiKey); - this.objectMapper = Objects.requireNonNull(objectMapper); + private final BusApiContext context; + + public VehiclesApiImpl(BusApiContext context) { + this.context = Objects.requireNonNull(context); } @Override @@ -54,11 +46,11 @@ public List findByIds(Collection ids) { String url = new URIBuilder() .setScheme(ApiUtils.SCHEME) - .setHost(this.host) + .setHost(this.context.host()) .setPath(VEHICLES_ENDPOINT) .addParameter("vid", idsString) .addParameter("tmres", "s") - .addParameter("key", this.apiKey) + .addParameter("key", this.context.apiKey()) .addParameter("format", "json") .toString(); @@ -79,11 +71,11 @@ public List findByRouteIds(Collection routeIds) { String url = new URIBuilder() .setScheme(ApiUtils.SCHEME) - .setHost(this.host) + .setHost(this.context.host()) .setPath(VEHICLES_ENDPOINT) .addParameter("rt", routeIdsString) .addParameter("tmres", "s") - .addParameter("key", this.apiKey) + .addParameter("key", this.context.apiKey()) .addParameter("format", "json") .toString(); @@ -97,7 +89,8 @@ private List makeRequest(String url) { CtaResponse> vehicleResponse; try { - vehicleResponse = this.objectMapper.readValue(response, typeReference); + vehicleResponse = this.context.objectMapper() + .readValue(response, typeReference); } catch (JacksonException e) { String message = String.format("Failed to parse response from %s", VEHICLES_ENDPOINT); @@ -120,7 +113,7 @@ private List makeRequest(String url) { } return vehicles.stream() - .map(VehicleMapper.MAPPER::toDomain) + .map(VehicleMapper.INSTANCE::toDomain) .toList(); } } diff --git a/src/main/java/com/cta4j/bus/api/vehicle/mapper/VehicleMapper.java b/src/main/java/com/cta4j/bus/api/vehicle/mapper/VehicleMapper.java index 3026d3e..d82ed30 100644 --- a/src/main/java/com/cta4j/bus/api/vehicle/mapper/VehicleMapper.java +++ b/src/main/java/com/cta4j/bus/api/vehicle/mapper/VehicleMapper.java @@ -1,7 +1,7 @@ package com.cta4j.bus.api.vehicle.mapper; import com.cta4j.bus.api.vehicle.external.CtaVehicle; -import com.cta4j.bus.api.common.util.CtaBusMappingQualifiers; +import com.cta4j.bus.api.core.util.CtaBusMappingQualifiers; import com.cta4j.bus.api.vehicle.model.Vehicle; import org.jetbrains.annotations.ApiStatus; import org.mapstruct.Mapper; @@ -11,7 +11,7 @@ @Mapper(uses = CtaBusMappingQualifiers.class) @ApiStatus.Internal public interface VehicleMapper { - VehicleMapper MAPPER = Mappers.getMapper(VehicleMapper.class); + VehicleMapper INSTANCE = Mappers.getMapper(VehicleMapper.class); @Mapping(source = "vid", target = "id") @Mapping(source = "rtpidatafeed", target = "metadata.dataFeed") From ad2b74781216dd25091f13e8721b2cbef95c1092 Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sat, 24 Jan 2026 13:36:19 -0600 Subject: [PATCH 45/53] API refactor --- .../java/com/cta4j/bus/api/core/request/RequestOptions.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/com/cta4j/bus/api/core/request/RequestOptions.java b/src/main/java/com/cta4j/bus/api/core/request/RequestOptions.java index 61c9ff7..4f2fb9d 100644 --- a/src/main/java/com/cta4j/bus/api/core/request/RequestOptions.java +++ b/src/main/java/com/cta4j/bus/api/core/request/RequestOptions.java @@ -10,7 +10,13 @@ public record RequestOptions( @Nullable Locale locale ) { + private static final RequestOptions DEFAULTS = new RequestOptions(); + public RequestOptions() { this(null); } + + public static RequestOptions defaults() { + return DEFAULTS; + } } From b7198c6f30b3cb76db1df3db582287f2c99874a9 Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sat, 24 Jan 2026 13:50:40 -0600 Subject: [PATCH 46/53] API refactor --- src/main/java/com/cta4j/bus/api/BusApi.java | 11 --------- .../bus/api/core/context/BusApiContext.java | 6 ++--- .../bus/api/core/request/RequestOptions.java | 22 ------------------ .../com/cta4j/bus/api/impl/BusApiImpl.java | 23 ++----------------- 4 files changed, 4 insertions(+), 58 deletions(-) delete mode 100644 src/main/java/com/cta4j/bus/api/core/request/RequestOptions.java diff --git a/src/main/java/com/cta4j/bus/api/BusApi.java b/src/main/java/com/cta4j/bus/api/BusApi.java index 87b3810..6892b05 100644 --- a/src/main/java/com/cta4j/bus/api/BusApi.java +++ b/src/main/java/com/cta4j/bus/api/BusApi.java @@ -1,6 +1,5 @@ package com.cta4j.bus.api; -import com.cta4j.bus.api.core.request.RequestOptions; import com.cta4j.bus.api.detour.DetoursApi; import com.cta4j.bus.api.direction.DirectionsApi; import com.cta4j.bus.api.impl.BusApiImpl; @@ -104,16 +103,6 @@ interface Builder { */ Builder host(String host); - /** - * Sets the default request options to use for requests. - *

- * If not specified, default request options are used. - * - * @param requestOptions the default request options - * @return this builder instance - */ - Builder defaultRequestOptions(RequestOptions requestOptions); - /** * Builds a configured {@link BusApi} instance. * diff --git a/src/main/java/com/cta4j/bus/api/core/context/BusApiContext.java b/src/main/java/com/cta4j/bus/api/core/context/BusApiContext.java index b8a7207..b1f7670 100644 --- a/src/main/java/com/cta4j/bus/api/core/context/BusApiContext.java +++ b/src/main/java/com/cta4j/bus/api/core/context/BusApiContext.java @@ -1,25 +1,23 @@ package com.cta4j.bus.api.core.context; -import com.cta4j.bus.api.core.request.RequestOptions; +import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; import tools.jackson.databind.ObjectMapper; import java.util.Objects; @NullMarked +@ApiStatus.Internal public record BusApiContext( String host, String apiKey, - RequestOptions defaultRequestOptions, - ObjectMapper objectMapper ) { public BusApiContext { Objects.requireNonNull(host); Objects.requireNonNull(apiKey); - Objects.requireNonNull(defaultRequestOptions); Objects.requireNonNull(objectMapper); } } diff --git a/src/main/java/com/cta4j/bus/api/core/request/RequestOptions.java b/src/main/java/com/cta4j/bus/api/core/request/RequestOptions.java deleted file mode 100644 index 4f2fb9d..0000000 --- a/src/main/java/com/cta4j/bus/api/core/request/RequestOptions.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.cta4j.bus.api.core.request; - -import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; - -import java.util.Locale; - -@NullMarked -public record RequestOptions( - @Nullable - Locale locale -) { - private static final RequestOptions DEFAULTS = new RequestOptions(); - - public RequestOptions() { - this(null); - } - - public static RequestOptions defaults() { - return DEFAULTS; - } -} diff --git a/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java b/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java index 5049baa..32165ea 100644 --- a/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java @@ -1,7 +1,6 @@ package com.cta4j.bus.api.impl; import com.cta4j.bus.api.core.context.BusApiContext; -import com.cta4j.bus.api.core.request.RequestOptions; import com.cta4j.bus.api.core.util.ApiUtils; import com.cta4j.bus.api.BusApi; import com.cta4j.bus.api.detour.DetoursApi; @@ -55,19 +54,16 @@ public final class BusApiImpl implements BusApi { public BusApiImpl( String host, - String apiKey, - RequestOptions defaultRequestOptions + String apiKey ) { Objects.requireNonNull(host); Objects.requireNonNull(apiKey); - Objects.requireNonNull(defaultRequestOptions); ObjectMapper objectMapper = new ObjectMapper(); this.context = new BusApiContext( host, apiKey, - defaultRequestOptions, objectMapper ); this.vehiclesApi = new VehiclesApiImpl(this.context); @@ -191,13 +187,9 @@ public static final class BuilderImpl implements BusApi.Builder { @Nullable private String host; - @Nullable - private RequestOptions defaultRequestOptions; - public BuilderImpl(String apiKey) { this.apiKey = Objects.requireNonNull(apiKey); this.host = null; - this.defaultRequestOptions = null; } @Override @@ -207,25 +199,14 @@ public Builder host(String host) { return this; } - @Override - public Builder defaultRequestOptions(RequestOptions requestOptions) { - this.defaultRequestOptions = Objects.requireNonNull(requestOptions); - - return this; - } - @Override public BusApi build() { String finalHost = Objects.requireNonNullElse( this.host, ApiUtils.DEFAULT_HOST ); - RequestOptions finalRequestOptions = Objects.requireNonNullElse( - this.defaultRequestOptions, - new RequestOptions() - ); - return new BusApiImpl(finalHost, this.apiKey, finalRequestOptions); + return new BusApiImpl(finalHost, this.apiKey); } } } From 54e399508aec4b2ae718a41717c1d9010c24e53b Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sat, 24 Jan 2026 13:53:57 -0600 Subject: [PATCH 47/53] API refactor --- src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java b/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java index 32165ea..e0f9d09 100644 --- a/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java +++ b/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java @@ -61,11 +61,7 @@ public BusApiImpl( ObjectMapper objectMapper = new ObjectMapper(); - this.context = new BusApiContext( - host, - apiKey, - objectMapper - ); + this.context = new BusApiContext(host, apiKey, objectMapper); this.vehiclesApi = new VehiclesApiImpl(this.context); this.routesApi = new RoutesApiImpl(this.context); this.directionsApi = new DirectionsApiImpl(this.context); @@ -201,10 +197,7 @@ public Builder host(String host) { @Override public BusApi build() { - String finalHost = Objects.requireNonNullElse( - this.host, - ApiUtils.DEFAULT_HOST - ); + String finalHost = Objects.requireNonNullElse(this.host, ApiUtils.DEFAULT_HOST); return new BusApiImpl(finalHost, this.apiKey); } From 0bf1905f1f7f4ad8fdddd17ba558a42e6cccffc4 Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sat, 24 Jan 2026 14:17:39 -0600 Subject: [PATCH 48/53] API refactor --- pom.xml | 2 +- .../java/com/cta4j/bus/{api => }/BusApi.java | 20 +++---- .../bus/{api => }/detour/DetoursApi.java | 4 +- .../internal}/impl/DetoursApiImpl.java | 26 ++++----- .../internal}/mapper/DetourMapper.java | 14 ++--- .../internal/wire}/CtaDetour.java | 2 +- .../wire}/CtaDetoursRouteDirection.java | 2 +- .../bus/{api => }/detour/model/Detour.java | 2 +- .../detour/model/DetourRouteDirection.java | 2 +- .../{api => }/direction/DirectionsApi.java | 2 +- .../internal}/impl/DirectionsApiImpl.java | 22 ++++---- .../internal/wire}/CtaDirection.java | 2 +- .../context/BusApiContext.java | 2 +- .../{api => internal}/impl/BusApiImpl.java | 56 +++++++++---------- .../mapper/Qualifiers.java} | 20 +++---- .../{api/core => internal}/util/ApiUtils.java | 4 +- .../internal}/util/DateTimeUtils.java | 2 +- .../internal}/util/HttpUtils.java | 4 +- .../wire}/CtaBustimeResponse.java | 2 +- .../external => internal/wire}/CtaError.java | 2 +- .../wire}/CtaResponse.java | 2 +- .../bus/{api => }/locale/LocalesApi.java | 4 +- .../internal}/impl/LocalesApiImpl.java | 26 ++++----- .../mapper/SupportedLocaleMapper.java | 10 ++-- .../internal/wire}/CtaLocale.java | 2 +- .../locale/model/SupportedLocale.java | 2 +- .../bus/{api => }/pattern/PatternsApi.java | 4 +- .../internal}/impl/PatternsApiImpl.java | 26 ++++----- .../internal}/mapper/RoutePatternMapper.java | 14 ++--- .../internal/wire}/CtaPattern.java | 2 +- .../internal/wire}/CtaPoint.java | 2 +- .../{api => }/pattern/model/PatternPoint.java | 2 +- .../pattern/model/PatternPointType.java | 2 +- .../{api => }/pattern/model/RoutePattern.java | 2 +- .../{api => }/prediction/PredictionsApi.java | 8 +-- .../internal}/impl/PredictionsApiImpl.java | 30 +++++----- .../internal}/mapper/PredictionMapper.java | 10 ++-- .../internal/wire}/CtaPrediction.java | 2 +- .../prediction/model/DynamicAction.java | 2 +- .../{api => }/prediction/model/FlagStop.java | 2 +- .../prediction/model/PassengerLoad.java | 2 +- .../prediction/model/Prediction.java | 2 +- .../prediction/model/PredictionMetadata.java | 2 +- .../prediction/model/PredictionType.java | 2 +- .../query/StopsPredictionsQuery.java | 2 +- .../query/VehiclesPredictionsQuery.java | 2 +- .../cta4j/bus/{api => }/route/RoutesApi.java | 4 +- .../internal}/impl/RoutesApiImpl.java | 26 ++++----- .../internal}/mapper/RouteMapper.java | 6 +- .../internal/wire}/CtaRoute.java | 2 +- .../bus/{api => }/route/model/Route.java | 2 +- .../cta4j/bus/{api => }/stop/StopsApi.java | 6 +- .../internal}/impl/StopsApiImpl.java | 26 ++++----- .../internal}/mapper/StopMapper.java | 6 +- .../internal/wire}/CtaStop.java | 2 +- .../cta4j/bus/{api => }/stop/model/Stop.java | 2 +- .../bus/{api => }/vehicle/VehiclesApi.java | 6 +- .../internal}/impl/VehiclesApiImpl.java | 26 ++++----- .../internal}/mapper/VehicleMapper.java | 10 ++-- .../internal/wire}/CtaVehicle.java | 2 +- .../{api => }/vehicle/model/TransitMode.java | 2 +- .../bus/{api => }/vehicle/model/Vehicle.java | 2 +- .../vehicle/model/VehicleCoordinates.java | 2 +- .../vehicle/model/VehicleMetadata.java | 4 +- .../exception/Cta4jException.java | 2 +- .../com/cta4j/train/client/TrainClient.java | 2 +- .../client/internal/TrainClientImpl.java | 4 +- .../train/mapper/StationArrivalMapper.java | 2 +- .../mapper/UpcomingTrainArrivalMapper.java | 4 +- 69 files changed, 254 insertions(+), 254 deletions(-) rename src/main/java/com/cta4j/bus/{api => }/BusApi.java (86%) rename src/main/java/com/cta4j/bus/{api => }/detour/DetoursApi.java (94%) rename src/main/java/com/cta4j/bus/{api/detour => detour/internal}/impl/DetoursApiImpl.java (85%) rename src/main/java/com/cta4j/bus/{api/detour => detour/internal}/mapper/DetourMapper.java (71%) rename src/main/java/com/cta4j/bus/{api/detour/external => detour/internal/wire}/CtaDetour.java (94%) rename src/main/java/com/cta4j/bus/{api/detour/external => detour/internal/wire}/CtaDetoursRouteDirection.java (91%) rename src/main/java/com/cta4j/bus/{api => }/detour/model/Detour.java (94%) rename src/main/java/com/cta4j/bus/{api => }/detour/model/DetourRouteDirection.java (87%) rename src/main/java/com/cta4j/bus/{api => }/direction/DirectionsApi.java (95%) rename src/main/java/com/cta4j/bus/{api/direction => direction/internal}/impl/DirectionsApiImpl.java (82%) rename src/main/java/com/cta4j/bus/{api/direction/external => direction/internal/wire}/CtaDirection.java (90%) rename src/main/java/com/cta4j/bus/{api/core => internal}/context/BusApiContext.java (91%) rename src/main/java/com/cta4j/bus/{api => internal}/impl/BusApiImpl.java (79%) rename src/main/java/com/cta4j/bus/{api/core/util/CtaBusMappingQualifiers.java => internal/mapper/Qualifiers.java} (89%) rename src/main/java/com/cta4j/bus/{api/core => internal}/util/ApiUtils.java (92%) rename src/main/java/com/cta4j/{common => bus/internal}/util/DateTimeUtils.java (95%) rename src/main/java/com/cta4j/{common => bus/internal}/util/HttpUtils.java (92%) rename src/main/java/com/cta4j/bus/{api/core/external => internal/wire}/CtaBustimeResponse.java (93%) rename src/main/java/com/cta4j/bus/{api/core/external => internal/wire}/CtaError.java (89%) rename src/main/java/com/cta4j/bus/{api/core/external => internal/wire}/CtaResponse.java (92%) rename src/main/java/com/cta4j/bus/{api => }/locale/LocalesApi.java (76%) rename src/main/java/com/cta4j/bus/{api/locale => locale/internal}/impl/LocalesApiImpl.java (84%) rename src/main/java/com/cta4j/bus/{api/locale => locale/internal}/mapper/SupportedLocaleMapper.java (62%) rename src/main/java/com/cta4j/bus/{api/locale/external => locale/internal/wire}/CtaLocale.java (91%) rename src/main/java/com/cta4j/bus/{api => }/locale/model/SupportedLocale.java (88%) rename src/main/java/com/cta4j/bus/{api => }/pattern/PatternsApi.java (84%) rename src/main/java/com/cta4j/bus/{api/pattern => pattern/internal}/impl/PatternsApiImpl.java (85%) rename src/main/java/com/cta4j/bus/{api/pattern => pattern/internal}/mapper/RoutePatternMapper.java (75%) rename src/main/java/com/cta4j/bus/{api/pattern/external => pattern/internal/wire}/CtaPattern.java (94%) rename src/main/java/com/cta4j/bus/{api/pattern/external => pattern/internal/wire}/CtaPoint.java (92%) rename src/main/java/com/cta4j/bus/{api => }/pattern/model/PatternPoint.java (93%) rename src/main/java/com/cta4j/bus/{api => }/pattern/model/PatternPointType.java (58%) rename src/main/java/com/cta4j/bus/{api => }/pattern/model/RoutePattern.java (95%) rename src/main/java/com/cta4j/bus/{api => }/prediction/PredictionsApi.java (84%) rename src/main/java/com/cta4j/bus/{api/prediction => prediction/internal}/impl/PredictionsApiImpl.java (86%) rename src/main/java/com/cta4j/bus/{api/prediction => prediction/internal}/mapper/PredictionMapper.java (87%) rename src/main/java/com/cta4j/bus/{api/prediction/external => prediction/internal/wire}/CtaPrediction.java (96%) rename src/main/java/com/cta4j/bus/{api => }/prediction/model/DynamicAction.java (92%) rename src/main/java/com/cta4j/bus/{api => }/prediction/model/FlagStop.java (85%) rename src/main/java/com/cta4j/bus/{api => }/prediction/model/PassengerLoad.java (65%) rename src/main/java/com/cta4j/bus/{api => }/prediction/model/Prediction.java (96%) rename src/main/java/com/cta4j/bus/{api => }/prediction/model/PredictionMetadata.java (95%) rename src/main/java/com/cta4j/bus/{api => }/prediction/model/PredictionType.java (57%) rename src/main/java/com/cta4j/bus/{api => }/prediction/query/StopsPredictionsQuery.java (98%) rename src/main/java/com/cta4j/bus/{api => }/prediction/query/VehiclesPredictionsQuery.java (97%) rename src/main/java/com/cta4j/bus/{api => }/route/RoutesApi.java (64%) rename src/main/java/com/cta4j/bus/{api/route => route/internal}/impl/RoutesApiImpl.java (81%) rename src/main/java/com/cta4j/bus/{api/route => route/internal}/mapper/RouteMapper.java (80%) rename src/main/java/com/cta4j/bus/{api/route/external => route/internal/wire}/CtaRoute.java (93%) rename src/main/java/com/cta4j/bus/{api => }/route/model/Route.java (91%) rename src/main/java/com/cta4j/bus/{api => }/stop/StopsApi.java (87%) rename src/main/java/com/cta4j/bus/{api/stop => stop/internal}/impl/StopsApiImpl.java (85%) rename src/main/java/com/cta4j/bus/{api/stop => stop/internal}/mapper/StopMapper.java (84%) rename src/main/java/com/cta4j/bus/{api/stop/external => stop/internal/wire}/CtaStop.java (95%) rename src/main/java/com/cta4j/bus/{api => }/stop/model/Stop.java (96%) rename src/main/java/com/cta4j/bus/{api => }/vehicle/VehiclesApi.java (89%) rename src/main/java/com/cta4j/bus/{api/vehicle => vehicle/internal}/impl/VehiclesApiImpl.java (84%) rename src/main/java/com/cta4j/bus/{api/vehicle => vehicle/internal}/mapper/VehicleMapper.java (89%) rename src/main/java/com/cta4j/bus/{api/vehicle/external => vehicle/internal/wire}/CtaVehicle.java (96%) rename src/main/java/com/cta4j/bus/{api => }/vehicle/model/TransitMode.java (86%) rename src/main/java/com/cta4j/bus/{api => }/vehicle/model/Vehicle.java (92%) rename src/main/java/com/cta4j/bus/{api => }/vehicle/model/VehicleCoordinates.java (94%) rename src/main/java/com/cta4j/bus/{api => }/vehicle/model/VehicleMetadata.java (92%) rename src/main/java/com/cta4j/{common => }/exception/Cta4jException.java (94%) diff --git a/pom.xml b/pom.xml index 10f4ed8..c6b5328 100644 --- a/pom.xml +++ b/pom.xml @@ -116,7 +116,7 @@ - com.cta4j.vehicle.client.internal.*,com.cta4j.vehicle.external.*,com.cta4j.vehicle.mapper.*,com.cta4j.train.client.internal.*,com.cta4j.train.external.*,com.cta4j.train.mapper.*,com.cta4j.common.util.* + com.cta4j.vehicle.client.internal.*,com.cta4j.vehicle.external.*,com.cta4j.vehicle.mapper.*,com.cta4j.train.client.internal.*,com.cta4j.train.external.*,com.cta4j.train.mapper.*,com.cta4j.bus.internal.util.* diff --git a/src/main/java/com/cta4j/bus/api/BusApi.java b/src/main/java/com/cta4j/bus/BusApi.java similarity index 86% rename from src/main/java/com/cta4j/bus/api/BusApi.java rename to src/main/java/com/cta4j/bus/BusApi.java index 6892b05..d82de83 100644 --- a/src/main/java/com/cta4j/bus/api/BusApi.java +++ b/src/main/java/com/cta4j/bus/BusApi.java @@ -1,14 +1,14 @@ -package com.cta4j.bus.api; +package com.cta4j.bus; -import com.cta4j.bus.api.detour.DetoursApi; -import com.cta4j.bus.api.direction.DirectionsApi; -import com.cta4j.bus.api.impl.BusApiImpl; -import com.cta4j.bus.api.locale.LocalesApi; -import com.cta4j.bus.api.pattern.PatternsApi; -import com.cta4j.bus.api.prediction.PredictionsApi; -import com.cta4j.bus.api.route.RoutesApi; -import com.cta4j.bus.api.stop.StopsApi; -import com.cta4j.bus.api.vehicle.VehiclesApi; +import com.cta4j.bus.detour.DetoursApi; +import com.cta4j.bus.direction.DirectionsApi; +import com.cta4j.bus.internal.impl.BusApiImpl; +import com.cta4j.bus.locale.LocalesApi; +import com.cta4j.bus.pattern.PatternsApi; +import com.cta4j.bus.prediction.PredictionsApi; +import com.cta4j.bus.route.RoutesApi; +import com.cta4j.bus.stop.StopsApi; +import com.cta4j.bus.vehicle.VehiclesApi; import org.jspecify.annotations.NullMarked; import java.time.Instant; diff --git a/src/main/java/com/cta4j/bus/api/detour/DetoursApi.java b/src/main/java/com/cta4j/bus/detour/DetoursApi.java similarity index 94% rename from src/main/java/com/cta4j/bus/api/detour/DetoursApi.java rename to src/main/java/com/cta4j/bus/detour/DetoursApi.java index 421ea7d..96c0d2f 100644 --- a/src/main/java/com/cta4j/bus/api/detour/DetoursApi.java +++ b/src/main/java/com/cta4j/bus/detour/DetoursApi.java @@ -1,6 +1,6 @@ -package com.cta4j.bus.api.detour; +package com.cta4j.bus.detour; -import com.cta4j.bus.api.detour.model.Detour; +import com.cta4j.bus.detour.model.Detour; import org.jspecify.annotations.NullMarked; import java.util.List; diff --git a/src/main/java/com/cta4j/bus/api/detour/impl/DetoursApiImpl.java b/src/main/java/com/cta4j/bus/detour/internal/impl/DetoursApiImpl.java similarity index 85% rename from src/main/java/com/cta4j/bus/api/detour/impl/DetoursApiImpl.java rename to src/main/java/com/cta4j/bus/detour/internal/impl/DetoursApiImpl.java index f6cf6c1..275daf7 100644 --- a/src/main/java/com/cta4j/bus/api/detour/impl/DetoursApiImpl.java +++ b/src/main/java/com/cta4j/bus/detour/internal/impl/DetoursApiImpl.java @@ -1,16 +1,16 @@ -package com.cta4j.bus.api.detour.impl; - -import com.cta4j.bus.api.core.context.BusApiContext; -import com.cta4j.bus.api.core.external.CtaBustimeResponse; -import com.cta4j.bus.api.core.external.CtaError; -import com.cta4j.bus.api.core.external.CtaResponse; -import com.cta4j.bus.api.core.util.ApiUtils; -import com.cta4j.bus.api.detour.DetoursApi; -import com.cta4j.bus.api.detour.external.CtaDetour; -import com.cta4j.bus.api.detour.mapper.DetourMapper; -import com.cta4j.bus.api.detour.model.Detour; -import com.cta4j.common.exception.Cta4jException; -import com.cta4j.common.util.HttpUtils; +package com.cta4j.bus.detour.internal.impl; + +import com.cta4j.bus.internal.context.BusApiContext; +import com.cta4j.bus.internal.wire.CtaBustimeResponse; +import com.cta4j.bus.internal.wire.CtaError; +import com.cta4j.bus.internal.wire.CtaResponse; +import com.cta4j.bus.internal.util.ApiUtils; +import com.cta4j.bus.detour.DetoursApi; +import com.cta4j.bus.detour.internal.wire.CtaDetour; +import com.cta4j.bus.detour.internal.mapper.DetourMapper; +import com.cta4j.bus.detour.model.Detour; +import com.cta4j.exception.Cta4jException; +import com.cta4j.bus.internal.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/com/cta4j/bus/api/detour/mapper/DetourMapper.java b/src/main/java/com/cta4j/bus/detour/internal/mapper/DetourMapper.java similarity index 71% rename from src/main/java/com/cta4j/bus/api/detour/mapper/DetourMapper.java rename to src/main/java/com/cta4j/bus/detour/internal/mapper/DetourMapper.java index 561ef63..a495767 100644 --- a/src/main/java/com/cta4j/bus/api/detour/mapper/DetourMapper.java +++ b/src/main/java/com/cta4j/bus/detour/internal/mapper/DetourMapper.java @@ -1,16 +1,16 @@ -package com.cta4j.bus.api.detour.mapper; +package com.cta4j.bus.detour.internal.mapper; -import com.cta4j.bus.api.detour.external.CtaDetour; -import com.cta4j.bus.api.detour.external.CtaDetoursRouteDirection; -import com.cta4j.bus.api.core.util.CtaBusMappingQualifiers; -import com.cta4j.bus.api.detour.model.Detour; -import com.cta4j.bus.api.detour.model.DetourRouteDirection; +import com.cta4j.bus.detour.internal.wire.CtaDetour; +import com.cta4j.bus.detour.internal.wire.CtaDetoursRouteDirection; +import com.cta4j.bus.internal.mapper.Qualifiers; +import com.cta4j.bus.detour.model.Detour; +import com.cta4j.bus.detour.model.DetourRouteDirection; import org.jetbrains.annotations.ApiStatus; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.factory.Mappers; -@Mapper(uses = CtaBusMappingQualifiers.class) +@Mapper(uses = Qualifiers.class) @ApiStatus.Internal public interface DetourMapper { DetourMapper INSTANCE = Mappers.getMapper(DetourMapper.class); diff --git a/src/main/java/com/cta4j/bus/api/detour/external/CtaDetour.java b/src/main/java/com/cta4j/bus/detour/internal/wire/CtaDetour.java similarity index 94% rename from src/main/java/com/cta4j/bus/api/detour/external/CtaDetour.java rename to src/main/java/com/cta4j/bus/detour/internal/wire/CtaDetour.java index 6b7db2b..82c8864 100644 --- a/src/main/java/com/cta4j/bus/api/detour/external/CtaDetour.java +++ b/src/main/java/com/cta4j/bus/detour/internal/wire/CtaDetour.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.detour.external; +package com.cta4j.bus.detour.internal.wire; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.jetbrains.annotations.ApiStatus; diff --git a/src/main/java/com/cta4j/bus/api/detour/external/CtaDetoursRouteDirection.java b/src/main/java/com/cta4j/bus/detour/internal/wire/CtaDetoursRouteDirection.java similarity index 91% rename from src/main/java/com/cta4j/bus/api/detour/external/CtaDetoursRouteDirection.java rename to src/main/java/com/cta4j/bus/detour/internal/wire/CtaDetoursRouteDirection.java index 5c97c5f..a96c3ab 100644 --- a/src/main/java/com/cta4j/bus/api/detour/external/CtaDetoursRouteDirection.java +++ b/src/main/java/com/cta4j/bus/detour/internal/wire/CtaDetoursRouteDirection.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.detour.external; +package com.cta4j.bus.detour.internal.wire; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.jetbrains.annotations.ApiStatus; diff --git a/src/main/java/com/cta4j/bus/api/detour/model/Detour.java b/src/main/java/com/cta4j/bus/detour/model/Detour.java similarity index 94% rename from src/main/java/com/cta4j/bus/api/detour/model/Detour.java rename to src/main/java/com/cta4j/bus/detour/model/Detour.java index 8451458..10bf051 100644 --- a/src/main/java/com/cta4j/bus/api/detour/model/Detour.java +++ b/src/main/java/com/cta4j/bus/detour/model/Detour.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.detour.model; +package com.cta4j.bus.detour.model; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; diff --git a/src/main/java/com/cta4j/bus/api/detour/model/DetourRouteDirection.java b/src/main/java/com/cta4j/bus/detour/model/DetourRouteDirection.java similarity index 87% rename from src/main/java/com/cta4j/bus/api/detour/model/DetourRouteDirection.java rename to src/main/java/com/cta4j/bus/detour/model/DetourRouteDirection.java index 8122b87..1f68ec2 100644 --- a/src/main/java/com/cta4j/bus/api/detour/model/DetourRouteDirection.java +++ b/src/main/java/com/cta4j/bus/detour/model/DetourRouteDirection.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.detour.model; +package com.cta4j.bus.detour.model; import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/com/cta4j/bus/api/direction/DirectionsApi.java b/src/main/java/com/cta4j/bus/direction/DirectionsApi.java similarity index 95% rename from src/main/java/com/cta4j/bus/api/direction/DirectionsApi.java rename to src/main/java/com/cta4j/bus/direction/DirectionsApi.java index 3bbb413..6f94ea1 100644 --- a/src/main/java/com/cta4j/bus/api/direction/DirectionsApi.java +++ b/src/main/java/com/cta4j/bus/direction/DirectionsApi.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.direction; +package com.cta4j.bus.direction; import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/com/cta4j/bus/api/direction/impl/DirectionsApiImpl.java b/src/main/java/com/cta4j/bus/direction/internal/impl/DirectionsApiImpl.java similarity index 82% rename from src/main/java/com/cta4j/bus/api/direction/impl/DirectionsApiImpl.java rename to src/main/java/com/cta4j/bus/direction/internal/impl/DirectionsApiImpl.java index f87192d..dc471ec 100644 --- a/src/main/java/com/cta4j/bus/api/direction/impl/DirectionsApiImpl.java +++ b/src/main/java/com/cta4j/bus/direction/internal/impl/DirectionsApiImpl.java @@ -1,14 +1,14 @@ -package com.cta4j.bus.api.direction.impl; - -import com.cta4j.bus.api.core.context.BusApiContext; -import com.cta4j.bus.api.core.util.ApiUtils; -import com.cta4j.bus.api.direction.DirectionsApi; -import com.cta4j.bus.api.core.external.CtaBustimeResponse; -import com.cta4j.bus.api.direction.external.CtaDirection; -import com.cta4j.bus.api.core.external.CtaError; -import com.cta4j.bus.api.core.external.CtaResponse; -import com.cta4j.common.exception.Cta4jException; -import com.cta4j.common.util.HttpUtils; +package com.cta4j.bus.direction.internal.impl; + +import com.cta4j.bus.internal.context.BusApiContext; +import com.cta4j.bus.internal.util.ApiUtils; +import com.cta4j.bus.direction.DirectionsApi; +import com.cta4j.bus.internal.wire.CtaBustimeResponse; +import com.cta4j.bus.direction.internal.wire.CtaDirection; +import com.cta4j.bus.internal.wire.CtaError; +import com.cta4j.bus.internal.wire.CtaResponse; +import com.cta4j.exception.Cta4jException; +import com.cta4j.bus.internal.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/com/cta4j/bus/api/direction/external/CtaDirection.java b/src/main/java/com/cta4j/bus/direction/internal/wire/CtaDirection.java similarity index 90% rename from src/main/java/com/cta4j/bus/api/direction/external/CtaDirection.java rename to src/main/java/com/cta4j/bus/direction/internal/wire/CtaDirection.java index 4dcfd8b..acfa03e 100644 --- a/src/main/java/com/cta4j/bus/api/direction/external/CtaDirection.java +++ b/src/main/java/com/cta4j/bus/direction/internal/wire/CtaDirection.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.direction.external; +package com.cta4j.bus.direction.internal.wire; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.jetbrains.annotations.ApiStatus; diff --git a/src/main/java/com/cta4j/bus/api/core/context/BusApiContext.java b/src/main/java/com/cta4j/bus/internal/context/BusApiContext.java similarity index 91% rename from src/main/java/com/cta4j/bus/api/core/context/BusApiContext.java rename to src/main/java/com/cta4j/bus/internal/context/BusApiContext.java index b1f7670..c3dd73e 100644 --- a/src/main/java/com/cta4j/bus/api/core/context/BusApiContext.java +++ b/src/main/java/com/cta4j/bus/internal/context/BusApiContext.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.core.context; +package com.cta4j.bus.internal.context; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java b/src/main/java/com/cta4j/bus/internal/impl/BusApiImpl.java similarity index 79% rename from src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java rename to src/main/java/com/cta4j/bus/internal/impl/BusApiImpl.java index e0f9d09..2997ca5 100644 --- a/src/main/java/com/cta4j/bus/api/impl/BusApiImpl.java +++ b/src/main/java/com/cta4j/bus/internal/impl/BusApiImpl.java @@ -1,30 +1,30 @@ -package com.cta4j.bus.api.impl; - -import com.cta4j.bus.api.core.context.BusApiContext; -import com.cta4j.bus.api.core.util.ApiUtils; -import com.cta4j.bus.api.BusApi; -import com.cta4j.bus.api.detour.DetoursApi; -import com.cta4j.bus.api.detour.impl.DetoursApiImpl; -import com.cta4j.bus.api.direction.DirectionsApi; -import com.cta4j.bus.api.locale.LocalesApi; -import com.cta4j.bus.api.locale.impl.LocalesApiImpl; -import com.cta4j.bus.api.pattern.PatternsApi; -import com.cta4j.bus.api.prediction.PredictionsApi; -import com.cta4j.bus.api.direction.impl.DirectionsApiImpl; -import com.cta4j.bus.api.pattern.impl.PatternsApiImpl; -import com.cta4j.bus.api.prediction.impl.PredictionsApiImpl; -import com.cta4j.bus.api.route.RoutesApi; -import com.cta4j.bus.api.stop.StopsApi; -import com.cta4j.bus.api.route.impl.RoutesApiImpl; -import com.cta4j.bus.api.stop.impl.StopsApiImpl; -import com.cta4j.bus.api.vehicle.VehiclesApi; -import com.cta4j.bus.api.vehicle.impl.VehiclesApiImpl; -import com.cta4j.bus.api.core.external.CtaBustimeResponse; -import com.cta4j.bus.api.core.external.CtaError; -import com.cta4j.bus.api.core.external.CtaResponse; -import com.cta4j.bus.api.core.util.CtaBusMappingQualifiers; -import com.cta4j.common.exception.Cta4jException; -import com.cta4j.common.util.HttpUtils; +package com.cta4j.bus.internal.impl; + +import com.cta4j.bus.internal.context.BusApiContext; +import com.cta4j.bus.internal.util.ApiUtils; +import com.cta4j.bus.BusApi; +import com.cta4j.bus.detour.DetoursApi; +import com.cta4j.bus.detour.internal.impl.DetoursApiImpl; +import com.cta4j.bus.direction.DirectionsApi; +import com.cta4j.bus.locale.LocalesApi; +import com.cta4j.bus.locale.internal.impl.LocalesApiImpl; +import com.cta4j.bus.pattern.PatternsApi; +import com.cta4j.bus.prediction.PredictionsApi; +import com.cta4j.bus.direction.internal.impl.DirectionsApiImpl; +import com.cta4j.bus.pattern.internal.impl.PatternsApiImpl; +import com.cta4j.bus.prediction.internal.impl.PredictionsApiImpl; +import com.cta4j.bus.route.RoutesApi; +import com.cta4j.bus.stop.StopsApi; +import com.cta4j.bus.route.internal.impl.RoutesApiImpl; +import com.cta4j.bus.stop.internal.impl.StopsApiImpl; +import com.cta4j.bus.vehicle.VehiclesApi; +import com.cta4j.bus.vehicle.internal.impl.VehiclesApiImpl; +import com.cta4j.bus.internal.wire.CtaBustimeResponse; +import com.cta4j.bus.internal.wire.CtaError; +import com.cta4j.bus.internal.wire.CtaResponse; +import com.cta4j.bus.internal.mapper.Qualifiers; +import com.cta4j.exception.Cta4jException; +import com.cta4j.bus.internal.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; @@ -122,7 +122,7 @@ public Instant systemTime() { throw new Cta4jException(message); } - Instant systemInstant = CtaBusMappingQualifiers.mapTimestamp(systemTime); + Instant systemInstant = Qualifiers.mapTimestamp(systemTime); if (systemInstant == null) { String message = String.format( diff --git a/src/main/java/com/cta4j/bus/api/core/util/CtaBusMappingQualifiers.java b/src/main/java/com/cta4j/bus/internal/mapper/Qualifiers.java similarity index 89% rename from src/main/java/com/cta4j/bus/api/core/util/CtaBusMappingQualifiers.java rename to src/main/java/com/cta4j/bus/internal/mapper/Qualifiers.java index 10d80a3..ca455ae 100644 --- a/src/main/java/com/cta4j/bus/api/core/util/CtaBusMappingQualifiers.java +++ b/src/main/java/com/cta4j/bus/internal/mapper/Qualifiers.java @@ -1,11 +1,11 @@ -package com.cta4j.bus.api.core.util; - -import com.cta4j.bus.api.prediction.model.DynamicAction; -import com.cta4j.bus.api.prediction.model.FlagStop; -import com.cta4j.bus.api.prediction.model.PassengerLoad; -import com.cta4j.bus.api.pattern.model.PatternPointType; -import com.cta4j.bus.api.prediction.model.PredictionType; -import com.cta4j.bus.api.vehicle.model.TransitMode; +package com.cta4j.bus.internal.mapper; + +import com.cta4j.bus.prediction.model.DynamicAction; +import com.cta4j.bus.prediction.model.FlagStop; +import com.cta4j.bus.prediction.model.PassengerLoad; +import com.cta4j.bus.pattern.model.PatternPointType; +import com.cta4j.bus.prediction.model.PredictionType; +import com.cta4j.bus.vehicle.model.TransitMode; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -20,11 +20,11 @@ @NullMarked @ApiStatus.Internal -public final class CtaBusMappingQualifiers { +public final class Qualifiers { private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm[:ss]"); private static final ZoneId CHICAGO_ZONE_ID = ZoneId.of("America/Chicago"); - private CtaBusMappingQualifiers() { + private Qualifiers() { throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); } diff --git a/src/main/java/com/cta4j/bus/api/core/util/ApiUtils.java b/src/main/java/com/cta4j/bus/internal/util/ApiUtils.java similarity index 92% rename from src/main/java/com/cta4j/bus/api/core/util/ApiUtils.java rename to src/main/java/com/cta4j/bus/internal/util/ApiUtils.java index 9e0c9a4..d9046f0 100644 --- a/src/main/java/com/cta4j/bus/api/core/util/ApiUtils.java +++ b/src/main/java/com/cta4j/bus/internal/util/ApiUtils.java @@ -1,6 +1,6 @@ -package com.cta4j.bus.api.core.util; +package com.cta4j.bus.internal.util; -import com.cta4j.bus.api.core.external.CtaError; +import com.cta4j.bus.internal.wire.CtaError; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/com/cta4j/common/util/DateTimeUtils.java b/src/main/java/com/cta4j/bus/internal/util/DateTimeUtils.java similarity index 95% rename from src/main/java/com/cta4j/common/util/DateTimeUtils.java rename to src/main/java/com/cta4j/bus/internal/util/DateTimeUtils.java index d837eb5..da99c35 100644 --- a/src/main/java/com/cta4j/common/util/DateTimeUtils.java +++ b/src/main/java/com/cta4j/bus/internal/util/DateTimeUtils.java @@ -1,4 +1,4 @@ -package com.cta4j.common.util; +package com.cta4j.bus.internal.util; import org.jetbrains.annotations.ApiStatus; diff --git a/src/main/java/com/cta4j/common/util/HttpUtils.java b/src/main/java/com/cta4j/bus/internal/util/HttpUtils.java similarity index 92% rename from src/main/java/com/cta4j/common/util/HttpUtils.java rename to src/main/java/com/cta4j/bus/internal/util/HttpUtils.java index 1b2f5b5..f661b47 100644 --- a/src/main/java/com/cta4j/common/util/HttpUtils.java +++ b/src/main/java/com/cta4j/bus/internal/util/HttpUtils.java @@ -1,6 +1,6 @@ -package com.cta4j.common.util; +package com.cta4j.bus.internal.util; -import com.cta4j.common.exception.Cta4jException; +import com.cta4j.exception.Cta4jException; import org.apache.hc.client5.http.fluent.Request; import org.jetbrains.annotations.ApiStatus; diff --git a/src/main/java/com/cta4j/bus/api/core/external/CtaBustimeResponse.java b/src/main/java/com/cta4j/bus/internal/wire/CtaBustimeResponse.java similarity index 93% rename from src/main/java/com/cta4j/bus/api/core/external/CtaBustimeResponse.java rename to src/main/java/com/cta4j/bus/internal/wire/CtaBustimeResponse.java index 1be0939..fa8bf59 100644 --- a/src/main/java/com/cta4j/bus/api/core/external/CtaBustimeResponse.java +++ b/src/main/java/com/cta4j/bus/internal/wire/CtaBustimeResponse.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.core.external; +package com.cta4j.bus.internal.wire; import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; diff --git a/src/main/java/com/cta4j/bus/api/core/external/CtaError.java b/src/main/java/com/cta4j/bus/internal/wire/CtaError.java similarity index 89% rename from src/main/java/com/cta4j/bus/api/core/external/CtaError.java rename to src/main/java/com/cta4j/bus/internal/wire/CtaError.java index 822f4c9..04f2b6d 100644 --- a/src/main/java/com/cta4j/bus/api/core/external/CtaError.java +++ b/src/main/java/com/cta4j/bus/internal/wire/CtaError.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.core.external; +package com.cta4j.bus.internal.wire; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.jetbrains.annotations.ApiStatus; diff --git a/src/main/java/com/cta4j/bus/api/core/external/CtaResponse.java b/src/main/java/com/cta4j/bus/internal/wire/CtaResponse.java similarity index 92% rename from src/main/java/com/cta4j/bus/api/core/external/CtaResponse.java rename to src/main/java/com/cta4j/bus/internal/wire/CtaResponse.java index b68c8ae..c532cc5 100644 --- a/src/main/java/com/cta4j/bus/api/core/external/CtaResponse.java +++ b/src/main/java/com/cta4j/bus/internal/wire/CtaResponse.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.core.external; +package com.cta4j.bus.internal.wire; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/com/cta4j/bus/api/locale/LocalesApi.java b/src/main/java/com/cta4j/bus/locale/LocalesApi.java similarity index 76% rename from src/main/java/com/cta4j/bus/api/locale/LocalesApi.java rename to src/main/java/com/cta4j/bus/locale/LocalesApi.java index f801099..772cce1 100644 --- a/src/main/java/com/cta4j/bus/api/locale/LocalesApi.java +++ b/src/main/java/com/cta4j/bus/locale/LocalesApi.java @@ -1,6 +1,6 @@ -package com.cta4j.bus.api.locale; +package com.cta4j.bus.locale; -import com.cta4j.bus.api.locale.model.SupportedLocale; +import com.cta4j.bus.locale.model.SupportedLocale; import org.jspecify.annotations.NullMarked; import java.util.List; diff --git a/src/main/java/com/cta4j/bus/api/locale/impl/LocalesApiImpl.java b/src/main/java/com/cta4j/bus/locale/internal/impl/LocalesApiImpl.java similarity index 84% rename from src/main/java/com/cta4j/bus/api/locale/impl/LocalesApiImpl.java rename to src/main/java/com/cta4j/bus/locale/internal/impl/LocalesApiImpl.java index 480a793..b050450 100644 --- a/src/main/java/com/cta4j/bus/api/locale/impl/LocalesApiImpl.java +++ b/src/main/java/com/cta4j/bus/locale/internal/impl/LocalesApiImpl.java @@ -1,16 +1,16 @@ -package com.cta4j.bus.api.locale.impl; - -import com.cta4j.bus.api.core.context.BusApiContext; -import com.cta4j.bus.api.core.external.CtaBustimeResponse; -import com.cta4j.bus.api.core.external.CtaError; -import com.cta4j.bus.api.core.external.CtaResponse; -import com.cta4j.bus.api.core.util.ApiUtils; -import com.cta4j.bus.api.locale.LocalesApi; -import com.cta4j.bus.api.locale.external.CtaLocale; -import com.cta4j.bus.api.locale.mapper.SupportedLocaleMapper; -import com.cta4j.bus.api.locale.model.SupportedLocale; -import com.cta4j.common.exception.Cta4jException; -import com.cta4j.common.util.HttpUtils; +package com.cta4j.bus.locale.internal.impl; + +import com.cta4j.bus.internal.context.BusApiContext; +import com.cta4j.bus.internal.wire.CtaBustimeResponse; +import com.cta4j.bus.internal.wire.CtaError; +import com.cta4j.bus.internal.wire.CtaResponse; +import com.cta4j.bus.internal.util.ApiUtils; +import com.cta4j.bus.locale.LocalesApi; +import com.cta4j.bus.locale.internal.wire.CtaLocale; +import com.cta4j.bus.locale.internal.mapper.SupportedLocaleMapper; +import com.cta4j.bus.locale.model.SupportedLocale; +import com.cta4j.exception.Cta4jException; +import com.cta4j.bus.internal.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/com/cta4j/bus/api/locale/mapper/SupportedLocaleMapper.java b/src/main/java/com/cta4j/bus/locale/internal/mapper/SupportedLocaleMapper.java similarity index 62% rename from src/main/java/com/cta4j/bus/api/locale/mapper/SupportedLocaleMapper.java rename to src/main/java/com/cta4j/bus/locale/internal/mapper/SupportedLocaleMapper.java index 939de4e..1011aff 100644 --- a/src/main/java/com/cta4j/bus/api/locale/mapper/SupportedLocaleMapper.java +++ b/src/main/java/com/cta4j/bus/locale/internal/mapper/SupportedLocaleMapper.java @@ -1,13 +1,13 @@ -package com.cta4j.bus.api.locale.mapper; +package com.cta4j.bus.locale.internal.mapper; -import com.cta4j.bus.api.core.util.CtaBusMappingQualifiers; -import com.cta4j.bus.api.locale.external.CtaLocale; -import com.cta4j.bus.api.locale.model.SupportedLocale; +import com.cta4j.bus.internal.mapper.Qualifiers; +import com.cta4j.bus.locale.internal.wire.CtaLocale; +import com.cta4j.bus.locale.model.SupportedLocale; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.factory.Mappers; -@Mapper(uses = CtaBusMappingQualifiers.class) +@Mapper(uses = Qualifiers.class) public interface SupportedLocaleMapper { SupportedLocaleMapper INSTANCE = Mappers.getMapper(SupportedLocaleMapper.class); diff --git a/src/main/java/com/cta4j/bus/api/locale/external/CtaLocale.java b/src/main/java/com/cta4j/bus/locale/internal/wire/CtaLocale.java similarity index 91% rename from src/main/java/com/cta4j/bus/api/locale/external/CtaLocale.java rename to src/main/java/com/cta4j/bus/locale/internal/wire/CtaLocale.java index 2a81083..53d3bdd 100644 --- a/src/main/java/com/cta4j/bus/api/locale/external/CtaLocale.java +++ b/src/main/java/com/cta4j/bus/locale/internal/wire/CtaLocale.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.locale.external; +package com.cta4j.bus.locale.internal.wire; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.jetbrains.annotations.ApiStatus; diff --git a/src/main/java/com/cta4j/bus/api/locale/model/SupportedLocale.java b/src/main/java/com/cta4j/bus/locale/model/SupportedLocale.java similarity index 88% rename from src/main/java/com/cta4j/bus/api/locale/model/SupportedLocale.java rename to src/main/java/com/cta4j/bus/locale/model/SupportedLocale.java index 3552e21..47aaa3d 100644 --- a/src/main/java/com/cta4j/bus/api/locale/model/SupportedLocale.java +++ b/src/main/java/com/cta4j/bus/locale/model/SupportedLocale.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.locale.model; +package com.cta4j.bus.locale.model; import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/com/cta4j/bus/api/pattern/PatternsApi.java b/src/main/java/com/cta4j/bus/pattern/PatternsApi.java similarity index 84% rename from src/main/java/com/cta4j/bus/api/pattern/PatternsApi.java rename to src/main/java/com/cta4j/bus/pattern/PatternsApi.java index b49ad01..0297334 100644 --- a/src/main/java/com/cta4j/bus/api/pattern/PatternsApi.java +++ b/src/main/java/com/cta4j/bus/pattern/PatternsApi.java @@ -1,6 +1,6 @@ -package com.cta4j.bus.api.pattern; +package com.cta4j.bus.pattern; -import com.cta4j.bus.api.pattern.model.RoutePattern; +import com.cta4j.bus.pattern.model.RoutePattern; import org.jspecify.annotations.NullMarked; import java.util.Collection; diff --git a/src/main/java/com/cta4j/bus/api/pattern/impl/PatternsApiImpl.java b/src/main/java/com/cta4j/bus/pattern/internal/impl/PatternsApiImpl.java similarity index 85% rename from src/main/java/com/cta4j/bus/api/pattern/impl/PatternsApiImpl.java rename to src/main/java/com/cta4j/bus/pattern/internal/impl/PatternsApiImpl.java index cb4ff45..3b25636 100644 --- a/src/main/java/com/cta4j/bus/api/pattern/impl/PatternsApiImpl.java +++ b/src/main/java/com/cta4j/bus/pattern/internal/impl/PatternsApiImpl.java @@ -1,16 +1,16 @@ -package com.cta4j.bus.api.pattern.impl; - -import com.cta4j.bus.api.core.context.BusApiContext; -import com.cta4j.bus.api.core.util.ApiUtils; -import com.cta4j.bus.api.pattern.PatternsApi; -import com.cta4j.bus.api.pattern.external.CtaPattern; -import com.cta4j.bus.api.pattern.mapper.RoutePatternMapper; -import com.cta4j.bus.api.pattern.model.RoutePattern; -import com.cta4j.bus.api.core.external.CtaBustimeResponse; -import com.cta4j.bus.api.core.external.CtaError; -import com.cta4j.bus.api.core.external.CtaResponse; -import com.cta4j.common.exception.Cta4jException; -import com.cta4j.common.util.HttpUtils; +package com.cta4j.bus.pattern.internal.impl; + +import com.cta4j.bus.internal.context.BusApiContext; +import com.cta4j.bus.internal.util.ApiUtils; +import com.cta4j.bus.pattern.PatternsApi; +import com.cta4j.bus.pattern.internal.wire.CtaPattern; +import com.cta4j.bus.pattern.internal.mapper.RoutePatternMapper; +import com.cta4j.bus.pattern.model.RoutePattern; +import com.cta4j.bus.internal.wire.CtaBustimeResponse; +import com.cta4j.bus.internal.wire.CtaError; +import com.cta4j.bus.internal.wire.CtaResponse; +import com.cta4j.exception.Cta4jException; +import com.cta4j.bus.internal.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/com/cta4j/bus/api/pattern/mapper/RoutePatternMapper.java b/src/main/java/com/cta4j/bus/pattern/internal/mapper/RoutePatternMapper.java similarity index 75% rename from src/main/java/com/cta4j/bus/api/pattern/mapper/RoutePatternMapper.java rename to src/main/java/com/cta4j/bus/pattern/internal/mapper/RoutePatternMapper.java index 006d02f..3c566d0 100644 --- a/src/main/java/com/cta4j/bus/api/pattern/mapper/RoutePatternMapper.java +++ b/src/main/java/com/cta4j/bus/pattern/internal/mapper/RoutePatternMapper.java @@ -1,16 +1,16 @@ -package com.cta4j.bus.api.pattern.mapper; +package com.cta4j.bus.pattern.internal.mapper; -import com.cta4j.bus.api.pattern.external.CtaPattern; -import com.cta4j.bus.api.pattern.external.CtaPoint; -import com.cta4j.bus.api.core.util.CtaBusMappingQualifiers; -import com.cta4j.bus.api.pattern.model.PatternPoint; -import com.cta4j.bus.api.pattern.model.RoutePattern; +import com.cta4j.bus.pattern.internal.wire.CtaPattern; +import com.cta4j.bus.pattern.internal.wire.CtaPoint; +import com.cta4j.bus.internal.mapper.Qualifiers; +import com.cta4j.bus.pattern.model.PatternPoint; +import com.cta4j.bus.pattern.model.RoutePattern; import org.jetbrains.annotations.ApiStatus; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.factory.Mappers; -@Mapper(uses = CtaBusMappingQualifiers.class) +@Mapper(uses = Qualifiers.class) @ApiStatus.Internal public interface RoutePatternMapper { RoutePatternMapper INSTANCE = Mappers.getMapper(RoutePatternMapper.class); diff --git a/src/main/java/com/cta4j/bus/api/pattern/external/CtaPattern.java b/src/main/java/com/cta4j/bus/pattern/internal/wire/CtaPattern.java similarity index 94% rename from src/main/java/com/cta4j/bus/api/pattern/external/CtaPattern.java rename to src/main/java/com/cta4j/bus/pattern/internal/wire/CtaPattern.java index c7e027c..362806d 100644 --- a/src/main/java/com/cta4j/bus/api/pattern/external/CtaPattern.java +++ b/src/main/java/com/cta4j/bus/pattern/internal/wire/CtaPattern.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.pattern.external; +package com.cta4j.bus.pattern.internal.wire; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.jetbrains.annotations.ApiStatus; diff --git a/src/main/java/com/cta4j/bus/api/pattern/external/CtaPoint.java b/src/main/java/com/cta4j/bus/pattern/internal/wire/CtaPoint.java similarity index 92% rename from src/main/java/com/cta4j/bus/api/pattern/external/CtaPoint.java rename to src/main/java/com/cta4j/bus/pattern/internal/wire/CtaPoint.java index c49c634..07fd622 100644 --- a/src/main/java/com/cta4j/bus/api/pattern/external/CtaPoint.java +++ b/src/main/java/com/cta4j/bus/pattern/internal/wire/CtaPoint.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.pattern.external; +package com.cta4j.bus.pattern.internal.wire; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.jetbrains.annotations.ApiStatus; diff --git a/src/main/java/com/cta4j/bus/api/pattern/model/PatternPoint.java b/src/main/java/com/cta4j/bus/pattern/model/PatternPoint.java similarity index 93% rename from src/main/java/com/cta4j/bus/api/pattern/model/PatternPoint.java rename to src/main/java/com/cta4j/bus/pattern/model/PatternPoint.java index 0a9244e..5c0ff52 100644 --- a/src/main/java/com/cta4j/bus/api/pattern/model/PatternPoint.java +++ b/src/main/java/com/cta4j/bus/pattern/model/PatternPoint.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.pattern.model; +package com.cta4j.bus.pattern.model; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; diff --git a/src/main/java/com/cta4j/bus/api/pattern/model/PatternPointType.java b/src/main/java/com/cta4j/bus/pattern/model/PatternPointType.java similarity index 58% rename from src/main/java/com/cta4j/bus/api/pattern/model/PatternPointType.java rename to src/main/java/com/cta4j/bus/pattern/model/PatternPointType.java index 3a28efa..f404ec6 100644 --- a/src/main/java/com/cta4j/bus/api/pattern/model/PatternPointType.java +++ b/src/main/java/com/cta4j/bus/pattern/model/PatternPointType.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.pattern.model; +package com.cta4j.bus.pattern.model; public enum PatternPointType { STOP, diff --git a/src/main/java/com/cta4j/bus/api/pattern/model/RoutePattern.java b/src/main/java/com/cta4j/bus/pattern/model/RoutePattern.java similarity index 95% rename from src/main/java/com/cta4j/bus/api/pattern/model/RoutePattern.java rename to src/main/java/com/cta4j/bus/pattern/model/RoutePattern.java index ed08840..69c436c 100644 --- a/src/main/java/com/cta4j/bus/api/pattern/model/RoutePattern.java +++ b/src/main/java/com/cta4j/bus/pattern/model/RoutePattern.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.pattern.model; +package com.cta4j.bus.pattern.model; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; diff --git a/src/main/java/com/cta4j/bus/api/prediction/PredictionsApi.java b/src/main/java/com/cta4j/bus/prediction/PredictionsApi.java similarity index 84% rename from src/main/java/com/cta4j/bus/api/prediction/PredictionsApi.java rename to src/main/java/com/cta4j/bus/prediction/PredictionsApi.java index ee66711..a81e842 100644 --- a/src/main/java/com/cta4j/bus/api/prediction/PredictionsApi.java +++ b/src/main/java/com/cta4j/bus/prediction/PredictionsApi.java @@ -1,8 +1,8 @@ -package com.cta4j.bus.api.prediction; +package com.cta4j.bus.prediction; -import com.cta4j.bus.api.prediction.model.Prediction; -import com.cta4j.bus.api.prediction.query.StopsPredictionsQuery; -import com.cta4j.bus.api.prediction.query.VehiclesPredictionsQuery; +import com.cta4j.bus.prediction.model.Prediction; +import com.cta4j.bus.prediction.query.StopsPredictionsQuery; +import com.cta4j.bus.prediction.query.VehiclesPredictionsQuery; import org.jspecify.annotations.NullMarked; import java.util.List; diff --git a/src/main/java/com/cta4j/bus/api/prediction/impl/PredictionsApiImpl.java b/src/main/java/com/cta4j/bus/prediction/internal/impl/PredictionsApiImpl.java similarity index 86% rename from src/main/java/com/cta4j/bus/api/prediction/impl/PredictionsApiImpl.java rename to src/main/java/com/cta4j/bus/prediction/internal/impl/PredictionsApiImpl.java index 8b0a2c6..c94dbb7 100644 --- a/src/main/java/com/cta4j/bus/api/prediction/impl/PredictionsApiImpl.java +++ b/src/main/java/com/cta4j/bus/prediction/internal/impl/PredictionsApiImpl.java @@ -1,18 +1,18 @@ -package com.cta4j.bus.api.prediction.impl; - -import com.cta4j.bus.api.core.context.BusApiContext; -import com.cta4j.bus.api.core.util.ApiUtils; -import com.cta4j.bus.api.prediction.PredictionsApi; -import com.cta4j.bus.api.prediction.external.CtaPrediction; -import com.cta4j.bus.api.prediction.mapper.PredictionMapper; -import com.cta4j.bus.api.prediction.model.Prediction; -import com.cta4j.bus.api.prediction.query.StopsPredictionsQuery; -import com.cta4j.bus.api.prediction.query.VehiclesPredictionsQuery; -import com.cta4j.bus.api.core.external.CtaBustimeResponse; -import com.cta4j.bus.api.core.external.CtaError; -import com.cta4j.bus.api.core.external.CtaResponse; -import com.cta4j.common.exception.Cta4jException; -import com.cta4j.common.util.HttpUtils; +package com.cta4j.bus.prediction.internal.impl; + +import com.cta4j.bus.internal.context.BusApiContext; +import com.cta4j.bus.internal.util.ApiUtils; +import com.cta4j.bus.prediction.PredictionsApi; +import com.cta4j.bus.prediction.internal.wire.CtaPrediction; +import com.cta4j.bus.prediction.internal.mapper.PredictionMapper; +import com.cta4j.bus.prediction.model.Prediction; +import com.cta4j.bus.prediction.query.StopsPredictionsQuery; +import com.cta4j.bus.prediction.query.VehiclesPredictionsQuery; +import com.cta4j.bus.internal.wire.CtaBustimeResponse; +import com.cta4j.bus.internal.wire.CtaError; +import com.cta4j.bus.internal.wire.CtaResponse; +import com.cta4j.exception.Cta4jException; +import com.cta4j.bus.internal.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/com/cta4j/bus/api/prediction/mapper/PredictionMapper.java b/src/main/java/com/cta4j/bus/prediction/internal/mapper/PredictionMapper.java similarity index 87% rename from src/main/java/com/cta4j/bus/api/prediction/mapper/PredictionMapper.java rename to src/main/java/com/cta4j/bus/prediction/internal/mapper/PredictionMapper.java index 2d3a6da..a13f2d7 100644 --- a/src/main/java/com/cta4j/bus/api/prediction/mapper/PredictionMapper.java +++ b/src/main/java/com/cta4j/bus/prediction/internal/mapper/PredictionMapper.java @@ -1,14 +1,14 @@ -package com.cta4j.bus.api.prediction.mapper; +package com.cta4j.bus.prediction.internal.mapper; -import com.cta4j.bus.api.prediction.external.CtaPrediction; -import com.cta4j.bus.api.core.util.CtaBusMappingQualifiers; -import com.cta4j.bus.api.prediction.model.Prediction; +import com.cta4j.bus.prediction.internal.wire.CtaPrediction; +import com.cta4j.bus.internal.mapper.Qualifiers; +import com.cta4j.bus.prediction.model.Prediction; import org.jetbrains.annotations.ApiStatus; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.factory.Mappers; -@Mapper(uses = CtaBusMappingQualifiers.class) +@Mapper(uses = Qualifiers.class) @ApiStatus.Internal public interface PredictionMapper { PredictionMapper INSTANCE = Mappers.getMapper(PredictionMapper.class); diff --git a/src/main/java/com/cta4j/bus/api/prediction/external/CtaPrediction.java b/src/main/java/com/cta4j/bus/prediction/internal/wire/CtaPrediction.java similarity index 96% rename from src/main/java/com/cta4j/bus/api/prediction/external/CtaPrediction.java rename to src/main/java/com/cta4j/bus/prediction/internal/wire/CtaPrediction.java index 0da78a5..67046d6 100644 --- a/src/main/java/com/cta4j/bus/api/prediction/external/CtaPrediction.java +++ b/src/main/java/com/cta4j/bus/prediction/internal/wire/CtaPrediction.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.prediction.external; +package com.cta4j.bus.prediction.internal.wire; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.jetbrains.annotations.ApiStatus; diff --git a/src/main/java/com/cta4j/bus/api/prediction/model/DynamicAction.java b/src/main/java/com/cta4j/bus/prediction/model/DynamicAction.java similarity index 92% rename from src/main/java/com/cta4j/bus/api/prediction/model/DynamicAction.java rename to src/main/java/com/cta4j/bus/prediction/model/DynamicAction.java index 509054a..705eaac 100644 --- a/src/main/java/com/cta4j/bus/api/prediction/model/DynamicAction.java +++ b/src/main/java/com/cta4j/bus/prediction/model/DynamicAction.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.prediction.model; +package com.cta4j.bus.prediction.model; public enum DynamicAction { NONE(0), diff --git a/src/main/java/com/cta4j/bus/api/prediction/model/FlagStop.java b/src/main/java/com/cta4j/bus/prediction/model/FlagStop.java similarity index 85% rename from src/main/java/com/cta4j/bus/api/prediction/model/FlagStop.java rename to src/main/java/com/cta4j/bus/prediction/model/FlagStop.java index c4cadeb..1ca7774 100644 --- a/src/main/java/com/cta4j/bus/api/prediction/model/FlagStop.java +++ b/src/main/java/com/cta4j/bus/prediction/model/FlagStop.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.prediction.model; +package com.cta4j.bus.prediction.model; public enum FlagStop { UNDEFINED(-1), diff --git a/src/main/java/com/cta4j/bus/api/prediction/model/PassengerLoad.java b/src/main/java/com/cta4j/bus/prediction/model/PassengerLoad.java similarity index 65% rename from src/main/java/com/cta4j/bus/api/prediction/model/PassengerLoad.java rename to src/main/java/com/cta4j/bus/prediction/model/PassengerLoad.java index c05e3f6..018e40c 100644 --- a/src/main/java/com/cta4j/bus/api/prediction/model/PassengerLoad.java +++ b/src/main/java/com/cta4j/bus/prediction/model/PassengerLoad.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.prediction.model; +package com.cta4j.bus.prediction.model; public enum PassengerLoad { FULL, diff --git a/src/main/java/com/cta4j/bus/api/prediction/model/Prediction.java b/src/main/java/com/cta4j/bus/prediction/model/Prediction.java similarity index 96% rename from src/main/java/com/cta4j/bus/api/prediction/model/Prediction.java rename to src/main/java/com/cta4j/bus/prediction/model/Prediction.java index f71bba9..b2ce3eb 100644 --- a/src/main/java/com/cta4j/bus/api/prediction/model/Prediction.java +++ b/src/main/java/com/cta4j/bus/prediction/model/Prediction.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.prediction.model; +package com.cta4j.bus.prediction.model; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; diff --git a/src/main/java/com/cta4j/bus/api/prediction/model/PredictionMetadata.java b/src/main/java/com/cta4j/bus/prediction/model/PredictionMetadata.java similarity index 95% rename from src/main/java/com/cta4j/bus/api/prediction/model/PredictionMetadata.java rename to src/main/java/com/cta4j/bus/prediction/model/PredictionMetadata.java index 0c38a6b..9dc8a13 100644 --- a/src/main/java/com/cta4j/bus/api/prediction/model/PredictionMetadata.java +++ b/src/main/java/com/cta4j/bus/prediction/model/PredictionMetadata.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.prediction.model; +package com.cta4j.bus.prediction.model; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; diff --git a/src/main/java/com/cta4j/bus/api/prediction/model/PredictionType.java b/src/main/java/com/cta4j/bus/prediction/model/PredictionType.java similarity index 57% rename from src/main/java/com/cta4j/bus/api/prediction/model/PredictionType.java rename to src/main/java/com/cta4j/bus/prediction/model/PredictionType.java index 53d447c..87c52f3 100644 --- a/src/main/java/com/cta4j/bus/api/prediction/model/PredictionType.java +++ b/src/main/java/com/cta4j/bus/prediction/model/PredictionType.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.prediction.model; +package com.cta4j.bus.prediction.model; public enum PredictionType { ARRIVAL, diff --git a/src/main/java/com/cta4j/bus/api/prediction/query/StopsPredictionsQuery.java b/src/main/java/com/cta4j/bus/prediction/query/StopsPredictionsQuery.java similarity index 98% rename from src/main/java/com/cta4j/bus/api/prediction/query/StopsPredictionsQuery.java rename to src/main/java/com/cta4j/bus/prediction/query/StopsPredictionsQuery.java index 588d5c1..5606a5b 100644 --- a/src/main/java/com/cta4j/bus/api/prediction/query/StopsPredictionsQuery.java +++ b/src/main/java/com/cta4j/bus/prediction/query/StopsPredictionsQuery.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.prediction.query; +package com.cta4j.bus.prediction.query; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; diff --git a/src/main/java/com/cta4j/bus/api/prediction/query/VehiclesPredictionsQuery.java b/src/main/java/com/cta4j/bus/prediction/query/VehiclesPredictionsQuery.java similarity index 97% rename from src/main/java/com/cta4j/bus/api/prediction/query/VehiclesPredictionsQuery.java rename to src/main/java/com/cta4j/bus/prediction/query/VehiclesPredictionsQuery.java index 39d6a38..4fb4182 100644 --- a/src/main/java/com/cta4j/bus/api/prediction/query/VehiclesPredictionsQuery.java +++ b/src/main/java/com/cta4j/bus/prediction/query/VehiclesPredictionsQuery.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.prediction.query; +package com.cta4j.bus.prediction.query; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; diff --git a/src/main/java/com/cta4j/bus/api/route/RoutesApi.java b/src/main/java/com/cta4j/bus/route/RoutesApi.java similarity index 64% rename from src/main/java/com/cta4j/bus/api/route/RoutesApi.java rename to src/main/java/com/cta4j/bus/route/RoutesApi.java index 242d1af..ca1615a 100644 --- a/src/main/java/com/cta4j/bus/api/route/RoutesApi.java +++ b/src/main/java/com/cta4j/bus/route/RoutesApi.java @@ -1,6 +1,6 @@ -package com.cta4j.bus.api.route; +package com.cta4j.bus.route; -import com.cta4j.bus.api.route.model.Route; +import com.cta4j.bus.route.model.Route; import org.jspecify.annotations.NullMarked; import java.util.List; diff --git a/src/main/java/com/cta4j/bus/api/route/impl/RoutesApiImpl.java b/src/main/java/com/cta4j/bus/route/internal/impl/RoutesApiImpl.java similarity index 81% rename from src/main/java/com/cta4j/bus/api/route/impl/RoutesApiImpl.java rename to src/main/java/com/cta4j/bus/route/internal/impl/RoutesApiImpl.java index cd8299e..b10a1a7 100644 --- a/src/main/java/com/cta4j/bus/api/route/impl/RoutesApiImpl.java +++ b/src/main/java/com/cta4j/bus/route/internal/impl/RoutesApiImpl.java @@ -1,16 +1,16 @@ -package com.cta4j.bus.api.route.impl; - -import com.cta4j.bus.api.core.context.BusApiContext; -import com.cta4j.bus.api.core.util.ApiUtils; -import com.cta4j.bus.api.route.RoutesApi; -import com.cta4j.bus.api.route.external.CtaRoute; -import com.cta4j.bus.api.route.mapper.RouteMapper; -import com.cta4j.bus.api.route.model.Route; -import com.cta4j.bus.api.core.external.CtaBustimeResponse; -import com.cta4j.bus.api.core.external.CtaError; -import com.cta4j.bus.api.core.external.CtaResponse; -import com.cta4j.common.exception.Cta4jException; -import com.cta4j.common.util.HttpUtils; +package com.cta4j.bus.route.internal.impl; + +import com.cta4j.bus.internal.context.BusApiContext; +import com.cta4j.bus.internal.util.ApiUtils; +import com.cta4j.bus.route.RoutesApi; +import com.cta4j.bus.route.internal.wire.CtaRoute; +import com.cta4j.bus.route.internal.mapper.RouteMapper; +import com.cta4j.bus.route.model.Route; +import com.cta4j.bus.internal.wire.CtaBustimeResponse; +import com.cta4j.bus.internal.wire.CtaError; +import com.cta4j.bus.internal.wire.CtaResponse; +import com.cta4j.exception.Cta4jException; +import com.cta4j.bus.internal.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/com/cta4j/bus/api/route/mapper/RouteMapper.java b/src/main/java/com/cta4j/bus/route/internal/mapper/RouteMapper.java similarity index 80% rename from src/main/java/com/cta4j/bus/api/route/mapper/RouteMapper.java rename to src/main/java/com/cta4j/bus/route/internal/mapper/RouteMapper.java index 973653f..a93a077 100644 --- a/src/main/java/com/cta4j/bus/api/route/mapper/RouteMapper.java +++ b/src/main/java/com/cta4j/bus/route/internal/mapper/RouteMapper.java @@ -1,7 +1,7 @@ -package com.cta4j.bus.api.route.mapper; +package com.cta4j.bus.route.internal.mapper; -import com.cta4j.bus.api.route.external.CtaRoute; -import com.cta4j.bus.api.route.model.Route; +import com.cta4j.bus.route.internal.wire.CtaRoute; +import com.cta4j.bus.route.model.Route; import org.jetbrains.annotations.ApiStatus; import org.mapstruct.Mapper; import org.mapstruct.Mapping; diff --git a/src/main/java/com/cta4j/bus/api/route/external/CtaRoute.java b/src/main/java/com/cta4j/bus/route/internal/wire/CtaRoute.java similarity index 93% rename from src/main/java/com/cta4j/bus/api/route/external/CtaRoute.java rename to src/main/java/com/cta4j/bus/route/internal/wire/CtaRoute.java index d9e4bec..a77ab89 100644 --- a/src/main/java/com/cta4j/bus/api/route/external/CtaRoute.java +++ b/src/main/java/com/cta4j/bus/route/internal/wire/CtaRoute.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.route.external; +package com.cta4j.bus.route.internal.wire; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.jetbrains.annotations.ApiStatus; diff --git a/src/main/java/com/cta4j/bus/api/route/model/Route.java b/src/main/java/com/cta4j/bus/route/model/Route.java similarity index 91% rename from src/main/java/com/cta4j/bus/api/route/model/Route.java rename to src/main/java/com/cta4j/bus/route/model/Route.java index 655b780..99a9194 100644 --- a/src/main/java/com/cta4j/bus/api/route/model/Route.java +++ b/src/main/java/com/cta4j/bus/route/model/Route.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.route.model; +package com.cta4j.bus.route.model; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; diff --git a/src/main/java/com/cta4j/bus/api/stop/StopsApi.java b/src/main/java/com/cta4j/bus/stop/StopsApi.java similarity index 87% rename from src/main/java/com/cta4j/bus/api/stop/StopsApi.java rename to src/main/java/com/cta4j/bus/stop/StopsApi.java index 03c1c99..73dbce0 100644 --- a/src/main/java/com/cta4j/bus/api/stop/StopsApi.java +++ b/src/main/java/com/cta4j/bus/stop/StopsApi.java @@ -1,7 +1,7 @@ -package com.cta4j.bus.api.stop; +package com.cta4j.bus.stop; -import com.cta4j.bus.api.stop.model.Stop; -import com.cta4j.common.exception.Cta4jException; +import com.cta4j.bus.stop.model.Stop; +import com.cta4j.exception.Cta4jException; import org.jspecify.annotations.NullMarked; import java.util.Collection; diff --git a/src/main/java/com/cta4j/bus/api/stop/impl/StopsApiImpl.java b/src/main/java/com/cta4j/bus/stop/internal/impl/StopsApiImpl.java similarity index 85% rename from src/main/java/com/cta4j/bus/api/stop/impl/StopsApiImpl.java rename to src/main/java/com/cta4j/bus/stop/internal/impl/StopsApiImpl.java index 61ac523..c07c75c 100644 --- a/src/main/java/com/cta4j/bus/api/stop/impl/StopsApiImpl.java +++ b/src/main/java/com/cta4j/bus/stop/internal/impl/StopsApiImpl.java @@ -1,16 +1,16 @@ -package com.cta4j.bus.api.stop.impl; - -import com.cta4j.bus.api.core.context.BusApiContext; -import com.cta4j.bus.api.core.util.ApiUtils; -import com.cta4j.bus.api.stop.StopsApi; -import com.cta4j.bus.api.stop.external.CtaStop; -import com.cta4j.bus.api.stop.mapper.StopMapper; -import com.cta4j.bus.api.stop.model.Stop; -import com.cta4j.bus.api.core.external.CtaBustimeResponse; -import com.cta4j.bus.api.core.external.CtaError; -import com.cta4j.bus.api.core.external.CtaResponse; -import com.cta4j.common.exception.Cta4jException; -import com.cta4j.common.util.HttpUtils; +package com.cta4j.bus.stop.internal.impl; + +import com.cta4j.bus.internal.context.BusApiContext; +import com.cta4j.bus.internal.util.ApiUtils; +import com.cta4j.bus.stop.StopsApi; +import com.cta4j.bus.stop.internal.wire.CtaStop; +import com.cta4j.bus.stop.internal.mapper.StopMapper; +import com.cta4j.bus.stop.model.Stop; +import com.cta4j.bus.internal.wire.CtaBustimeResponse; +import com.cta4j.bus.internal.wire.CtaError; +import com.cta4j.bus.internal.wire.CtaResponse; +import com.cta4j.exception.Cta4jException; +import com.cta4j.bus.internal.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/com/cta4j/bus/api/stop/mapper/StopMapper.java b/src/main/java/com/cta4j/bus/stop/internal/mapper/StopMapper.java similarity index 84% rename from src/main/java/com/cta4j/bus/api/stop/mapper/StopMapper.java rename to src/main/java/com/cta4j/bus/stop/internal/mapper/StopMapper.java index 2a74ae3..05536f5 100644 --- a/src/main/java/com/cta4j/bus/api/stop/mapper/StopMapper.java +++ b/src/main/java/com/cta4j/bus/stop/internal/mapper/StopMapper.java @@ -1,7 +1,7 @@ -package com.cta4j.bus.api.stop.mapper; +package com.cta4j.bus.stop.internal.mapper; -import com.cta4j.bus.api.stop.external.CtaStop; -import com.cta4j.bus.api.stop.model.Stop; +import com.cta4j.bus.stop.internal.wire.CtaStop; +import com.cta4j.bus.stop.model.Stop; import org.jetbrains.annotations.ApiStatus; import org.mapstruct.Mapper; import org.mapstruct.Mapping; diff --git a/src/main/java/com/cta4j/bus/api/stop/external/CtaStop.java b/src/main/java/com/cta4j/bus/stop/internal/wire/CtaStop.java similarity index 95% rename from src/main/java/com/cta4j/bus/api/stop/external/CtaStop.java rename to src/main/java/com/cta4j/bus/stop/internal/wire/CtaStop.java index 6efa702..34ea8ff 100644 --- a/src/main/java/com/cta4j/bus/api/stop/external/CtaStop.java +++ b/src/main/java/com/cta4j/bus/stop/internal/wire/CtaStop.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.stop.external; +package com.cta4j.bus.stop.internal.wire; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.jetbrains.annotations.ApiStatus; diff --git a/src/main/java/com/cta4j/bus/api/stop/model/Stop.java b/src/main/java/com/cta4j/bus/stop/model/Stop.java similarity index 96% rename from src/main/java/com/cta4j/bus/api/stop/model/Stop.java rename to src/main/java/com/cta4j/bus/stop/model/Stop.java index d9bdf5f..fc822bd 100644 --- a/src/main/java/com/cta4j/bus/api/stop/model/Stop.java +++ b/src/main/java/com/cta4j/bus/stop/model/Stop.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.stop.model; +package com.cta4j.bus.stop.model; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; diff --git a/src/main/java/com/cta4j/bus/api/vehicle/VehiclesApi.java b/src/main/java/com/cta4j/bus/vehicle/VehiclesApi.java similarity index 89% rename from src/main/java/com/cta4j/bus/api/vehicle/VehiclesApi.java rename to src/main/java/com/cta4j/bus/vehicle/VehiclesApi.java index 973f6b0..b96d2e0 100644 --- a/src/main/java/com/cta4j/bus/api/vehicle/VehiclesApi.java +++ b/src/main/java/com/cta4j/bus/vehicle/VehiclesApi.java @@ -1,7 +1,7 @@ -package com.cta4j.bus.api.vehicle; +package com.cta4j.bus.vehicle; -import com.cta4j.bus.api.vehicle.model.Vehicle; -import com.cta4j.common.exception.Cta4jException; +import com.cta4j.bus.vehicle.model.Vehicle; +import com.cta4j.exception.Cta4jException; import org.jspecify.annotations.NullMarked; import java.util.Collection; diff --git a/src/main/java/com/cta4j/bus/api/vehicle/impl/VehiclesApiImpl.java b/src/main/java/com/cta4j/bus/vehicle/internal/impl/VehiclesApiImpl.java similarity index 84% rename from src/main/java/com/cta4j/bus/api/vehicle/impl/VehiclesApiImpl.java rename to src/main/java/com/cta4j/bus/vehicle/internal/impl/VehiclesApiImpl.java index 6871bde..70e0546 100644 --- a/src/main/java/com/cta4j/bus/api/vehicle/impl/VehiclesApiImpl.java +++ b/src/main/java/com/cta4j/bus/vehicle/internal/impl/VehiclesApiImpl.java @@ -1,16 +1,16 @@ -package com.cta4j.bus.api.vehicle.impl; - -import com.cta4j.bus.api.core.context.BusApiContext; -import com.cta4j.bus.api.core.util.ApiUtils; -import com.cta4j.bus.api.vehicle.VehiclesApi; -import com.cta4j.bus.api.vehicle.external.CtaVehicle; -import com.cta4j.bus.api.vehicle.mapper.VehicleMapper; -import com.cta4j.bus.api.vehicle.model.Vehicle; -import com.cta4j.bus.api.core.external.CtaBustimeResponse; -import com.cta4j.bus.api.core.external.CtaError; -import com.cta4j.bus.api.core.external.CtaResponse; -import com.cta4j.common.exception.Cta4jException; -import com.cta4j.common.util.HttpUtils; +package com.cta4j.bus.vehicle.internal.impl; + +import com.cta4j.bus.internal.context.BusApiContext; +import com.cta4j.bus.internal.util.ApiUtils; +import com.cta4j.bus.vehicle.VehiclesApi; +import com.cta4j.bus.vehicle.internal.wire.CtaVehicle; +import com.cta4j.bus.vehicle.internal.mapper.VehicleMapper; +import com.cta4j.bus.vehicle.model.Vehicle; +import com.cta4j.bus.internal.wire.CtaBustimeResponse; +import com.cta4j.bus.internal.wire.CtaError; +import com.cta4j.bus.internal.wire.CtaResponse; +import com.cta4j.exception.Cta4jException; +import com.cta4j.bus.internal.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/com/cta4j/bus/api/vehicle/mapper/VehicleMapper.java b/src/main/java/com/cta4j/bus/vehicle/internal/mapper/VehicleMapper.java similarity index 89% rename from src/main/java/com/cta4j/bus/api/vehicle/mapper/VehicleMapper.java rename to src/main/java/com/cta4j/bus/vehicle/internal/mapper/VehicleMapper.java index d82ed30..0428175 100644 --- a/src/main/java/com/cta4j/bus/api/vehicle/mapper/VehicleMapper.java +++ b/src/main/java/com/cta4j/bus/vehicle/internal/mapper/VehicleMapper.java @@ -1,14 +1,14 @@ -package com.cta4j.bus.api.vehicle.mapper; +package com.cta4j.bus.vehicle.internal.mapper; -import com.cta4j.bus.api.vehicle.external.CtaVehicle; -import com.cta4j.bus.api.core.util.CtaBusMappingQualifiers; -import com.cta4j.bus.api.vehicle.model.Vehicle; +import com.cta4j.bus.vehicle.internal.wire.CtaVehicle; +import com.cta4j.bus.internal.mapper.Qualifiers; +import com.cta4j.bus.vehicle.model.Vehicle; import org.jetbrains.annotations.ApiStatus; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.factory.Mappers; -@Mapper(uses = CtaBusMappingQualifiers.class) +@Mapper(uses = Qualifiers.class) @ApiStatus.Internal public interface VehicleMapper { VehicleMapper INSTANCE = Mappers.getMapper(VehicleMapper.class); diff --git a/src/main/java/com/cta4j/bus/api/vehicle/external/CtaVehicle.java b/src/main/java/com/cta4j/bus/vehicle/internal/wire/CtaVehicle.java similarity index 96% rename from src/main/java/com/cta4j/bus/api/vehicle/external/CtaVehicle.java rename to src/main/java/com/cta4j/bus/vehicle/internal/wire/CtaVehicle.java index d8b1ad6..1338f12 100644 --- a/src/main/java/com/cta4j/bus/api/vehicle/external/CtaVehicle.java +++ b/src/main/java/com/cta4j/bus/vehicle/internal/wire/CtaVehicle.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.vehicle.external; +package com.cta4j.bus.vehicle.internal.wire; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.jetbrains.annotations.ApiStatus; diff --git a/src/main/java/com/cta4j/bus/api/vehicle/model/TransitMode.java b/src/main/java/com/cta4j/bus/vehicle/model/TransitMode.java similarity index 86% rename from src/main/java/com/cta4j/bus/api/vehicle/model/TransitMode.java rename to src/main/java/com/cta4j/bus/vehicle/model/TransitMode.java index 015efad..e62858d 100644 --- a/src/main/java/com/cta4j/bus/api/vehicle/model/TransitMode.java +++ b/src/main/java/com/cta4j/bus/vehicle/model/TransitMode.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.vehicle.model; +package com.cta4j.bus.vehicle.model; public enum TransitMode { NONE(0), diff --git a/src/main/java/com/cta4j/bus/api/vehicle/model/Vehicle.java b/src/main/java/com/cta4j/bus/vehicle/model/Vehicle.java similarity index 92% rename from src/main/java/com/cta4j/bus/api/vehicle/model/Vehicle.java rename to src/main/java/com/cta4j/bus/vehicle/model/Vehicle.java index 5a004c7..d9db006 100644 --- a/src/main/java/com/cta4j/bus/api/vehicle/model/Vehicle.java +++ b/src/main/java/com/cta4j/bus/vehicle/model/Vehicle.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.vehicle.model; +package com.cta4j.bus.vehicle.model; import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/com/cta4j/bus/api/vehicle/model/VehicleCoordinates.java b/src/main/java/com/cta4j/bus/vehicle/model/VehicleCoordinates.java similarity index 94% rename from src/main/java/com/cta4j/bus/api/vehicle/model/VehicleCoordinates.java rename to src/main/java/com/cta4j/bus/vehicle/model/VehicleCoordinates.java index 2e13d64..1f3ad86 100644 --- a/src/main/java/com/cta4j/bus/api/vehicle/model/VehicleCoordinates.java +++ b/src/main/java/com/cta4j/bus/vehicle/model/VehicleCoordinates.java @@ -1,4 +1,4 @@ -package com.cta4j.bus.api.vehicle.model; +package com.cta4j.bus.vehicle.model; import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/com/cta4j/bus/api/vehicle/model/VehicleMetadata.java b/src/main/java/com/cta4j/bus/vehicle/model/VehicleMetadata.java similarity index 92% rename from src/main/java/com/cta4j/bus/api/vehicle/model/VehicleMetadata.java rename to src/main/java/com/cta4j/bus/vehicle/model/VehicleMetadata.java index c47d09a..4cdddbb 100644 --- a/src/main/java/com/cta4j/bus/api/vehicle/model/VehicleMetadata.java +++ b/src/main/java/com/cta4j/bus/vehicle/model/VehicleMetadata.java @@ -1,6 +1,6 @@ -package com.cta4j.bus.api.vehicle.model; +package com.cta4j.bus.vehicle.model; -import com.cta4j.bus.api.prediction.model.PassengerLoad; +import com.cta4j.bus.prediction.model.PassengerLoad; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; diff --git a/src/main/java/com/cta4j/common/exception/Cta4jException.java b/src/main/java/com/cta4j/exception/Cta4jException.java similarity index 94% rename from src/main/java/com/cta4j/common/exception/Cta4jException.java rename to src/main/java/com/cta4j/exception/Cta4jException.java index 221f7c1..c2b832c 100644 --- a/src/main/java/com/cta4j/common/exception/Cta4jException.java +++ b/src/main/java/com/cta4j/exception/Cta4jException.java @@ -1,4 +1,4 @@ -package com.cta4j.common.exception; +package com.cta4j.exception; /** * A custom exception class for handling cta4j specific errors. diff --git a/src/main/java/com/cta4j/train/client/TrainClient.java b/src/main/java/com/cta4j/train/client/TrainClient.java index 6542255..61efe4b 100644 --- a/src/main/java/com/cta4j/train/client/TrainClient.java +++ b/src/main/java/com/cta4j/train/client/TrainClient.java @@ -1,7 +1,7 @@ package com.cta4j.train.client; import com.cta4j.train.client.internal.TrainClientImpl; -import com.cta4j.common.exception.Cta4jException; +import com.cta4j.exception.Cta4jException; import com.cta4j.train.model.StationArrival; import com.cta4j.train.model.Train; import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/com/cta4j/train/client/internal/TrainClientImpl.java b/src/main/java/com/cta4j/train/client/internal/TrainClientImpl.java index 44dc407..5a89834 100644 --- a/src/main/java/com/cta4j/train/client/internal/TrainClientImpl.java +++ b/src/main/java/com/cta4j/train/client/internal/TrainClientImpl.java @@ -1,7 +1,7 @@ package com.cta4j.train.client.internal; import com.cta4j.train.client.TrainClient; -import com.cta4j.common.exception.Cta4jException; +import com.cta4j.exception.Cta4jException; import com.cta4j.train.external.arrival.CtaArrivalsCtatt; import com.cta4j.train.external.arrival.CtaArrivalsEta; import com.cta4j.train.external.arrival.CtaArrivalsResponse; @@ -16,7 +16,7 @@ import com.cta4j.train.model.Train; import com.cta4j.train.model.UpcomingTrainArrival; import com.cta4j.train.model.StationArrival; -import com.cta4j.common.util.HttpUtils; +import com.cta4j.bus.internal.util.HttpUtils; import org.apache.hc.core5.net.URIBuilder; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; diff --git a/src/main/java/com/cta4j/train/mapper/StationArrivalMapper.java b/src/main/java/com/cta4j/train/mapper/StationArrivalMapper.java index 812eaf0..e893eb9 100644 --- a/src/main/java/com/cta4j/train/mapper/StationArrivalMapper.java +++ b/src/main/java/com/cta4j/train/mapper/StationArrivalMapper.java @@ -3,7 +3,7 @@ import com.cta4j.train.external.arrival.CtaArrivalsEta; import com.cta4j.train.model.Route; import com.cta4j.train.model.StationArrival; -import com.cta4j.common.util.DateTimeUtils; +import com.cta4j.bus.internal.util.DateTimeUtils; import org.jetbrains.annotations.ApiStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/com/cta4j/train/mapper/UpcomingTrainArrivalMapper.java b/src/main/java/com/cta4j/train/mapper/UpcomingTrainArrivalMapper.java index fa81ea8..fe6cbbc 100644 --- a/src/main/java/com/cta4j/train/mapper/UpcomingTrainArrivalMapper.java +++ b/src/main/java/com/cta4j/train/mapper/UpcomingTrainArrivalMapper.java @@ -1,10 +1,10 @@ package com.cta4j.train.mapper; -import com.cta4j.common.exception.Cta4jException; +import com.cta4j.exception.Cta4jException; import com.cta4j.train.external.follow.CtaFollowEta; import com.cta4j.train.model.Route; import com.cta4j.train.model.UpcomingTrainArrival; -import com.cta4j.common.util.DateTimeUtils; +import com.cta4j.bus.internal.util.DateTimeUtils; import org.jetbrains.annotations.ApiStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; From d697a8cc3afa640dbeaf962b6fa53a90463043cf Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sat, 24 Jan 2026 14:42:06 -0600 Subject: [PATCH 49/53] API refactor --- .../com/cta4j/bus/detour/model/Detour.java | 28 +++++++++++++++++++ .../com/cta4j/exception/Cta4jException.java | 6 ++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/cta4j/bus/detour/model/Detour.java b/src/main/java/com/cta4j/bus/detour/model/Detour.java index 10bf051..a045226 100644 --- a/src/main/java/com/cta4j/bus/detour/model/Detour.java +++ b/src/main/java/com/cta4j/bus/detour/model/Detour.java @@ -7,6 +7,18 @@ import java.util.List; import java.util.Objects; +/** + * Represents a service detour affecting one or more routes and directions within a specific time window. + * + * @param id The unique ID of the detour + * @param version The version of the detour + * @param active Whether the detour is currently active + * @param description The human-readable description of the detour + * @param routeDirections The routes and directions affected by the detour + * @param startTime The time at which the detour begins + * @param endTime The time at which the detour ends + * @param dataFeed The identifier for the data feed that supplied the detour, or {@code null} if not available + */ @NullMarked public record Detour( String id, @@ -26,6 +38,20 @@ public record Detour( @Nullable String dataFeed ) { + /** + * Creates a {@code Detour}. + * + * @param id the detour ID + * @param version the detour version + * @param active whether the detour is active + * @param description the detour description + * @param routeDirections the {@code List} of route directions affected by the detour + * @param startTime the detour start time + * @param endTime the detour end time + * @param dataFeed the data feed ID, or {@code null} if not available + * @throws NullPointerException if {@code id}, {@code version}, {@code description}, {@code routeDirections}, + * {@code startTime}, or {@code endTime} is {@code null} + */ public Detour { Objects.requireNonNull(id); Objects.requireNonNull(version); @@ -35,5 +61,7 @@ public record Detour( Objects.requireNonNull(endTime); routeDirections.forEach(Objects::requireNonNull); + + routeDirections = List.copyOf(routeDirections); } } diff --git a/src/main/java/com/cta4j/exception/Cta4jException.java b/src/main/java/com/cta4j/exception/Cta4jException.java index c2b832c..7dba98e 100644 --- a/src/main/java/com/cta4j/exception/Cta4jException.java +++ b/src/main/java/com/cta4j/exception/Cta4jException.java @@ -1,11 +1,11 @@ package com.cta4j.exception; /** - * A custom exception class for handling cta4j specific errors. + * A custom exception class for handling cta4j-specific errors. */ public class Cta4jException extends RuntimeException { /** - * Constructs a new Cta4jException with the specified detail message. + * Constructs a new {@code Cta4jException} with the specified detail message. * * @param message the detail message */ @@ -14,7 +14,7 @@ public Cta4jException(String message) { } /** - * Constructs a new Cta4jException with the specified detail message and cause. + * Constructs a new {@code Cta4jException} with the specified detail message and cause. * * @param message the detail message * @param cause the cause of the exception From ec8c8318dcfdca9c74b1dbe1e31ea5eea7cd8512 Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sat, 24 Jan 2026 14:57:05 -0600 Subject: [PATCH 50/53] API refactor --- .../java/com/cta4j/bus/detour/DetoursApi.java | 6 +++--- .../java/com/cta4j/bus/detour/model/Detour.java | 16 ++++++++-------- .../bus/detour/model/DetourRouteDirection.java | 13 +++++++++++++ 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/cta4j/bus/detour/DetoursApi.java b/src/main/java/com/cta4j/bus/detour/DetoursApi.java index 96c0d2f..ae9ac0c 100644 --- a/src/main/java/com/cta4j/bus/detour/DetoursApi.java +++ b/src/main/java/com/cta4j/bus/detour/DetoursApi.java @@ -16,7 +16,7 @@ public interface DetoursApi { /** * Retrieves all active detours. * - * @return a {@link List} of active {@link Detour} instances; + * @return a {@link List} of active {@link Detour}s; * an empty {@link List} if no detours are currently active */ List list(); @@ -25,7 +25,7 @@ public interface DetoursApi { * Retrieves all active detours for the specified route. * * @param routeId the route identifier - * @return a {@link List} of {@link Detour} instances associated with the route; + * @return a {@link List} of {@link Detour}s associated with the route; * an empty {@link List} if no detours exist for the route * @throws NullPointerException if {@code routeId} is {@code null} */ @@ -36,7 +36,7 @@ public interface DetoursApi { * * @param routeId the route identifier * @param direction the travel direction (e.g. Northbound, Southbound) - * @return a {@link List} of {@link Detour} instances matching the route and direction; + * @return a {@link List} of {@link Detour}s matching the route and direction; * an empty {@link List} if no detours match * @throws NullPointerException if {@code routeId} or {@code direction} is {@code null} */ diff --git a/src/main/java/com/cta4j/bus/detour/model/Detour.java b/src/main/java/com/cta4j/bus/detour/model/Detour.java index a045226..bba29b1 100644 --- a/src/main/java/com/cta4j/bus/detour/model/Detour.java +++ b/src/main/java/com/cta4j/bus/detour/model/Detour.java @@ -41,14 +41,14 @@ public record Detour( /** * Creates a {@code Detour}. * - * @param id the detour ID - * @param version the detour version - * @param active whether the detour is active - * @param description the detour description - * @param routeDirections the {@code List} of route directions affected by the detour - * @param startTime the detour start time - * @param endTime the detour end time - * @param dataFeed the data feed ID, or {@code null} if not available + * @param id the unique ID of the detour + * @param version the version of the detour + * @param active whether the detour is currently active + * @param description the human-readable description of the detour + * @param routeDirections the routes and directions affected by the detour + * @param startTime the time at which the detour begins + * @param endTime the time at which the detour ends + * @param dataFeed the identifier for the data feed that supplied the detour, or {@code null} if not available * @throws NullPointerException if {@code id}, {@code version}, {@code description}, {@code routeDirections}, * {@code startTime}, or {@code endTime} is {@code null} */ diff --git a/src/main/java/com/cta4j/bus/detour/model/DetourRouteDirection.java b/src/main/java/com/cta4j/bus/detour/model/DetourRouteDirection.java index 1f68ec2..7db1bf0 100644 --- a/src/main/java/com/cta4j/bus/detour/model/DetourRouteDirection.java +++ b/src/main/java/com/cta4j/bus/detour/model/DetourRouteDirection.java @@ -4,12 +4,25 @@ import java.util.Objects; +/** + * Represents a route and direction affected by a detour. + * + * @param route The route ID of the detour + * @param direction The direction of the detour + */ @NullMarked public record DetourRouteDirection( String route, String direction ) { + /** + * Creates a {@code DetourRouteDirection}. + * + * @param route the route ID of the detour + * @param direction the direction of the detour + * @throws NullPointerException if {@code route} or {@code direction} is {@code null} + */ public DetourRouteDirection { Objects.requireNonNull(route); Objects.requireNonNull(direction); From f71c35c04a823b1ce9bd7a83c461faa90909363c Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sat, 24 Jan 2026 15:01:23 -0600 Subject: [PATCH 51/53] API refactor --- .../cta4j/bus/detour/internal/mapper/DetourMapper.java | 2 +- .../com/cta4j/bus/detour/model/DetourRouteDirection.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/cta4j/bus/detour/internal/mapper/DetourMapper.java b/src/main/java/com/cta4j/bus/detour/internal/mapper/DetourMapper.java index a495767..1cc4b1e 100644 --- a/src/main/java/com/cta4j/bus/detour/internal/mapper/DetourMapper.java +++ b/src/main/java/com/cta4j/bus/detour/internal/mapper/DetourMapper.java @@ -24,7 +24,7 @@ public interface DetourMapper { @Mapping(source = "rtpidatafeed", target = "dataFeed") Detour toDomain(CtaDetour dto); - @Mapping(source = "rt", target = "route") + @Mapping(source = "rt", target = "routeId") @Mapping(source = "dir", target = "direction") DetourRouteDirection toDomain(CtaDetoursRouteDirection dto); } diff --git a/src/main/java/com/cta4j/bus/detour/model/DetourRouteDirection.java b/src/main/java/com/cta4j/bus/detour/model/DetourRouteDirection.java index 7db1bf0..d4c2e78 100644 --- a/src/main/java/com/cta4j/bus/detour/model/DetourRouteDirection.java +++ b/src/main/java/com/cta4j/bus/detour/model/DetourRouteDirection.java @@ -7,24 +7,24 @@ /** * Represents a route and direction affected by a detour. * - * @param route The route ID of the detour + * @param routeId The route ID of the detour * @param direction The direction of the detour */ @NullMarked public record DetourRouteDirection( - String route, + String routeId, String direction ) { /** * Creates a {@code DetourRouteDirection}. * - * @param route the route ID of the detour + * @param routeId the route ID of the detour * @param direction the direction of the detour * @throws NullPointerException if {@code route} or {@code direction} is {@code null} */ public DetourRouteDirection { - Objects.requireNonNull(route); + Objects.requireNonNull(routeId); Objects.requireNonNull(direction); } } From c54bfc8cd8c9424d868581708281902150b514d2 Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sat, 24 Jan 2026 15:07:15 -0600 Subject: [PATCH 52/53] API refactor --- .../java/com/cta4j/bus/detour/DetoursApi.java | 16 ++++++++-------- .../bus/detour/model/DetourRouteDirection.java | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/cta4j/bus/detour/DetoursApi.java b/src/main/java/com/cta4j/bus/detour/DetoursApi.java index ae9ac0c..49759d6 100644 --- a/src/main/java/com/cta4j/bus/detour/DetoursApi.java +++ b/src/main/java/com/cta4j/bus/detour/DetoursApi.java @@ -22,22 +22,22 @@ public interface DetoursApi { List list(); /** - * Retrieves all active detours for the specified route. + * Retrieves all active detours for the specified route ID. * - * @param routeId the route identifier - * @return a {@link List} of {@link Detour}s associated with the route; - * an empty {@link List} if no detours exist for the route + * @param routeId the route ID + * @return a {@link List} of {@link Detour}s associated with the route ID; + * an empty {@link List} if no detours exist for the route ID * @throws NullPointerException if {@code routeId} is {@code null} */ List findByRouteId(String routeId); /** - * Retrieves all active detours for the specified route and direction. + * Retrieves all active detours for the specified route ID and direction. * - * @param routeId the route identifier + * @param routeId the route ID * @param direction the travel direction (e.g. Northbound, Southbound) - * @return a {@link List} of {@link Detour}s matching the route and direction; - * an empty {@link List} if no detours match + * @return a {@link List} of {@link Detour}s associated with the route ID and direction; + * an empty {@link List} if no detours exist for the route ID and direction * @throws NullPointerException if {@code routeId} or {@code direction} is {@code null} */ List findByRouteIdAndDirection(String routeId, String direction); diff --git a/src/main/java/com/cta4j/bus/detour/model/DetourRouteDirection.java b/src/main/java/com/cta4j/bus/detour/model/DetourRouteDirection.java index d4c2e78..d1c6db1 100644 --- a/src/main/java/com/cta4j/bus/detour/model/DetourRouteDirection.java +++ b/src/main/java/com/cta4j/bus/detour/model/DetourRouteDirection.java @@ -21,7 +21,7 @@ public record DetourRouteDirection( * * @param routeId the route ID of the detour * @param direction the direction of the detour - * @throws NullPointerException if {@code route} or {@code direction} is {@code null} + * @throws NullPointerException if {@code routeId} or {@code direction} is {@code null} */ public DetourRouteDirection { Objects.requireNonNull(routeId); From 83b3cdc8a587a5a6cc4a9d0503a60415634b5d0a Mon Sep 17 00:00:00 2001 From: Logan Kulinski Date: Sun, 25 Jan 2026 13:33:55 -0600 Subject: [PATCH 53/53] JavaDoc comments --- .../com/cta4j/bus/detour/model/Detour.java | 16 ++--- .../detour/model/DetourRouteDirection.java | 4 +- .../bus/locale/model/SupportedLocale.java | 12 ++++ .../internal/mapper/RoutePatternMapper.java | 4 +- .../cta4j/bus/pattern/model/PatternPoint.java | 22 ++++++ .../bus/pattern/model/PatternPointType.java | 10 +++ .../cta4j/bus/pattern/model/RoutePattern.java | 26 ++++++- .../bus/prediction/model/DynamicAction.java | 72 +++++++++++++++++++ 8 files changed, 151 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/cta4j/bus/detour/model/Detour.java b/src/main/java/com/cta4j/bus/detour/model/Detour.java index bba29b1..0a98b04 100644 --- a/src/main/java/com/cta4j/bus/detour/model/Detour.java +++ b/src/main/java/com/cta4j/bus/detour/model/Detour.java @@ -10,14 +10,14 @@ /** * Represents a service detour affecting one or more routes and directions within a specific time window. * - * @param id The unique ID of the detour - * @param version The version of the detour - * @param active Whether the detour is currently active - * @param description The human-readable description of the detour - * @param routeDirections The routes and directions affected by the detour - * @param startTime The time at which the detour begins - * @param endTime The time at which the detour ends - * @param dataFeed The identifier for the data feed that supplied the detour, or {@code null} if not available + * @param id the unique ID of the detour + * @param version the version of the detour + * @param active whether the detour is currently active + * @param description the human-readable description of the detour + * @param routeDirections the routes and directions affected by the detour + * @param startTime the time at which the detour begins + * @param endTime the time at which the detour ends + * @param dataFeed the identifier for the data feed that supplied the detour, or {@code null} if not available */ @NullMarked public record Detour( diff --git a/src/main/java/com/cta4j/bus/detour/model/DetourRouteDirection.java b/src/main/java/com/cta4j/bus/detour/model/DetourRouteDirection.java index d1c6db1..ef888e3 100644 --- a/src/main/java/com/cta4j/bus/detour/model/DetourRouteDirection.java +++ b/src/main/java/com/cta4j/bus/detour/model/DetourRouteDirection.java @@ -7,8 +7,8 @@ /** * Represents a route and direction affected by a detour. * - * @param routeId The route ID of the detour - * @param direction The direction of the detour + * @param routeId the route ID of the detour + * @param direction the direction of the detour */ @NullMarked public record DetourRouteDirection( diff --git a/src/main/java/com/cta4j/bus/locale/model/SupportedLocale.java b/src/main/java/com/cta4j/bus/locale/model/SupportedLocale.java index 47aaa3d..7a3f072 100644 --- a/src/main/java/com/cta4j/bus/locale/model/SupportedLocale.java +++ b/src/main/java/com/cta4j/bus/locale/model/SupportedLocale.java @@ -5,12 +5,24 @@ import java.util.Locale; import java.util.Objects; +/** + * Represents a locale supported by the CTA Bus API. + * + * @param locale the supported {@link Locale} + * @param displayName the human-readable name of the locale + */ @NullMarked public record SupportedLocale( Locale locale, String displayName ) { + /** + * Creates a {@code SupportedLocale}. + * + * @param locale the supported {@link Locale} + * @param displayName the human-readable name of the locale + */ public SupportedLocale { Objects.requireNonNull(locale); Objects.requireNonNull(displayName); diff --git a/src/main/java/com/cta4j/bus/pattern/internal/mapper/RoutePatternMapper.java b/src/main/java/com/cta4j/bus/pattern/internal/mapper/RoutePatternMapper.java index 3c566d0..0215f95 100644 --- a/src/main/java/com/cta4j/bus/pattern/internal/mapper/RoutePatternMapper.java +++ b/src/main/java/com/cta4j/bus/pattern/internal/mapper/RoutePatternMapper.java @@ -15,8 +15,8 @@ public interface RoutePatternMapper { RoutePatternMapper INSTANCE = Mappers.getMapper(RoutePatternMapper.class); - @Mapping(source = "pid", target = "patternId") - @Mapping(source = "ln", target = "patternCount") + @Mapping(source = "pid", target = "id") + @Mapping(source = "ln", target = "length") @Mapping(source = "rtdir", target = "direction") @Mapping(source = "pt", target = "points") @Mapping(source = "dtrid", target = "detourId") diff --git a/src/main/java/com/cta4j/bus/pattern/model/PatternPoint.java b/src/main/java/com/cta4j/bus/pattern/model/PatternPoint.java index 5c0ff52..f13a886 100644 --- a/src/main/java/com/cta4j/bus/pattern/model/PatternPoint.java +++ b/src/main/java/com/cta4j/bus/pattern/model/PatternPoint.java @@ -6,6 +6,17 @@ import java.math.BigDecimal; import java.util.Objects; +/** + * Represents a point in a bus route pattern. + * + * @param sequence the position of this point in the overall sequence of points + * @param type the type of pattern point + * @param stopId the identifier of the stop, if applicable + * @param stopName the name of the stop, if applicable + * @param distanceToPatternPoint the distance to the next pattern point, if applicable + * @param latitude the latitude coordinate of the pattern point + * @param longitude the longitude coordinate of the pattern point + */ @NullMarked public record PatternPoint( int sequence, @@ -25,6 +36,17 @@ public record PatternPoint( BigDecimal longitude ) { + /** + * Constructs a {@code PatternPoint}. + * + * @param sequence the position of this point in the overall sequence of points + * @param type the type of pattern point + * @param stopId the identifier of the stop, if applicable + * @param stopName the name of the stop, if applicable + * @param distanceToPatternPoint the distance to the next pattern point, if applicable + * @param latitude the latitude coordinate of the pattern point + * @param longitude the longitude coordinate of the pattern point + */ public PatternPoint { Objects.requireNonNull(type); Objects.requireNonNull(latitude); diff --git a/src/main/java/com/cta4j/bus/pattern/model/PatternPointType.java b/src/main/java/com/cta4j/bus/pattern/model/PatternPointType.java index f404ec6..17d504e 100644 --- a/src/main/java/com/cta4j/bus/pattern/model/PatternPointType.java +++ b/src/main/java/com/cta4j/bus/pattern/model/PatternPointType.java @@ -1,6 +1,16 @@ package com.cta4j.bus.pattern.model; +/** + * Represents the type of point within a route or pattern geometry. + */ public enum PatternPointType { + /** + * Indicates a stop along the route. + */ STOP, + + /** + * Indicates a waypoint along the route. + */ WAYPOINT } diff --git a/src/main/java/com/cta4j/bus/pattern/model/RoutePattern.java b/src/main/java/com/cta4j/bus/pattern/model/RoutePattern.java index 69c436c..daf9844 100644 --- a/src/main/java/com/cta4j/bus/pattern/model/RoutePattern.java +++ b/src/main/java/com/cta4j/bus/pattern/model/RoutePattern.java @@ -6,11 +6,21 @@ import java.util.List; import java.util.Objects; +/** + * Represents a bus route pattern. + * + * @param id the unique identifier of the route pattern + * @param length the length of the route pattern in feet + * @param direction the direction of the route pattern (e.g., "Northbound", "Southbound") + * @param points the list of pattern points that make up the route pattern + * @param detourId the identifier of the detour, if applicable + * @param detourPoints the list of pattern points for the detour, if applicable + */ @NullMarked public record RoutePattern( - String patternId, + String id, - int patternCount, + int length, String direction, @@ -22,8 +32,18 @@ public record RoutePattern( @Nullable List detourPoints ) { + /** + * Constructs a {@code RoutePattern}. + * + * @param id the unique identifier of the route pattern + * @param length the length of the route pattern in feet + * @param direction the direction of the route pattern (e.g., "Northbound", "Southbound") + * @param points the list of pattern points that make up the route pattern + * @param detourId the identifier of the detour, if applicable + * @param detourPoints the list of pattern points for the detour, if applicable + */ public RoutePattern { - Objects.requireNonNull(patternId); + Objects.requireNonNull(id); Objects.requireNonNull(direction); Objects.requireNonNull(points); diff --git a/src/main/java/com/cta4j/bus/prediction/model/DynamicAction.java b/src/main/java/com/cta4j/bus/prediction/model/DynamicAction.java index 705eaac..42640d1 100644 --- a/src/main/java/com/cta4j/bus/prediction/model/DynamicAction.java +++ b/src/main/java/com/cta4j/bus/prediction/model/DynamicAction.java @@ -1,38 +1,105 @@ package com.cta4j.bus.prediction.model; +/** + * Represents the various dynamic actions that can be applied to a bus trip. + */ public enum DynamicAction { + /** + * Indicates that no dynamic action has been applied. + */ NONE(0), + /** + * Indicates that the event or trip has been canceled. + */ CANCELLED(1), + /** + * Indicates that the event or trip will be handled by a different vehicle or operator. + */ REASSIGNED(2), + /** + * Indicates that the time of the event, or the entire trip, has been moved. + */ SHIFTED(3), + /** + * Indicates that the event is “drop-off only” and will not stop to pick up passengers. + */ EXPRESSED(4), + /** + * Indicates that the trip has events that are affected by Disruption Management changes, but the trip itself is + * not affected. + */ STOPS_AFFECTED(6), + /** + * Indicates that the trip was created dynamically and does not appear in the TA schedule. + */ NEW_TRIP(8), + /** + * Indicates one of the following: + *

    + *
  • + * The trip has been split, and this part of the split is using the original trip identifier(s). + *
  • + *
  • + * The trip has been short-turned leading to the removal of short-turned stops from the trip resulting in + * the trip being partial. + *
  • + *
+ */ PARTIAL_TRIP(9), + /** + * Indicates the trip has been split, and this part of the split has been assigned a new trip identifier(s). + */ PARTIAL_TRIP_NEW(10), + /** + * Indicates that the event or trip has been marked as canceled, but the cancellation should not be shown to the + * public. + */ DELAYED_CANCEL(12), + /** + * Indicates that event has been added to the trip. It was not originally scheduled. + */ ADDED_STOP(13), + /** + * Indicates that the trip has been affected by a delay. + */ UNKNOWN_DELAY(14), + /** + * Indicates that the trip, which was created dynamically, has been affected by a delay. + */ UNKNOWN_DELAY_NEW(15), + /** + * Indicates that the trip has been invalidated. Predictions for it should not be shown to the public. + */ INVALIDATED_TRIP(16), + /** + * Indicates that the trip, which was created dynamically, has been invalidated. Predictions for it should not be + * shown to the public. + */ INVALIDATED_TRIP_NEW(17), + /** + * Indicates that the trip, which was created dynamically, has been canceled. + */ CANCELLED_TRIP_NEW(18), + /** + * Indicates that the trip, which was created dynamically, has events that are affected by Disruption Management + * changes, but the trip itself is not affected. + */ STOPS_AFFECTED_NEW(19); private final int code; @@ -41,6 +108,11 @@ public enum DynamicAction { this.code = code; } + /** + * Gets the code associated with this dynamic action. + * + * @return the dynamic action code + */ public int getCode() { return this.code; }