diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..665ecfdf9 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,11 @@ +**Summary:** + +Summarize the changes in the pull request including how it relates to any issues (include the issue number, or link them). + +**Expected behavior:** + +Explain how you expect the pull request to work in your testing (in case other platforms/versions exhibit different behavior). + +Please make sure these boxes are checked before submitting your pull request - thanks! + +- [ ] Linked all relevant issues diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 000000000..a03049b4e --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,14 @@ +changelog: + categories: + - title: Breaking changes + labels: + - "Breaking change" + - title: Bugfixes + labels: + - bug + - title: Non-breaking changes + labels: + - "*" + - title: Dependency upgrades + labels: + - dependencies diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f516e09e6..2c9d90c5d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,36 +5,83 @@ on: pull_request: branches: [ master ] +env: + MAVEN_ARGS: "--no-transfer-progress -Dstyle.color=always" + jobs: - build: + build: runs-on: ubuntu-latest + strategy: + matrix: + locale: [ "en_US.utf8", "fr_FR.utf8" ] + + env: + LANG: ${{ matrix.locale }} + steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - - name: Set up JDK - uses: actions/setup-java@v1 - with: - java-version: 11 + - name: Set locale to ${{ matrix.locale }} + run: | + lang=`echo "${{ matrix.locale }}" | head -c 2` + sudo apt-get -qq install -y language-pack-${lang} + + echo "" + + # list installed locales + echo "Available locales" + locale -a + sudo locale-gen ${{ matrix.locale }} + sudo update-locale LANG=${{ matrix.locale }} + + - run: date - - name: Cache Maven dependencies - uses: actions/cache@v3 + - uses: actions/setup-java@v4 with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven- + distribution: 'temurin' + java-version: '17' + cache: 'maven' - name: Test project with Maven - run: mvn --no-transfer-progress test install + run: | + mvn $MAVEN_ARGS test package - - name: Build documentation - run: mvn --no-transfer-progress site + cli-integration-tests: + runs-on: ubuntu-latest - - name: Deploy documentation to Github Pages - # only deploy after merging to master - if: github.repository_owner == 'OneBusAway' && github.event_name == 'push' && github.ref == 'refs/heads/master' - uses: JamesIves/github-pages-deploy-action@v4 - with: - folder: target/site/ + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + cache: 'maven' + + - name: Run CLI integration tests + run: ./cli-tests/cli-tests.sh + + container-image: + if: github.repository_owner == 'onebusaway' && github.event_name == 'push' && github.ref == 'refs/heads/master' + runs-on: ubuntu-latest + + env: + CONTAINER_REGISTRY_NAMESPACE: docker.io/opentransitsoftwarefoundation + CONTAINER_REGISTRY_USER: onebusawaybot + CONTAINER_REGISTRY_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }} + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + cache: 'maven' + + - name: Build container image tarball + run: | + MAVEN_SKIP_ARGS="-Dmaven.test.skip=true -Dmaven.source.skip=true" + mvn $MAVEN_ARGS $MAVEN_SKIP_ARGS package jib:build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..39281602d --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,20 @@ +name: Create Github release + +on: + push: + tags: + - "v*.*.*" + +permissions: + contents: write + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Release + uses: softprops/action-gh-release@v2 + with: + generate_release_notes: true \ No newline at end of file diff --git a/.gitignore b/.gitignore index 77f573429..a99882886 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ junkyard /onebusaway-gtfs-modules.iml /pom.xml.versionsBackup *.iml +*.zip +*.jar diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7060531e5..000000000 --- a/.travis.yml +++ /dev/null @@ -1,6 +0,0 @@ -language: java -jdk: - - openjdk8 -cache: - directories: - - $HOME/.m2 diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..7cbf3f563 --- /dev/null +++ b/Makefile @@ -0,0 +1,4 @@ +release: + git checkout master + git pull + mvn release:clean release:prepare release:perform -Dgoals=deploy release:clean diff --git a/README.md b/README.md index 3bfe716cf..b1e9a4044 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,29 @@ -# onebusaway-gtfs-modules [![CI](https://github.com/OneBusAway/onebusaway-gtfs-modules/actions/workflows/ci.yml/badge.svg)](https://github.com/OneBusAway/onebusaway-gtfs-modules/actions/workflows/ci.yml) +# onebusaway-gtfs-modules + +[![CI](https://github.com/OneBusAway/onebusaway-gtfs-modules/actions/workflows/ci.yml/badge.svg)](https://github.com/OneBusAway/onebusaway-gtfs-modules/actions/workflows/ci.yml) +[![Maven Central](https://img.shields.io/maven-central/v/org.onebusaway/onebusaway-gtfs-modules.svg)](https://mvnrepository.com/artifact/org.onebusaway/onebusaway-gtfs-modules) A Java library for reading and writing [GTFS](https://developers.google.com/transit/gtfs) feeds, including database support. -See more documentation [on the wiki](https://github.com/OneBusAway/onebusaway-gtfs-modules/wiki). +See more documentation in the [`docs folder`](./docs). ## Maven usage In your `pom.xml`, include: -~~~ - - - public.onebusaway.org - https://repo.camsys-apps.com/releases/ - - -~~~ - -... and inside ``: +``` -~~~ - org.onebusaway - onebusaway-gtfs - 1.3.88 + org.onebusaway + onebusaway-gtfs + ${VERSION} -~~~ +``` ...where `` contains the latest version number. + +## Update on camsys-apps.com repo + +In August 2024 @leonardehrenfried took over maintainership and subsequently all artifacts are +now again published to Maven Central. Adding camsys-apps.com to your Maven repo configuration is no +longer necessary when you use version 1.4.18 or newer. \ No newline at end of file diff --git a/cli-tests/cli-tests.sh b/cli-tests/cli-tests.sh new file mode 100755 index 000000000..b3d56200d --- /dev/null +++ b/cli-tests/cli-tests.sh @@ -0,0 +1,28 @@ +#!/bin/bash +x + +mvn clean package --no-transfer-progress -DskipTests -Dmaven.source.skip=true -Dmaven.javadoc.skip=true + +EXAMPLE_1="example.gtfs.zip" +EXAMPLE_2="deathvalley.gtfs.zip" + +# transformer-cli + +TRANSFORMER_JAR="transformer-cli.jar" + +cp onebusaway-gtfs-transformer-cli/target/onebusaway-gtfs-transformer-cli.jar ./${TRANSFORMER_JAR} +wget https://github.com/google/transit/blob/master/gtfs/spec/en/examples/sample-feed-1.zip?raw=true -O ${EXAMPLE_1} + +java -jar ${TRANSFORMER_JAR} --help + +java -jar ${TRANSFORMER_JAR} --transform="{'op':'remove','match':{'file':'stops.txt','stop_id':'BEATTY_AIRPORT'}}" ${EXAMPLE_1} gtfs.transformed.zip + +# merge-cli + +MERGE_JAR="merge-cli.jar" + +cp onebusaway-gtfs-merge-cli/target/onebusaway-gtfs-merge-cli-*.jar ./${MERGE_JAR} +wget "http://data.trilliumtransit.com/gtfs/deathvalley-demo-ca-us/deathvalley-demo-ca-us.zip" -O ${EXAMPLE_2} + +java -jar ${MERGE_JAR} --help + +java -jar ${MERGE_JAR} ${EXAMPLE_1} ${EXAMPLE_2} gtfs.merged.zip diff --git a/src/site/apt/index.apt.vm b/docs/index.md similarity index 65% rename from src/site/apt/index.apt.vm rename to docs/index.md index f8445a862..07dac4987 100644 --- a/src/site/apt/index.apt.vm +++ b/docs/index.md @@ -1,36 +1,28 @@ -onebusaway-gtfs-modules +# onebusaway-gtfs-modules - We provide a Java library for reading and writing {{{https://developers.google.com/transit/gtfs} GTFS}} feeds, including database support. - - <> ${currentVersion} - - Details on all releases can be found in the {{{./release-notes.html}Release Notes}}. + We provide a Java library for reading and writing [ GTFS](https://developers.google.com/transit/gtfs) feeds, including database support. The library is broken up into a few key modules: - * <<>> - The core library for reading and writing GTFS + * `onebusaway-gtfs` - The core library for reading and writing GTFS - * <<>> - Support for {{{http://www.hibernate.org/}Hibernate}} database persistence of GTFS data + * `onebusaway-gtfs-hibernate` - Support for [Hibernate](http://www.hibernate.org/) database persistence of GTFS data - * <<>> - Command-line utilty for loading GTFS feeds into a database - see {{{./onebusaway-gtfs-hibernate-cli.html}the full documentation}}. + * `onebusaway-gtfs-hibernate-cli` - Command-line utilty for loading GTFS feeds into a database - see [the full documentation](./onebusaway-gtfs-hibernate-cli.md). - * <<>> - Tools for transforming GTFS data + * `onebusaway-gtfs-transformer` - Tools for transforming GTFS data - * <<>> - Command-line utility for transforming GTFS - see {{{./onebusaway-gtfs-transformer-cli.html}the full documentation}}. + * `onebusaway-gtfs-transformer-cli` - Command-line utility for transforming GTFS - see [the full documentation](./onebusaway-gtfs-transformer-cli.md). - * <<>> - Tools for merging GTFS data + * `onebusaway-gtfs-merge` - Tools for merging GTFS data - * <<>> - Command-line utility for merging GTFS feeds - see {{{./onebusaway-gtfs-merge-cli.html}the full documentation}}. - -Documentation - - You can access the {{{./apidocs/index.html}latest Javadoc for the library}}. Also, see example source code below. + * `onebusaway-gtfs-merge-cli` - Command-line utility for merging GTFS feeds - see [the full documentation](./onebusaway-gtfs-merge-cli.md). -Using in Maven +## Using in Maven The library is available as a Maven module. Simply add the following dependencies: -+---+ +``` @@ -51,19 +43,30 @@ Using in Maven ${currentVersion} -+---+ +``` + +## Docker images + +There are automatically generated docker images available at https://registry.hub.docker.com/u/opentransitsoftwarefoundation. +Contributions to image-specific documentation are welcome. + +### `onebusaway-gtfs-transformer-cli` + +See [the full documentation](./onebusaway-gtfs-transformer-cli.md) for more configuration options. -#if( $currentVersion.contains('SNAPSHOT') ) - To use a SNAPSHOT version of the library, you'll need to {{{https://github.com/OneBusAway/onebusaway/wiki/Maven-Repository}add a reference to the OneBusAway Maven repository}}. -#end +For example, assuming that all the following files are in the `/path/to/local/data/directory` directory, to run the `remove-matching-route.rule` rule against `gtfs-data.zip` to generate `gtfs-data-out.zip` you can use: +```bash +docker run -v /path/to/local/data/directory:/data --rm opentransitsoftwarefoundation/onebusaway-gtfs-transformer-cli:4.4.0 --transform=/data/remove-matching-route.rule /data/gtfs-data.zip /data/gtfs-data-out.zip +``` +The `gtfs-data-out.zip` file will be in the `/path/to/local/data/directory` directory. -Example Code +## Example Code * Basic Reading Let's introduce basic code for reading a GTFS feed and handling the resulting entities: -+---+ +``` public class GtfsReaderExampleMain { public static void main(String[] args) throws IOException { @@ -107,18 +110,18 @@ public class GtfsReaderExampleMain { } } } -+---+ +``` - Notice that the {{{./apidocs/org/onebusaway/gtfs/serialization/GtfsReader.html}GtfsReader}} does the bulk of the work of reading the GTFS feed. The general pattern is to create the reader, set the input file, and call `run()` to start the reading process. You can manage the resulting GTFS entities in a couple of ways: + Notice that the [GtfsReader](./apidocs/org/onebusaway/gtfs/serialization/GtfsReader.html) does the bulk of the work of reading the GTFS feed. The general pattern is to create the reader, set the input file, and call `run()` to start the reading process. You can manage the resulting GTFS entities in a couple of ways: - * Register an {{{../../onebusaway-csv-entities/${onebusaway_csv_entities_version}/apidocs/org/onebusaway/csv_entities/EntityHandler.html}EntityHandler}} to handle entities as they are read + * Register an [EntityHandler](../../onebusaway-csv-entities/${onebusaway_csv_entities_version}/apidocs/org/onebusaway/csv_entities/EntityHandler.html) to handle entities as they are read - * Use an instance of {{{./apidocs/org/onebusaway/gtfs/services/GenericMutableDao.html}GenericMutableDao}} to examine the loaded entities after reading is complete + * Use an instance of [GenericMutableDao](./apidocs/org/onebusaway/gtfs/services/GenericMutableDao.html) to examine the loaded entities after reading is complete * Basic Writing -+---+ +``` public class GtfsWriterExampleMain { public static void main(String[] args) throws IOException { @@ -146,17 +149,17 @@ public class GtfsWriterExampleMain { writer.close(); } } -+---+ +``` * Basic Database Reading - The class <<>> in the -<<>> directory includes basic code for reading + The class `org.onebusaway.gtfs.examples.GtfsHibernateReaderExampleMain` in the +`onebusaway-gtfs-hibernate/src/test/java` directory includes basic code for reading a GTFS feed into a database and querying the resulting entities. The sample code has been summarized for length and clarity: -+---+ +``` public class GtfsHibernateReaderExampleMain { public static void main(String[] args) throws IOException { @@ -200,10 +203,10 @@ public class GtfsHibernateReaderExampleMain { return new HibernateGtfsFactory(sessionFactory); } } -+---+ +``` This code is roughly similar to the example reader code for -<<>>, with the main difference being the use of <<>>, which is a convenience +`onebusaway-gtfs`, with the main difference being the use of `HibernateGtfsFactory`, which is a convenience factory for creating database-aware DAOs. @@ -214,13 +217,13 @@ it to use a different database and you totally can. See {{http://hibernate.org/ Hibernate, but also check out the default hibernate config file used in the example above. It's located in the following directory: -+---+ +``` onebusaway-gtfs-hibernate/src/test/resources/org/onebusaway/gtfs/examples/hibernate-configuration-examples.xml -+---+ +``` The contents look like: -+---+ +``` @@ -236,25 +239,25 @@ onebusaway-gtfs-hibernate/src/test/resources/org/onebusaway/gtfs/examples/hibern -+---+ +``` Here you can configure the data source used for the database connection along with the Hibernate dialect. * Reading Custom Fields - Does your GTFS feed have custom fields not defined by the core <<>> library? It's possible + Does your GTFS feed have custom fields not defined by the core `onebusaway-gtfs` library? It's possible to read and write this data without modify OBA source code using the "extensions" mechanism. Consider a -<<>> file with a custom <<>> field: +`stops.txt` file with a custom `extra_stop_info` field: -+---+ +``` stop_id,stop_name,stop_lat,stop_lon,extra_stop_info 123,Some Station,47.0,-122.0,This is a cool transit station -+---+ +``` - The <<>> field isn't included in the the {{{./apidocs/org/onebusaway/gtfs/model/Stop.html}Stop}} data -model by default. So instead, we define a special <<>> Java bean type with the new field: + The `extra_stop_info` field isn't included in the the [Stop](./apidocs/org/onebusaway/gtfs/model/Stop.html) data +model by default. So instead, we define a special `StopExtension` Java bean type with the new field: -+---+ +``` public static class StopExtension { @CsvField(optional = true) private String extraStopInfo; @@ -262,28 +265,28 @@ public static class StopExtension { public String getExtraStopInfo() { ... } public void setExtraStopInfo(String info) { ... } } -+---+ +``` We can now register our class as an extension of the default stop data model: -+---+ +``` DefaultEntitySchemaFactory factory = GtfsEntitySchemaFactory.createEntitySchemaFactory(); factory.addExtension(Stop.class, StopExtension.class); GtfsReader reader = new GtfsReader(); reader.setEntitySchemaFactory(factory); -+---+ +``` - Now when you read your GTFS feed with the <<>> instance, <<>> objects + Now when you read your GTFS feed with the `GtfsReader` instance, `StopExtension` objects will automatically be created, populated, and attached to stops as they are read: -+---+ +``` Stop stop = dao.getStopForId(...); StopExtension extension = stop.getExtension(StopExtension.class); System.out.println(extension.getExtraStopInfo()); -+---+ +``` For more information on defining the mapping from GTFS fields to Java beans, see documentation for -the {{{https://github.com/OneBusAway/onebusaway-csv-entities}onebusaway-csv-entities}} project, -including the {{{../../onebusaway-csv-entities/${onebusaway_csv_entities_version}/apidocs/org/onebusaway/csv_entities/schema/annotations/CsvField.html}@CsvField}} +the [onebusaway-csv-entities](https://github.com/OneBusAway/onebusaway-csv-entities) project, +including the [@CsvField](../../onebusaway-csv-entities/${onebusaway_csv_entities_version}/apidocs/org/onebusaway/csv_entities/schema/annotations/CsvField.html) annotation documentation. \ No newline at end of file diff --git a/src/site/apt/onebusaway-gtfs-hibernate-cli.apt.vm b/docs/onebusaway-gtfs-hibernate-cli.md similarity index 68% rename from src/site/apt/onebusaway-gtfs-hibernate-cli.apt.vm rename to docs/onebusaway-gtfs-hibernate-cli.md index 9e7af5bcc..4cf866f4f 100644 --- a/src/site/apt/onebusaway-gtfs-hibernate-cli.apt.vm +++ b/docs/onebusaway-gtfs-hibernate-cli.md @@ -6,8 +6,8 @@ GTFS Hibernate Command-Line Utility Introduction - The <<>> command-line utility is a simple command-line tool for loading a -{{{https://developers.google.com/transit/gtfs}GTFS}} feed into a database. + The `onebusaway-gtfs-hibernate-cli` command-line utility is a simple command-line tool for loading a +[GTFS](https://developers.google.com/transit/gtfs) feed into a database. Getting the Application @@ -21,13 +21,13 @@ Getting the Application #set( $url = 'https://repo.camsys-apps.com/' + $repository + '/org/onebusaway/onebusaway-gtfs-hibernate-cli/' + ${currentVersion} + '/onebusaway-gtfs-hibernate-cli-' + ${currentVersion} + '.jar' ) - {{{${url}}onebusaway-gtfs-hibernate-cli-${currentVersion}.jar}} + [.jar](${url}}onebusaway-gtfs-hibernate-cli-${currentVersion) Using the Application You'll need a Java 11 runtime installed to run the client. To run the application: -+---+ +``` java -classpath onebusaway-gtfs-hiberante-cli.jar:your-database-jdbc.jar \ org.onebusaway.gtfs.GtfsDatabaseLoaderMain \ --driverClass=... \ @@ -35,7 +35,7 @@ java -classpath onebusaway-gtfs-hiberante-cli.jar:your-database-jdbc.jar \ --username=... \ --password=... \ gtfs_path -+---+ +``` Note that the utility doesn't include any JDBC client jars for any databases by default. You will need to download an appropriate JDBC client for your database and include it on the classpath when running @@ -44,13 +44,13 @@ using the command-line arguments specified below. * Arguments - * <<<--driverClass=...>>> : JDBC driver class for your JDBC provider (eg. "org.hsqldb.jdbcDriver") + * `--driverClass=...` : JDBC driver class for your JDBC provider (eg. "org.hsqldb.jdbcDriver") - * <<<--url=...>>> : JDBC connection url for your database (eg. "jdbc:hsqldb:mem:temp_db") + * `--url=...` : JDBC connection url for your database (eg. "jdbc:hsqldb:mem:temp_db") - * <<<--username=...>>> : JDBC connection username + * `--username=...` : JDBC connection username - * <<<--password=...>>> : JDBC connection password + * `--password=...` : JDBC connection password [] diff --git a/docs/onebusaway-gtfs-merge-cli.md b/docs/onebusaway-gtfs-merge-cli.md new file mode 100644 index 000000000..5c8fbb3c2 --- /dev/null +++ b/docs/onebusaway-gtfs-merge-cli.md @@ -0,0 +1,112 @@ +# GTFS Merge Command-Line Application + +## Introduction + +The `onebusaway-gtfs-merge-cli` command-line application is a simple command-line tool for merging +[GTFS](https://developers.google.com/transit/gtfs) feeds. + +## Getting the Application + +You can download the application from Maven Central. + +Go to https://repo1.maven.org/maven2/org/onebusaway/onebusaway-gtfs-merge-cli/, select the version +you want and get the URL for the largest jar file. An example would be +https://repo1.maven.org/maven2/org/onebusaway/onebusaway-gtfs-merge-cli/3.2.2/onebusaway-gtfs-merge-cli-3.2.2.jar + +## Using the Application + +You'll need a Java 17 runtime installed to run the client. + +To run the application: + +``` +java -jar onebusaway-gtfs-merge-cli.jar [--args] input_gtfs_path_a input_gtfs_path_b ... output_gtfs_path +``` + +**Note**: Merging large GTFS feeds is often processor and memory intensive. You'll likely need to increase the +max amount of memory allocated to Java with an option like `-Xmx1G` (adjust the limit as needed). I also recommend +adding the `-server` argument if you are running the Oracle or OpenJDK, as it can really increase performance. + +## Configuring the Application + +The merge application supports a number of options and arguments for configuring the application's behavior. The +general pattern is to specify options for each type of file in a GTFS feed using the `--file` option, specifying +specific options for each file type after the `--file` option. Here's a quick example: + +``` +--file=routes.txt --duplicateDetection=identity --file=calendar.txt --logDroppedDuplicates ... +``` + + The merge application supports merging the following files: + + - `agency.txt` + - `stops.txt` + - `routes.txt` + - `trips.txt` and `stop_times.txt` + - `calendar.txt` and `calendar_dates.txt` + - `shapes.txt` + - `fare_attributes.txt` + - `fare_rules.txt` + - `frequencies.txt` + - `transfers.txt` + +You can specify merge options for each of these files using the `--file=gtfs_file.txt` option. File types listed +together (eg. `trips.txt>> and `stop_times.txt`) are handled by the same merge strategy, so specifying options for +either will have the same effect. For details on options you might specify, read on. + +## Handling Duplicates + +The main issue to considering when merging GTFS feeds is the handling of duplicate entries between the two feeds, +including how to identify duplicates and what to do with duplicates when they are found. + +### Identifying Duplicates + +We support a couple of methods for determining when entries from two different feeds are actually duplicates. By default, +the merge tool will attempt to automatically determine the best merge strategy to use. You can also control the specific +strategy used on a per-file basis using the `--duplicateDetection` argument. You can specify any of the following +strategies for duplicate detection. + + - `--duplicateDetection=identity` - If two entries have the same id (eg. stop id, route id, trip id), then they are + considered the same. This is the more strict matching policy. + + - `--duplicateDetection=fuzzy` - If two entries have common elements (eg. stop name or location, route short name, + trip stop sequence), then they are considered the same. This is the more lenient matching policy, and is highly + dependent on the type of GTFS entry being matched. + + - `--duplicateDetection=none` - Entries between two feeds are never considered to be duplicates, even if they have + the same id or similar properties. + +### Logging Duplicates + +Sometimes your feed might have unexpected duplicates. You can tell the merge tool to log duplicates it finds or even +immediately exit with the following arguments: + + - `--logDroppedDuplicates` - log a message when a duplicate is found + + - `--errorOnDroppedDuplicates` - throw an exception when a duplicate is found, stopping the program + +## Examples + +### Handling a Service Change + +Agencies often schedule major changes to their system around a particular date, with one GTFS feed for before the +service change and a different GTFS feed for after. We'd like to be able to merge these disjoint feeds into one +feed with continuous coverage. + +In our example, an agency produces two feeds where the entries in `agency.txt` and `stops.txt` are exactly +the same, so the default policy of identifying and dropping duplicates will work fine there. The `routes.txt` file +is a bit trickier, since the route ids are different between the two feeds but the entries are largely the same. We +will use fuzzy duplicate detection to match the routes between the two feeds. + +The next issue is the `calendar.txt` file. The agency uses the same `service_id` values in both feeds +(eg. `WEEK`, `SAT`, `SUN`) with different start and end dates in the two feeds. If the default policy of +dropping duplicate entries was used, we'd lose the dates in one of the service periods. Instead, we rename duplicates +such that the service ids from the second feed will be renamed to `b-WEEK`, `b-SAT`, etc. and all +`trips.txt` entries in the second feed will be updated appropriately. The result is that trips from the first +and second feed will both have the proper calendar entries in the merged feed. + +Putting it all together, here is what the command-line options for the application would look like: + +``` +--file=routes.txt --fuzzyDuplicates --file=calendar.txt --renameDuplicates +``` \ No newline at end of file diff --git a/docs/onebusaway-gtfs-transformer-cli-sample1.png b/docs/onebusaway-gtfs-transformer-cli-sample1.png new file mode 100644 index 000000000..eec780758 Binary files /dev/null and b/docs/onebusaway-gtfs-transformer-cli-sample1.png differ diff --git a/docs/onebusaway-gtfs-transformer-cli.md b/docs/onebusaway-gtfs-transformer-cli.md new file mode 100644 index 000000000..7b357e7b7 --- /dev/null +++ b/docs/onebusaway-gtfs-transformer-cli.md @@ -0,0 +1,639 @@ +# GTFS Transformation Command-Line Application + + +* [GTFS Transformation Command-Line Application](#gtfs-transformation-command-line-application) + * [Introduction](#introduction) + * [Requirements](#requirements) + * [Getting the Application](#getting-the-application) + * [Using the Application](#using-the-application) + * [Arguments](#arguments) + * [Transform Syntax](#transform-syntax) + * [Matching](#matching-) + * [Regular Expressions](#regular-expressions) + * [Compound Property Expressions](#compound-property-expressions) + * [Multi-Value Matches](#multi-value-matches) + * [Collection-Like Entities](#collection-like-entities) + * [Types of Transforms](#types-of-transforms) + * [Add an Entity](#add-an-entity) + * [Update an Entity](#update-an-entity) + * [Find/Replace](#findreplace) + * [Path Expressions](#path-expressions-) + * [Retain an Entity](#retain-an-entity) + * [Remove an Entity](#remove-an-entity) + * [Retain Up From Polygon](#retain-up-from-polygon) + * [Trim Trip From Polygon](#trim-trip-from-polygon) + * [Trim a Trip](#trim-a-trip) + * [Generate Stop Times](#generate-stop-times) + * [Extend Service Calendars](#extend-service-calendars) + * [Remove Old Calendar Statements](#remove-old-calendar-statements) + * [Deduplicate Calendar Entries](#deduplicate-calendar-entries) + * [Truncate New Calendar Statements](#truncate-new-calendar-statements) + * [Merge Trips and Simplify Calendar Entries](#merge-trips-and-simplify-calendar-entries) + * [Shift Negative Stop Times](#shift-negative-stop-times) + * [Arbitrary Transform](#arbitrary-transform) + * [How to Reduce your GTFS](#how-to-reduce-your-gtfs) + * [Clip National GTFS for Regional Integration and Consistency](#clip-national-gtfs-for-regional-integration-and-consistency) + + +## Introduction + +The `onebusaway-gtfs-transformer-cli` command-line application is a simple command-line tool for transforming +[GTFS](https://developers.google.com/transit/gtfs) feeds. + +### Requirements + + * Java 17 or greater + +### Getting the Application + +You can download the application from Maven Central: https://repo1.maven.org/maven2/org/onebusaway/onebusaway-gtfs-transformer-cli/ + +Select the largest jar file from the version you would like to use, for example https://repo1.maven.org/maven2/org/onebusaway/onebusaway-gtfs-transformer-cli/2.0.0/onebusaway-gtfs-transformer-cli-2.0.0.jar + +### Using the Application + +To run the application: + +``` +java -jar onebusaway-gtfs-transformer-cli.jar [-args] input_gtfs_path ... output_gtfs_path +``` + +`input_gtfs_path` and `output_gtfs_path` can be either a directory containing a GTFS feed or a .zip file. + +_Note_: Transforming large GTFS feeds is processor and memory intensive. You'll likely need to increase the +max amount of memory allocated to Java with an option like `-Xmx1G` or greater. Adding the `-server` argument +if you are running the Oracle or OpenJDK can also increase performance. + +### Arguments + + * `--transform=...` : specify a transformation to apply to the input GTFS feed (see syntax below) + * `--agencyId=id` : specify a default agency id for the input GTFS feed + * `--overwriteDuplicates` : specify that duplicate GTFS entities should overwrite each other when read + + +### Transform Syntax + +Transforms are specified as snippets of example. A simple example to remove a stop might look like: + +``` +{"op":"remove","match":{"file":"stops.txt","stop_name":"Stop NameP"}} +``` + +You can pass those snippets to the application in a couple of ways. The simplest is directly on the command line. + +``` +--transform='{...}' +``` + +You can have multiple `--transform` arguments to specify multiple transformations. However, if you have a LOT of +transformations that you wish to apply, it can be easier to put them in a file, with a JSON snippet per line. Then +specify the file on the command-line: + +``` + --transform=path/to/local-file +``` + +You can even specify a URL where the transformations will be read: + +``` +--transform=http://server/path +``` + +### Matching + +We provide a number of configurable transformations out-of-the-box that can do simple operations like adding, +updating, retaining, and removing GTFS entities. Many of the transforms accept a "`match`" term that controls how the +rule matches against entities: + +``` +{"op":..., "match":{"file":"routes.txt", "route_short_name":"574"}} +``` + +Here, the match snippet at minimum requires a `file` property that specifies the type of GTFS entity to match. +Any file name defined in the [GTFS specification](https://developers.google.com/transit/gtfs/reference#FeedFiles) +can be used. + +You can specify additional properties and values to match against as needed. Again, use the field names defined for +each file name in the GTFS specification. For example, the snippet above will match any entry in `routes.txt` with a +`route_short_name` value of `574`. + +### Regular Expressions + + Property matching also supports regular expressions that allow you to match property values conforming to a regexp pattern. For example, the snippet below will match any entry in `stops.txt` with a `stop_id` starting with `de:08`. + +``` +{"op":..., "match":{"file":"stops.txt", "stop_id":"m/^de:08.*/"}} +``` + +### Compound Property Expressions + + Property matching also supports compound property expressions that allow you to match across GTFS relational +references. Let's look at a simple example: + +``` +{"op":..., "match":{"file":"trips.txt", "route.route_short_name":"10"}} +``` + +Here the special `routes` property references the route entry associated with each trip, allowing you to match +the properties of the route. You can even chain references, like `route.agency` to match against the agency +associated with the trip. Here is the full list of supported compound property references: + +``` +{"op":..., "match":{"file":"routes.txt", "agency.name":"Metro"}} +{"op":..., "match":{"file":"trips.txt", "route.route_short_name":"10"}} +{"op":..., "match":{"file":"stop_times.txt", "stop.stop_id":"153"}} +{"op":..., "match":{"file":"stop_times.txt", "trip.route.route_type":3}} +{"op":..., "match":{"file":"frequencies.txt", "trip.service_id":"WEEKDAY"}} +{"op":..., "match":{"file":"transfers.txt", "fromStop.stop_id":"173"}} +{"op":..., "match":{"file":"transfers.txt", "toStop.stop_id":"173"}} +{"op":..., "match":{"file":"fare_rules.txt", "fare.currencyType":"USD"}} +{"op":..., "match":{"file":"fare_rules.txt", "route.route_short_name":"10"}} +``` + +### Multi-Value Matches + + The compound property expressions shown above are all for 1-to-1 relations, but matching also supports a limited +form of multi-value matching for 1-to-N relations. Let's look at a simple example: + +``` +{"op":..., "match":{"file":"routes.txt", "any(trips.trip_headsign)":"Downtown"}} +``` + +Notice the addition of `any(...)` around the property name. Here we are using a special `trips` property that +expands to include all trips associated with each route. Now, if *any* trip belonging to the route has the specified +`trip_headsign` value, then the route matches. Here is the full list of supported multi-value property matches: + +``` +{"op":..., "match":{"file":"agency.txt", "any(routes.X)":"Y"}} +{"op":..., "match":{"file":"routes.txt", "any(trips.X)":"Y"}} +{"op":..., "match":{"file":"trips.txt", "any(stop_times.X)":"Y"}} +``` + +### Collection-Like Entities + +There are a number of GTFS entites that are more effectively collections identified by a common key. For example, +shape points in `shapes.txt` linked by a common `shape_id` value or `calendar.txt` and `calendar_dates.txt` entries +linked by a common `service_id` value. You can use a special `collection `match clause to match against the entire +collection. + +``` +{"op":..., "match":{"collection":"shape", "shape_id":"XYZ"}} +{"op":..., "match":{"collection":"calendar", "service_id":"XYZ"}} +``` + +You can use the calendar collection matches, for example, to retain a calendar, including all `calendar.txt`, +`calendar_dates.txt`, and `trip.txt` entries that reference the specified `service_id` value. This convenient +short-hand is easier than writing the equivalent expression using references to the three file types separately. + +### Types of Transforms + +#### Add an Entity + +Create and add a new entity to the feed. + +``` +{"op":"add","obj":{"file":"agency.txt", "agency_id":"ST", "agency_name":"Sound Transit", +"agency_url":"http://www.soundtransit.org", "agency_timezone":"America/Los_Angeles"}} +``` + +#### Update an Entity + +You can update arbitrary fields of a GTFS entity. + +``` +{"op":"update", "match":{"file":"routes.txt", "route_short_name":"574"}, "update":{"agency_id":"ST"}} +``` + +Normally, update values are used as-is. However, we support a number of +special update operations: + +#### Find/Replace + +``` +{"op":"update", "match":{"file":"trips.txt"}, "update":{"trip_short_name":"s/North/N/"}} +``` + +By using `s/.../.../` syntax in the update value, the upda```te will perform +a find-replace operation on the specified property value. Consider the +following example: + +``` +{"op":"update", "match":{"file":"trips.txt"}, "update":{"trip_short_name":"s/North/N/"}} +``` + +Here, a trip with a headsign of `North Seattle` will be updated to `N Seattle`. + +#### Path Expressions + +By using `path(...)` syntax in the update value, the expression will be +treated as a compound Java bean properties path expression. This path +expression will be evaluated against the target entity to produce the update +value. Consider the following example: + +``` +{"op":"update", "match":{"file":"trips.txt"}, "update":{"trip_short_name":"path(route.longName)"}} +``` + +Here, the `trip_short_name` field is updated for each trip in the feed. +The value will be copied from the `route_long_name` field of each trip's +associated route. + +#### Retain an Entity + +We also provide a powerful mechanism for selecting just a sub-set of a feed. +You can apply retain operations to entities you wish to keep and all the supporting entities referenced +by the retained entity will be retained as well. Unreferenced entities will be pruned. + +In the following example, only route B15 will be retained, along with all the stops, trips, stop times, shapes, and agencies linked to directly by that route. + +``` +{"op":"retain", "match":{"file":"routes.txt", "route_short_name":"B15"}} +``` + +By default, we retain across [block_id](https://developers.google.com/transit/gtfs/reference#trips_block_id_field) values +specified in trips.txt. That means if a particular trip is retained (perhaps because its parent route is retained), +and the trip specifies a block_id, then all the trips referencing that block_id will be retained as well, along with +their own routes, stop times, and shapes. This can potentially lead to unexpected results if you retain one route and +suddenly see other routes included because they are linked by block_id. + +You can disable this feature by specifying `retainBlocks: false` in the JSON transformer snippet. Here is an +example: + +``` +{"op":"retain","match":{"file":"routes.txt", "route_short_name":"B15"}, "retainBlocks":false} +``` + +#### Remove an Entity + +You can remove a specific entity from a feed. + +``` +{"op":"remove", "match":{"file":"stops.txt", "stop_name":"Stop Name"}} +``` + +Note that removing an entity has a cascading effect. If you remove a trip, all the stop times that depend on that +trip will also be removed. If you remove a route, all the trips and stop times for that route will be removed. + +#### Retain Up From Polygon + +Retain Up From Polygon is an operation that filters GTFS input data based on a specified geographic area, using a polygon defined in WKT (Well-Known Text) format, which is configurable in the JSON transformer snippet. + +This strategy applies two main functions: + + * **Retain Function**: retains **up** all stops, trips, and routes that are located inside the defined polygon. + + The algorithm starts by applying retain up to each entity, traversing the entity dependency tree. Starting from the stop, retain up is applied to the stop_times referencing this stop, then to the trips, and so on. + + Once the base of the entity tree is reached, it automatically applies retain **down** to all the traversed entities. Therefore, all the trips of the route and then all the stop_times of each trip will be tagged as **retain**. + + * **Remove Function**: any entities not retained within the polygon are removed. + +This strategy ensures that the GTFS output retains only the entities directly or indirectly linked to the geographical area concerned. + +**Parameters**: + + * **polygon**: a required argument, which accepts the polygon in WKT format using the WGS84 coordinate system (SRID: 4326). This polygon defines the area of interest for filtering. + +``` +{"op":"transform","class":"org.onebusaway.gtfs_transformer.impl.RetainUpFromPolygon","polygon":"POLYGON ((-123.0 37.0, -123.0 38.0, -122.0 38.0, -122.0 37.0, -123.0 37.0))"} +``` + +#### Trim Trip From Polygon + +The Trim Trip From Polygon strategy refines GTFS data by removing all stop_times associated with stops located outside a specified geographical area. The area is defined using a configurable WKT Polygon or Multipolygon in the JSON transformer snippet. + +This removal of stop_times is achieved by invoking the **TrimTrip operation**, ensuring that only stops within the defined polygon are retained. + +Only valid stop_times within the polygon are retained, maintaining the integrity of the trips. + +**Parameters**: + + * **polygon**: a required argument, which accepts the polygon in WKT format using the WGS84 coordinate system (SRID: 4326). This polygon defines the area of interest for filtering. + +``` +{"op":"transform","class":"org.onebusaway.gtfs_transformer.impl.TrimTripFromPolygon","polygon":"POLYGON ((-123.0 37.0, -123.0 38.0, -122.0 38.0, -122.0 37.0, -123.0 37.0))"} +``` + +#### Trim a Trip + +You can remove stop times from the beginning or end of a trip using the "trim_trip" operation. Example: + +``` +{"op":"trim_trip", "match":{"file":"trips.txt", "route_id":"R10"}, "from_stop_id":"138S"} +``` + +For any trip belonging to the specified route and passing through the specified stop, all stop times from the specified +stop onward will be removed from the trip. You can also remove stop times from the beginning of the trip as well: + +``` +{"op":"trim_trip", "match":{"file":"trips.txt", "route_id":"R10"}, "to_stop_id":"138S"} +``` + +Or both: + +``` +{"op":"trim_trip", "match":{"file":"trips.txt", "route_id":"R10"}, "to_stop_id":"125S", "from_stop_id":"138S"} +``` + +#### Generate Stop Times + +You can generate stop time entries for a trip. Example: + +``` +{"op":"stop_times_factory", "trip_id":"TRIP01", "start_time":"06:00:00", "end_time":"06:20:00", "stop_ids":["S01", "S02", "S03"]} +``` + +A series of entries in `stop_times.txt` will be generated for the specified trip, traveling along the specified sequence of +stops. The departure time for the first stop will be set from the `start_time` field, the arrival time for the last stop will +be set from the `end_time` field, and the times for intermediate stops will be interpolated based on their distance along the +trip. + +#### Extend Service Calendars + +Sometimes you need to extend the service dates in a GTFS feed, perhaps in order to temporarily extend an expired feed. Extending +the feed by hand can be a tedious task, especially when the feed uses a complex combination of `calendar.txt` and `calendar_dates.txt` +entries. Fortunately, the GTFS tranformer tool supports a `calendar_extension` operation that can help simplify the work. Example: + +``` +{"op":"calendar_extension", "end_date":"20130331"} +``` + +The operation requires just one argument by default: `end_date` to specify the new end-date for the feed. The operation does +its best to intelligently extend each service calendar, as identified by a `service_id` in `calendar.txt` or `calendar_dates.txt`. +There are a few wrinkles to be aware of, however. + +Extending a `calendar.txt` entry is usually just a matter of setting a new `end_date` value in the feed. Extending a service +calendar represented only through `calendar_dates.txt` entries is a bit more complex. For such a service calendar, we attempt to +determine which days of the week are typically active for the calendar and extend only those. For example, is the calendar is +always active on Saturday but has one or two Sunday entries, we will only add entries for Saturday when extending the calendar. + +Also note that we will not extend "inactive" service calendars. A service calendar is considered inactive if its last active +service date is already in the past. By default, any calendar that's been expired for more than two weeks is considered inactive. +This helps handle feeds that have merged two service periods in one feed. For example, one calendar active from June 1 - July 31 +and a second calendar active from August 1 to September 31. If it's the last week of September and you are extending the feed, +you typically only mean to extend the second service calendar. You can control this inactive calendar cutoff with an optional +argument: + +``` +{"op":"calendar_extension", "end_date":"20130331", "inactive_calendar_cutoff":"20121031"} +``` + +Calendars that have expired before the specified date will be considered inactive and won't be extended. + +_Note_: We don't make any effort to extend canceled service dates, as specified in `calendar_dates.txt` for holidays and +other special events. It's too tricky to automatically determine how they should be handled. You may need to still handle +those manually. + +#### Remove Old Calendar Statements + +RemoveOldCalendarStatements is an operation designed to remove calendar and calendar dates entries that are no longer valid on today's date. + +By default, it deletes entries from both the calendar.txt and calendar_dates.txt files, where the end_date in calendar.txt or the date field in calendar_dates.txt has passed. + +With the remove_today attribute added to the JSON transformer snippet, users can control whether entries in calendar or calendar_dates that are valid for today are included or excluded in the GTFS output. + + * If remove_today is set to true, the transformer will remove entries for the current date. + +``` + {"op":"transform", "class":"org.onebusaway.gtfs_transformer.impl.RemoveOldCalendarStatements", "remove_today":true} +``` + + * If remove_today is set to false or not specified, the transformer will retain the calendar entries for the current date. + +``` +{"op":"transform", "class":"org.onebusaway.gtfs_transformer.impl.RemoveOldCalendarStatements", "remove_today":false} +``` + +Additionally, after truncating the calendar entries, it is recommended to use a **retain operation** to ensure that only trips with valid calendar dates are retained. + +Without this retain operation, the `trips.txt` file will contain trips with non-existent calendar dates, leading to invalid data. + +``` +{"op":"transform", "class":"org.onebusaway.gtfs_transformer.impl.RemoveOldCalendarStatements", "remove_today":false} +{"op":"retain", "match":{"file":"calendar_dates.txt"}, "retainBlocks":false} +``` + +#### Deduplicate Calendar Entries + +Finds GTFS service_ids that have the exact same set of active days and consolidates each set of duplicated +ids to a single service_id entry. + +``` +{"op":"deduplicate_service_ids"} +``` + +#### Truncate New Calendar Statements + +This operation truncates calendar and calendar date entries based on the configuration attributes in the JSON transformer snippet: + + * calendar_field: Specifies the unit of time for truncation. It can have one of the following values: + - `Calendar.YEAR` = 1 + - `Calendar.MONTH` = 2 (default) + - `Calendar.DAY_OF_MONTH` = 5 + - `Calendar.DAY_OF_YEAR` = 6 + + * calendar_amount: Specifies the number of units to truncate entries. + The value is an integer representing the amount (default = 1). + +Both `calendar_field` and `calendar_amount` must be provided as integers in the JSON transformer. + +If these parameters are not specified, the default behavior is truncation by 1 month. + +Example : + +Truncate calendar and calendar dates to the next 21 days: + +``` +{"op":"transform", "class":"org.onebusaway.gtfs_transformer.impl.TruncateNewCalendarStatements","calendar_field":6,"calendar_amount":21} +``` + +Truncate entries to the next 3 months: + +``` +{"op":"transform", "class":"org.onebusaway.gtfs_transformer.impl.TruncateNewCalendarStatements","calendar_field":2,"calendar_amount":3} +``` + +Additionally, after truncating the calendar entries, it is recommended to use a **retain operation** to ensure that only trips with valid calendar dates are retained. + +Without this retain operation, the `trips.txt` file will contain trips with non-existent calendar dates, leading to invalid data. + +``` +{"op":"transform", "class":"org.onebusaway.gtfs_transformer.impl.TruncateNewCalendarStatements","calendar_field":6,"calendar_amount":21} +{"op":"retain", "match":{"file":"calendar_dates.txt"}, "retainBlocks":false} +``` + +#### Merge Trips and Simplify Calendar Entries + +Some agencies model their transit schedule favoring multiple entries in calendar_dates.txt as opposed to a more concise +entry in calendar.txt. A smaller number of agencies take this scheme even further, creating trips.txt entries for each +service date, even when the underlying trips are exactly the same. This can cause the size of the GTFS to grow dramatically +as trips and stop times are duplicated. + +We provide a simple transformer that can attempt to detect these duplicate trips, remove them, and simplify the underlying +calendar entries to match. To run it, apply the following transform: + +``` +{"op":"calendar_simplification"} +``` + +The transform takes additional optional arguments to control its behavior: + + * min_number_of_weeks_for_calendar_entry - how many weeks does a service id need to + span before it gets its own entry in calendar.txt (default=3) + + * day_of_the_week_inclusion_ratio - if a service id is being modeled with a + calendar.txt entry, how frequent does a day of the week need to before it's + modeled positively in calendar.txt with any negative exceptions noted in + calendar_dates.txt, vs making no entry for that day of the week in + calendar.txt and instead noting any positive exceptions in + calendar_dates.txt. This is useful for filtering out a calendar that is + always active on Sunday, but has one or two Mondays for a holiday. + Frequency is defined as how often the target day of the week occurs vs the + count for day of the week appearing MOST frequently for the service id + (default=0.5) + + * undo_google_transit_data_feed_merge_tool - set to true to indicate that merged trip ids, + as produced by the [GoogleTransitDataFeedMergeTool](http://code.google.com/p/googletransitdatafeed/wiki/Merge), + should be un-mangled where possible. Merged trip ids will often have the form + `OriginalTripId_merged_1234567`. We attempt to set the trip id back to `OrginalTripId` + where appropriate. + + +#### Shift Negative Stop Times + +Some agencies have trips that they model as starting BEFORE midnight on a given service date. For these agencies, it +would be convenient to represent these trips with negative arrival and departure times in stop_times.txt. The GTFS spec and +many GTFS consumers do not support negative stop times, however. + +To help these agencies, we provide a transform to "fix" GTFS feeds with negative stop times by identifying such trips, +shifting the arrival and departure times to make them positive, and updating the service calendar entries for these trips +such that the resulting schedule is semantically the same. + +To run it, apply the following transform: + +``` +{"op":"shift_negative_stop_times"} +``` + +_A note on negative stop times:_ When writing negative stop times, the negative value ONLY applies to the hour portion + of the time. Here are a few examples: + + * "-01:45:00" => "23:45:00" on the previous day + + * "-05:13:32" => "19:13:32" on the previous day + +* Remove non-revenue stops + + Stop_times which do not allow pick up or drop off are also known as non-revenue stops. Some GTFS consumers display + these stops as if they were stops that passengers can use, at which point it is helpful to remove them. + + To remove them, apply the following transform: + +``` +{"op":"remove_non_revenue_stops"} +``` + + Terminals (the first and last stop_time of a trip) can be excluded from removal with the following transform: + +``` +{"op":"remove_non_revenue_stops_excluding_terminals"}} +``` + +* Replacing trip_headsign with the last stop + + Certain feeds contain unhelpful or incorrect trip_headsign. They can be replaced with the last stop's stop_name. + +``` +{"op":"last_stop_to_headsign"} +``` + +#### Arbitrary Transform + +We also allow you to specify arbitrary transformations as well. Here, you specify your transformation class and we will +automatically instantiate it for use in the transform pipeline. + +``` +{"op":"transform", "class":"some.class.implementing.GtfsTransformStrategy"} +``` + +We additionally provide a mechanism for setting additional properties of the transform. For all additional properties +specified in the JSON snippet, we will attempt to set that Java bean property value on the instantiated transformation object. +See for example: + +``` +{"op":"transform", "class":"org.onebusaway.gtfs_transformer.updates.ShapeTransformStrategy", "shape_id":"6010031", \ +"shape":"wjb~G|abmVpAz]v_@@?wNE_GDaFs@?@dFX`GGjN__@A"} +``` + +Here, we set additional properties on the `ShapeTransformStrategy`, making it possible to reuse and configure a generic +transformer to your needs. + +Additional Examples + +### How to Reduce your GTFS + +We can apply a modification that retains certain GTFS entities and all other entities required directly or indirectly by +those entities. For example, create a file with the following contents (call it modifications.txt, as an example): + +``` +{"op":"retain", "match":{"file":"routes.txt", "route_short_name":"B15"}} +{"op":"retain", "match":{"file":"routes.txt", "route_short_name":"B62"}} +{"op":"retain", "match":{"file":"routes.txt", "route_short_name":"B63"}} +{"op":"retain", "match":{"file":"routes.txt", "route_short_name":"BX19"}} +{"op":"retain", "match":{"file":"routes.txt", "route_short_name":"Q54"}} +{"op":"retain", "match":{"file":"routes.txt", "route_short_name":"S53"}} +``` + +Then run: + +``` +java -jar onebusaway-gtfs-transformer-cli.jar --transform=modifications.txt source-gtfs.zip target-gtfs.zip +``` + +The resulting GTFS will have the retained only the routes with the matching short names and all other entities required +to support those routes. + +* Add a Full Schedule to an Existing Feed + +Consider an existing feed with a number of routes and stops. We can add an entirely new route, with trips and stop-times +and frequency-based service, using the transform. This can be handy to add temporary service to an existing feed. + +``` +{"op":"add", "obj":{"file":"routes.txt", "route_id":"r0", "route_long_name":"Temporary Shuttle", "route_type":3}} + +{"op":"add", "obj":{"file":"calendar.txt", "service_id":"WEEKDAY", "start_date":"20120601", "end_date":"20130630", "monday":1, "tuesday":1, "wednesday":1, "thursday":1, "friday":1}} + +{"op":"add", "obj":{"file":"trips.txt", "trip_id":"t0", "route_id":"r0", "service_id":"WEEKDAY", "trip_headsign":"Inbound"}} +{"op":"add", "obj":{"file":"trips.txt", "trip_id":"t1", "route_id":"r0", "service_id":"WEEKDAY", "trip_headsign":"Outbound"}} + +{"op":"add","obj":{"file":"frequencies.txt","trip_id":"t0","start_time":"06:00:00","end_time":"22:00:00","headway_secs":900}} +{"op":"add","obj":{"file":"frequencies.txt","trip_id":"t1","start_time":"06:00:00","end_time":"22:00:00","headway_secs":900}} + +{"op":"stop_times_factory", "trip_id":"t0", "start_time":"06:00:00", "end_time":"06:20:00", "stop_ids":["s0", "s1", "s2", "s3"]} +{"op":"stop_times_factory", "trip_id":"t1", "start_time":"06:00:00", "end_time":"06:20:00", "stop_ids":["s3", "s2", "s1", "s0"]} +``` + +### Clip National GTFS for Regional Integration and Consistency + +This section of the document describes how to reduce a large GTFS to a smaller area. Several transformations can be applied to a national GTFS to clean it up and adjust the data to a regional area in order to get ready for the integration with another regional GTFS. Below is an overview of the operations carried out: + + * Removing Inactive Calendars and Dates. + * Truncating Calendars and Dates to 21 days. + * Retaining Data Within a Specific Geographic Area: a small geographic area is used for retaining only the entities within our area of interest. All routes and trips that do not pass through this area will therefore be eliminated. + * Trimming Stop Times Outside a Specific Geographic Area: a larger polygon is used to ensure that only the relevant stops_times within a wider region are retained. That means that all trips that go outside the area are truncated. + * Clean up entities that are no longer referenced by any trips. + +RetainUpFromPolygon and TrimTripFromPolygon together will clip the GTFS data to a small area and allow some Origin/Destination transit to nearby cities. + +``` +{"op":"transform", "class":"org.onebusaway.gtfs_transformer.impl.RemoveOldCalendarStatements"} +{"op":"transform", "class":"org.onebusaway.gtfs_transformer.impl.TruncateNewCalendarStatements","calendar_field":6,"calendar_amount":21} +{"op":"retain", "match":{"file":"calendar_dates.txt"}, "retainBlocks":false} + +{"op":"transform","class":"org.onebusaway.gtfs_transformer.impl.RetainUpFromPolygon","polygon":"MULTIPOLYGON (((1.2 43.7, 1.55 43.7, 1.55 43.4, 1.2 43.4, 1.2 43.7)))"} + +{"op":"transform","class":"org.onebusaway.gtfs_transformer.impl.TrimTripFromPolygon","polygon":"MULTIPOLYGON (((1.0 44.2, 2.2 44.2, 2.2 43.3, 1.0 43.3, 1.0 44.2)))"} +{"op":"retain", "match":{"file":"trips.txt"}, "retainBlocks":false} +``` + +![RetainUpFromPolygon and TrimTripFromPolygon](onebusaway-gtfs-transformer-cli-sample1.png "RetainUpFromPolygon and TrimTripFromPolygon") \ No newline at end of file diff --git a/onebusaway-collections/pom.xml b/onebusaway-collections/pom.xml new file mode 100644 index 000000000..e2b825966 --- /dev/null +++ b/onebusaway-collections/pom.xml @@ -0,0 +1,58 @@ + + 4.0.0 + + onebusaway-collections + jar + + onebusaway-collections + A library with a number of convenient methods for working with collections + + + org.onebusaway + onebusaway-gtfs-modules + 5.0.1-openmove-1 + ../pom.xml + + + + + junit + junit + 4.13.2 + test + + + io.github.classgraph + classgraph + 4.8.179 + + + + + + + com.google.cloud.tools + jib-maven-plugin + + + true + + + + + io.github.zlika + reproducible-build-maven-plugin + 0.17 + + + run-when-packaged + + strip-jar + + package + + + + + + diff --git a/onebusaway-gtfs/src/main/java/META-INF/MANIFEST.MF b/onebusaway-collections/src/main/java/META-INF/MANIFEST.MF similarity index 100% rename from onebusaway-gtfs/src/main/java/META-INF/MANIFEST.MF rename to onebusaway-collections/src/main/java/META-INF/MANIFEST.MF diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/CollectionsLibrary.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/CollectionsLibrary.java new file mode 100644 index 000000000..868b587ff --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/CollectionsLibrary.java @@ -0,0 +1,34 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +public class CollectionsLibrary { + + public static Set set(T... values) { + Set set = new HashSet(); + for (T value : values) + set.add(value); + return set; + } + + public static final boolean isEmpty(Collection c) { + return c == null || c.isEmpty(); + } +} diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/ConcurrentCollectionsLibrary.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/ConcurrentCollectionsLibrary.java new file mode 100644 index 000000000..141dbd949 --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/ConcurrentCollectionsLibrary.java @@ -0,0 +1,167 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentMap; + +public class ConcurrentCollectionsLibrary { + + private static final ListFactory _listFactory = new ListFactory(); + + private static final SetFactory _setFactory = new SetFactory(); + + public static void addToMapValueList( + ConcurrentMap> map, KEY key, VALUE value) { + CollectionFactory> factory = listFactory(); + addToMapValueCollection(map, key, value, factory); + } + + public static void removeFromMapValueList( + ConcurrentMap> map, KEY key, VALUE value) { + CollectionFactory> factory = listFactory(); + removeFromMapValueCollection(map, key, value, factory); + } + + public static void addToMapValueSet( + ConcurrentMap> map, KEY key, VALUE value) { + CollectionFactory> factory = setFactory(); + addToMapValueCollection(map, key, value, factory); + } + + public static void removeFromMapValueSet( + ConcurrentMap> map, KEY key, VALUE value) { + CollectionFactory> factory = setFactory(); + removeFromMapValueCollection(map, key, value, factory); + } + + /**** + * + ****/ + + private static > void addToMapValueCollection( + ConcurrentMap map, KEY key, VALUE value, + CollectionFactory factory) { + + while (true) { + + C values = map.get(key); + + if (values == null) { + C newKeys = factory.create(value); + values = map.putIfAbsent(key, newKeys); + if (values == null) + return; + } + + C origCopy = factory.copy(values); + + if (origCopy.contains(value)) + return; + + C extendedCopy = factory.copy(origCopy); + extendedCopy.add(value); + + if (map.replace(key, origCopy, extendedCopy)) + return; + } + } + + private static > void removeFromMapValueCollection( + ConcurrentMap map, KEY key, VALUE value, + CollectionFactory factory) { + + while (true) { + + C values = map.get(key); + + if (values == null) + return; + + C origCopy = factory.copy(values); + + if (!origCopy.contains(value)) + return; + + C reducedCopy = factory.copy(origCopy); + reducedCopy.remove(value); + + if (reducedCopy.isEmpty()) { + if (map.remove(key, origCopy)) + return; + } else { + if (map.replace(key, origCopy, reducedCopy)) + return; + } + } + } + + @SuppressWarnings("unchecked") + private static CollectionFactory> listFactory() { + return _listFactory; + } + + @SuppressWarnings("unchecked") + private static CollectionFactory> setFactory() { + return _setFactory; + } + + private interface CollectionFactory> { + public C create(VALUE value); + + public C copy(C existingValues); + } + + @SuppressWarnings("rawtypes") + private static class ListFactory implements CollectionFactory { + + @SuppressWarnings("unchecked") + @Override + public Collection create(Object value) { + List values = new ArrayList(1); + values.add(value); + return values; + } + + @SuppressWarnings("unchecked") + @Override + public Collection copy(Collection existingValues) { + return new ArrayList(existingValues); + } + } + + @SuppressWarnings("rawtypes") + private static class SetFactory implements CollectionFactory { + + @SuppressWarnings("unchecked") + @Override + public Collection create(Object value) { + Set values = new HashSet(); + values.add(value); + return values; + } + + @SuppressWarnings("unchecked") + @Override + public Collection copy(Collection existingValues) { + return new HashSet(existingValues); + } + } +} diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/Counter.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/Counter.java new file mode 100644 index 000000000..82d98bccb --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/Counter.java @@ -0,0 +1,122 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class Counter implements Serializable { + + private static final long serialVersionUID = 1L; + + private Map _counts = new HashMap(); + + private int _total = 0; + + public int size() { + return _counts.size(); + } + + public void increment(T key, int offset) { + int count = getCount(key) + offset; + _counts.put(key, count); + _total += offset; + } + + public void increment(T key) { + increment(key, 1); + } + + public void decrement(T key) { + increment(key, -1); + } + + public int getCount(T key) { + Integer count = _counts.get(key); + if (count == null) + count = 0; + return count; + } + + public Set getKeys() { + return _counts.keySet(); + } + + public Set> getEntrySet() { + return _counts.entrySet(); + } + + public int getTotal() { + return _total; + } + + public T getMax() { + int maxCount = 0; + T maxValue = null; + for (Map.Entry entry : _counts.entrySet()) { + if (maxValue == null || maxCount < entry.getValue()) { + maxValue = entry.getKey(); + maxCount = entry.getValue(); + } + } + return maxValue; + } + + /** + * @return sorted from min to max + */ + public List getSortedKeys() { + List values = new ArrayList(_counts.keySet()); + Collections.sort(values, new Comparator() { + public int compare(T o1, T o2) { + int a = getCount(o1); + int b = getCount(o2); + if (a == b) + return 0; + return a < b ? -1 : 1; + } + }); + return values; + } + + /******************************************************************************************************************* + * {@link Object} Interface + ******************************************************************************************************************/ + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof Counter)) + return false; + Counter c = (Counter) obj; + return _counts.equals(c._counts); + } + + @Override + public int hashCode() { + return _counts.hashCode(); + } + + @Override + public String toString() { + return _counts.toString(); + } +} diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/DirectedGraph.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/DirectedGraph.java new file mode 100644 index 000000000..15cae7d19 --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/DirectedGraph.java @@ -0,0 +1,164 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.onebusaway.collections.tuple.Pair; +import org.onebusaway.collections.tuple.Tuples; + +public class DirectedGraph { + + private Map> _outboundEdges = new HashMap>(); + + private Map> _inboundEdges = new HashMap>(); + + public DirectedGraph() { + + } + + public DirectedGraph(DirectedGraph graph) { + for (T node : graph.getNodes()) + addNode(node); + for (Pair edge : graph.getEdges()) + addEdge(edge.getFirst(), edge.getSecond()); + } + + public Set getNodes() { + Set nodes = new HashSet(); + nodes.addAll(_outboundEdges.keySet()); + nodes.addAll(_inboundEdges.keySet()); + return nodes; + } + + public Set> getEdges() { + Set> edges = new HashSet>(); + for (T from : _outboundEdges.keySet()) { + for (T to : _outboundEdges.get(from)) + edges.add(Tuples.pair(from, to)); + } + return edges; + } + + public Set getInboundNodes(T node) { + return get(_inboundEdges, node, false); + } + + public Set getOutboundNodes(T node) { + return get(_outboundEdges, node, false); + } + + public boolean isConnected(T from, T to) { + + if (from.equals(to)) + return true; + + return isConnected(from, to, new HashSet()); + } + + public void addNode(T node) { + get(_outboundEdges, node, true); + get(_inboundEdges, node, true); + } + + public void addEdge(T from, T to) { + get(_outboundEdges, from, true).add(to); + get(_inboundEdges, to, true).add(from); + } + + public void removeEdge(T from, T to) { + get(_outboundEdges, from, false).remove(to); + get(_inboundEdges, to, false).remove(from); + } + + private void removeNode(T node) { + + for (T from : get(_inboundEdges, node, false)) + get(_outboundEdges, from, false).remove(node); + _inboundEdges.remove(node); + + for (T to : get(_outboundEdges, node, false)) + get(_inboundEdges, to, false).remove(node); + _outboundEdges.remove(node); + } + + public List getTopologicalSort(Comparator tieBreaker) { + + List order = new ArrayList(); + DirectedGraph g = new DirectedGraph(this); + + while (true) { + + Set nodes = g.getNodes(); + + if (nodes.isEmpty()) + return order; + + List noInbound = new ArrayList(); + + for (T node : nodes) { + if (g.getInboundNodes(node).isEmpty()) + noInbound.add(node); + } + + if (noInbound.isEmpty()) + throw new IllegalStateException("cycle"); + + if (tieBreaker != null) + Collections.sort(noInbound, tieBreaker); + + T node = noInbound.get(0); + order.add(node); + g.removeNode(node); + } + } + + /**** + * Private Methods + ****/ + + private boolean isConnected(T from, T to, Set visited) { + + if (from.equals(to)) + return true; + + for (T next : get(_outboundEdges, from, false)) { + if (visited.add(next)) { + if (isConnected(next, to, visited)) + return true; + } + } + + return false; + } + + private Set get(Map> edges, T key, boolean create) { + Set set = edges.get(key); + if (set == null) { + set = new HashSet(); + if (create) + edges.put(key, set); + } + return set; + } +} diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/FactoryMap.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/FactoryMap.java new file mode 100644 index 000000000..5581388f1 --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/FactoryMap.java @@ -0,0 +1,291 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; + +/** + * A extension of {@link HashMap} that will automatically create a {@link Map} + * key-value entry if a call to {@link #get(Object)} is made where the key is + * not already present in the map. + * + * Default map entries can be created by passing in an instance of + * {@link IValueFactory} as an object factory (see + * {@link #FactoryMap(IValueFactory)}). + * + * Maps can also be created by passing a plain-old Java object. The class of the + * Java object will be used to create new value instances on demand, as long the + * class has a no-arg constructor (see {@link #FactoryMap(Object)}). + * + * @author bdferris + */ +public class FactoryMap extends HashMap { + + private static final long serialVersionUID = 1L; + + private IValueFactory _valueFactory; + + /** + * Object factory interface for creating a new value for a specified key for + * use in {@link FactoryMap} + * + * @author bdferris + */ + public interface IValueFactory { + public VF create(KF key); + } + + /** + * A convenience method for creating an instance of {@link FactoryMap} that + * wraps an existing {@link Map} and has a specific default value. The default + * value's class will be used to create new value instances as long as it has + * a no-arg constructor. + * + * @param map an existing map to wrap + * @param defaultValue see {@link #FactoryMap(Object)} for discussion + * @return a {@link Map} with factory-map behavior + */ + public static Map create(Map map, V defaultValue) { + return new MapImpl(map, new ClassInstanceFactory( + defaultValue.getClass())); + } + + /** + * A convenience method for creating an instance of {@link FactoryMap} that + * wraps an existing {@link Map} and has a specific default value factory. + * + * @param map an existing map to wrap + * @param factory see {@link #FactoryMap(IValueFactory)} for discussion + * @return a {@link Map} with factory-map behavior + */ + public static Map create(Map map, + IValueFactory factory) { + return new MapImpl(map, factory); + } + + /** + * A convenience method for creating an instance of {@link FactoryMap} that + * wraps an existing {@link SortedMap} and has a specific default value. The + * default value's class will be used to create new value instances as long as + * it has a no-arg constructor. + * + * @param map an existing sorted map to wrap + * @param defaultValue see {@link #FactoryMap(Object)} for discussion + * @return a {@link SortedMap} with factory-map behavior + */ + public static SortedMap createSorted(SortedMap map, + V defaultValue) { + return new SortedMapImpl(map, new ClassInstanceFactory( + defaultValue.getClass())); + } + + /** + * A convenience method for creating an instance of {@link FactoryMap} that + * wraps an existing {@link SortedMap} and has a specific default value + * factory. + * + * @param map an existing sorted map to wrap + * @param factory see {@link #FactoryMap(IValueFactory)} for discussion + * @return a {@link SortedMap} with factory-map behavior + */ + public static SortedMap createSorted(SortedMap map, + IValueFactory factory) { + return new SortedMapImpl(map, factory); + } + + /** + * A factory map constructor that accepts a default value instance. The + * {@link Class} of the default value instance will be used to create new + * default value instances as needed assuming the class has no-arg + * constructor. New values will be created when calls are made to + * {@link #get(Object)} and the specified key is not already present in the + * map. Why do we accept an object instance instead of a class instance? It + * makes it easier to handle cases where V is itself a parameterized type. + * + * @param factoryInstance the {@link Class} of the instance will be used to + * create new values as needed + */ + public FactoryMap(V factoryInstance) { + this(new ClassInstanceFactory(factoryInstance.getClass())); + } + + /** + * A factory map constructor that accepts a {@link IValueFactory} default + * value factory. The value factory will be called when calls are made to + * {@link #get(Object)} and the specified key is not already present in the + * map. + * + * @param valueFactory the default value factory + */ + public FactoryMap(IValueFactory valueFactory) { + _valueFactory = valueFactory; + } + + /** + * Returns the value to which the specified key is mapped, or a default value + * instance if the specified key is not present in the map. Subsequent clals + * to {@link #get(Object)} with the same key will return the same value + * instance. + * + * @see Map#get(Object) + * @see #put(Object, Object) + */ + @SuppressWarnings("unchecked") + @Override + public V get(Object key) { + if (!containsKey(key)) + put((K) key, createValue((K) key)); + return super.get(key); + } + + private V createValue(K key) { + return _valueFactory.create(key); + } + + private static class ClassInstanceFactory implements + IValueFactory, Serializable { + + private static final long serialVersionUID = 1L; + + private Class _valueClass; + + @SuppressWarnings({"rawtypes", "unchecked"}) + public ClassInstanceFactory(Class valueClass) { + _valueClass = valueClass; + } + + public V create(K key) { + try { + return _valueClass.newInstance(); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + } + + private static class MapImpl implements Map, Serializable { + + private static final long serialVersionUID = 1L; + + private Map _source; + + private IValueFactory _valueFactory; + + public MapImpl(Map source, IValueFactory valueFactory) { + _source = source; + _valueFactory = valueFactory; + } + + public void clear() { + _source.clear(); + } + + public boolean containsKey(Object key) { + return _source.containsKey(key); + } + + public boolean containsValue(Object value) { + return _source.containsValue(value); + } + + public Set> entrySet() { + return _source.entrySet(); + } + + @SuppressWarnings("unchecked") + public V get(Object key) { + if (!containsKey(key)) + _source.put((K) key, createValue((K) key)); + return _source.get(key); + } + + public boolean isEmpty() { + return _source.isEmpty(); + } + + public Set keySet() { + return _source.keySet(); + } + + public V put(K key, V value) { + return _source.put(key, value); + } + + public void putAll(Map t) { + _source.putAll(t); + } + + public V remove(Object key) { + return _source.remove(key); + } + + public int size() { + return _source.size(); + } + + public Collection values() { + return _source.values(); + } + + private V createValue(K key) { + return _valueFactory.create(key); + } + } + + private static class SortedMapImpl extends MapImpl implements + SortedMap { + + private static final long serialVersionUID = 1L; + + private SortedMap _source; + + public SortedMapImpl(SortedMap source, + IValueFactory valueFactory) { + super(source, valueFactory); + _source = source; + } + + public Comparator comparator() { + return _source.comparator(); + } + + public K firstKey() { + return _source.firstKey(); + } + + public SortedMap headMap(K toKey) { + return _source.headMap(toKey); + } + + public K lastKey() { + return _source.lastKey(); + } + + public SortedMap subMap(K fromKey, K toKey) { + return _source.subMap(fromKey, toKey); + } + + public SortedMap tailMap(K fromKey) { + return _source.tailMap(fromKey); + } + } +} diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/FunctionalLibrary.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/FunctionalLibrary.java new file mode 100644 index 000000000..82a847753 --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/FunctionalLibrary.java @@ -0,0 +1,47 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections; + +import java.util.ArrayList; +import java.util.List; + +import org.onebusaway.collections.beans.PropertyPathExpression; + +public final class FunctionalLibrary { + private FunctionalLibrary() { + + } + + public static List filter(Iterable elements, + String propertyPathExpression, Object value) { + List matches = new ArrayList(); + PropertyPathExpression query = new PropertyPathExpression( + propertyPathExpression); + for (T element : elements) { + Object result = query.invoke(element); + if ((value == null && result == null) + || (value != null && value.equals(result))) + matches.add(element); + } + return matches; + } + + public static T filterFirst(Iterable elements, + String propertyPathExpression, Object value) { + List matches = filter(elements, propertyPathExpression, value); + return matches.isEmpty() ? null : matches.get(0); + } +} diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/MappingLibrary.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/MappingLibrary.java new file mode 100644 index 000000000..746d471ba --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/MappingLibrary.java @@ -0,0 +1,193 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.onebusaway.collections.beans.PropertyPathExpression; + +/** + * A number of functional-programming-inspired convenience methods for mapping + * one set of values to another. + * + * @author bdferris + * @see PropertyPathExpression + */ +public class MappingLibrary { + + /** + * Iterate over a collection of values, evaluating a + * {@link PropertyPathExpression} on each value, and constructing a + * {@link List} from the expression results. + * + * @param values an iterable collection of values to iterate over + * @param propertyPathExpression a property path expression to evaluate + * against each collection value + * @return a List composed of the property path expression evaluation results + */ + @SuppressWarnings("unchecked") + public static List map(Iterable values, + String propertyPathExpression) { + List mappedValues = new ArrayList(); + PropertyPathExpression query = new PropertyPathExpression( + propertyPathExpression); + for (T1 value : values) + mappedValues.add((T2) query.invoke(value)); + return mappedValues; + } + + /** + * This method is kept for backwards compatibility, and a more concise version + * can be found in {@link #map(Iterable, String)} + */ + public static List map(Iterable values, + String propertyPathExpression, Class resultType) { + return map(values, propertyPathExpression); + } + + /** + * Construct a {@link Map} from a set of values where the key for each value + * is the result from the evaluation of a {@link PropertyPathExpression} on + * each value. If two values in the iterable collection have the same key, + * subsequent values will overwrite previous values. + * + * @param values an iterable collection of values to iterate over + * @param propertyPathExpression a property path expression to evaluate + * against each collection value + * @return a map with values from the specified collection and keys from the + * property path expression + */ + @SuppressWarnings("unchecked") + public static Map mapToValue(Iterable values, + String propertyPathExpression) { + + Map byKey = new HashMap(); + PropertyPathExpression query = new PropertyPathExpression( + propertyPathExpression); + + for (V value : values) { + K key = (K) query.invoke(value); + byKey.put(key, value); + } + + return byKey; + } + + /** + * This method is kept for backwards compatibility, and a more concise version + * can be found in {@link #mapToValue(Iterable, String)} + */ + public static Map mapToValue(Iterable values, + String property, Class keyType) { + return mapToValue(values, property); + } + + /** + * Construct a {@link Map} from a set of values where the key for each value + * is the result of the evaluation of a {@link PropertyPathExpression} on each + * value. Each key maps to a {@link List} of values that all mapped to that + * same key. + * + * @param values an iterable collection of values to iterate over + * @param propertyPathExpression a property path expression to evaluate + * against each collection value + * @return a map with values from the specified collection and keys from the + * property path expression + */ + @SuppressWarnings("unchecked") + public static Map> mapToValueList(Iterable values, + String property) { + return mapToValueCollection(values, property, new ArrayList().getClass()); + } + + /** + * This method is kept for backwards compatibility, and a more concise version + * can be found in {@link #mapToValueList(Iterable, String)} + */ + @SuppressWarnings("unchecked") + public static Map> mapToValueList(Iterable values, + String property, Class keyType) { + return mapToValueCollection(values, property, new ArrayList().getClass()); + } + + /** + * Construct a {@link Map} from a set of values where the key for each value + * is the result of the evaluation of a {@link PropertyPathExpression} on each + * value. Each key maps to a {@link Set} of values that all mapped to that + * same key. + * + * @param values an iterable collection of values to iterate over + * @param propertyPathExpression a property path expression to evaluate + * against each collection value + * @return a map with values from the specified collection and keys from the + * property path expression + */ + + @SuppressWarnings("unchecked") + public static Map> mapToValueSet(Iterable values, + String property) { + return mapToValueCollection(values, property, new HashSet().getClass()); + } + + /** + * Construct a {@link Map} from a set of values where the key for each value + * is the result of the evaluation of a {@link PropertyPathExpression} on each + * value. Each key maps to a collection of values that all mapped to that same + * key. The collection type must have a no-arg constructor that can be used to + * create new collection instances as necessary. + * + * @param values an iterable collection of values to iterate over + * @param propertyPathExpression a property path expression to evaluate + * against each collection value + * @param collectionType the collection type used to contain mutiple values + * that map to the same key + * @return a map with values from the specified collection and keys from the + * property path expression + */ + @SuppressWarnings("unchecked") + public static , CIMPL extends C> Map mapToValueCollection( + Iterable values, String property, Class collectionType) { + + Map byKey = new HashMap(); + PropertyPathExpression query = new PropertyPathExpression(property); + + for (V value : values) { + + K key = (K) query.invoke(value); + C valuesForKey = byKey.get(key); + if (valuesForKey == null) { + + try { + valuesForKey = collectionType.newInstance(); + } catch (Exception ex) { + throw new IllegalStateException( + "error instantiating collection type: " + collectionType, ex); + } + + byKey.put(key, valuesForKey); + } + valuesForKey.add(value); + } + + return byKey; + } +} diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/Max.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/Max.java new file mode 100644 index 000000000..22443582c --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/Max.java @@ -0,0 +1,33 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections; + +public class Max { + + private Min _min = new Min(); + + public void add(double value, T element) { + _min.add(-value, element); + } + + public T getMaxElement() { + return _min.getMinElement(); + } + + public double getMaxValue() { + return -_min.getMinValue(); + } +} diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/Min.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/Min.java new file mode 100644 index 000000000..88d41369a --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/Min.java @@ -0,0 +1,55 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections; + +import java.util.ArrayList; +import java.util.List; + +public class Min { + + private double _minValue = Double.POSITIVE_INFINITY; + + private List _minElements = new ArrayList(); + + public void add(double value, T element) { + if (value == _minValue) { + _minElements.add(element); + } else if (value < _minValue) { + _minElements.clear(); + _minElements.add(element); + _minValue = value; + } + } + + public boolean isEmpty() { + return _minElements.isEmpty(); + } + + public double getMinValue() { + return _minValue; + } + + public T getMinElement() { + if (_minElements.isEmpty()) + return null; + return _minElements.get(0); + } + + public List getMinElements() { + return _minElements; + } + +} diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/Range.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/Range.java new file mode 100644 index 000000000..f53dc3925 --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/Range.java @@ -0,0 +1,78 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections; + +/** + * A simple, mutable double-precision range class for tracking a min and max + * value. + * + * @author bdferris + */ +public class Range { + + private double _min = Double.POSITIVE_INFINITY; + + private double _max = Double.NEGATIVE_INFINITY; + + public Range() { + + } + + public Range(double v) { + addValue(v); + } + + public Range(double from, double to) { + addValue(from); + addValue(to); + } + + public void addValue(double value) { + _min = Math.min(_min, value); + _max = Math.max(_max, value); + } + + public void setMin(double value) { + _min = value; + _max = Math.max(_max, value); + } + + public void setMax(double value) { + _min = Math.min(_min, value); + _max = value; + } + + public double getMin() { + return _min; + } + + public double getMax() { + return _max; + } + + public double getRange() { + return _max - _min; + } + + public boolean isEmpty() { + return _min > _max; + } + + @Override + public String toString() { + return _min + " " + _max; + } +} diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/adapter/AdaptableCollection.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/adapter/AdaptableCollection.java new file mode 100644 index 000000000..2fd9c9c7a --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/adapter/AdaptableCollection.java @@ -0,0 +1,43 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections.adapter; + +import java.util.AbstractCollection; +import java.util.Collection; +import java.util.Iterator; + +class AdaptableCollection extends AbstractCollection { + + private final Collection _source; + + private final IAdapter _adapater; + + public AdaptableCollection(Collection source, + IAdapter adapater) { + _source = source; + _adapater = adapater; + } + + @Override + public Iterator iterator() { + return AdapterLibrary.adaptIterator(_source.iterator(), _adapater); + } + + @Override + public int size() { + return _source.size(); + } +} diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/adapter/AdaptableSet.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/adapter/AdaptableSet.java new file mode 100644 index 000000000..0c0344647 --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/adapter/AdaptableSet.java @@ -0,0 +1,43 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections.adapter; + +import java.util.AbstractSet; +import java.util.Iterator; +import java.util.Set; + +class AdaptableSet extends AbstractSet { + + private final Set _source; + + private final IAdapter _adapter; + + public AdaptableSet(Set source, IAdapter adapter) { + _source = source; + _adapter = adapter; + } + + @Override + public Iterator iterator() { + return AdapterLibrary.adaptIterator(_source.iterator(), _adapter); + } + + @Override + public int size() { + return _source.size(); + } + +} diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/adapter/AdaptableValueMapEntry.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/adapter/AdaptableValueMapEntry.java new file mode 100644 index 000000000..737f71efa --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/adapter/AdaptableValueMapEntry.java @@ -0,0 +1,48 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections.adapter; + +import java.util.Map; +import java.util.Map.Entry; + +class AdaptableValueMapEntry implements + Map.Entry { + + private final Entry _source; + + private final IAdapter _adapter; + + public AdaptableValueMapEntry(Map.Entry source, + IAdapter adapter) { + _source = source; + _adapter = adapter; + } + + @Override + public KEY getKey() { + return _source.getKey(); + } + + @Override + public TO_VALUE getValue() { + return AdapterLibrary.apply(_adapter, _source.getValue()); + } + + @Override + public TO_VALUE setValue(TO_VALUE value) { + throw new UnsupportedOperationException(); + } +} diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/adapter/AdaptableValueSortedMap.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/adapter/AdaptableValueSortedMap.java new file mode 100644 index 000000000..b9634cd29 --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/adapter/AdaptableValueSortedMap.java @@ -0,0 +1,144 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections.adapter; + +import java.util.Collection; +import java.util.Comparator; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; + +class AdaptableValueSortedMap implements + SortedMap { + + private final SortedMap _source; + + private final IAdapter _adapter; + + public AdaptableValueSortedMap(SortedMap source, + IAdapter adapter) { + _source = source; + _adapter = adapter; + } + + @Override + public int size() { + return _source.size(); + } + + @Override + public boolean isEmpty() { + return _source.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return _source.containsKey(key); + } + + @Override + public TO_VALUE get(Object key) { + return adapt(_source.get(key)); + } + + @Override + public TO_VALUE remove(Object key) { + return adapt(_source.remove(key)); + } + + @Override + public void clear() { + _source.clear(); + } + + @Override + public Comparator comparator() { + return _source.comparator(); + } + + @Override + public SortedMap subMap(KEY fromKey, KEY toKey) { + return new AdaptableValueSortedMap( + _source.subMap(fromKey, toKey), _adapter); + } + + @Override + public SortedMap headMap(KEY toKey) { + return new AdaptableValueSortedMap( + _source.headMap(toKey), _adapter); + } + + @Override + public SortedMap tailMap(KEY fromKey) { + return new AdaptableValueSortedMap( + _source.tailMap(fromKey), _adapter); + } + + @Override + public KEY firstKey() { + return _source.firstKey(); + } + + @Override + public KEY lastKey() { + return _source.lastKey(); + } + + @Override + public Set keySet() { + return _source.keySet(); + } + + @Override + public Collection values() { + return AdapterLibrary.adaptCollection(_source.values(), _adapter); + } + + @Override + public Set> entrySet() { + return AdapterLibrary.adaptSet(_source.entrySet(), + new MapEntryValueAdapter(_adapter)); + } + + /**** + * Any value methods that include modification are unsupported + ****/ + + @Override + public TO_VALUE put(KEY key, TO_VALUE value) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean containsValue(Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public void putAll(Map m) { + throw new UnsupportedOperationException(); + } + + /**** + * Private Methods + ****/ + + private TO_VALUE adapt(FROM_VALUE value) { + if (value == null) + return null; + return _adapter.adapt(value); + } +} diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/adapter/AdapterLibrary.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/adapter/AdapterLibrary.java new file mode 100644 index 000000000..ee2e18e95 --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/adapter/AdapterLibrary.java @@ -0,0 +1,83 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections.adapter; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; + +public class AdapterLibrary { + + public static final TO apply(IAdapter adapter, FROM value) { + if (value == null) + return null; + return adapter.adapt(value); + } + + public static IAdapter getIdentityAdapter(Class type) { + return new IdentityAdapter(); + } + + public static Iterable adapt(Iterable source, + IAdapter adapter) { + return new IterableAdapter(source, adapter); + } + + public static Iterator adaptIterator(Iterator source, + IAdapter adapter) { + return new IteratorAdapter(source, adapter); + } + + public static Collection adaptCollection( + Collection source, IAdapter adapter) { + return new AdaptableCollection(source, adapter); + } + + public static Set adaptSet(Set source, + IAdapter adapter) { + return new AdaptableSet(source, adapter); + } + + public static Map.Entry adaptMapEntry( + Map.Entry source, IAdapter adapter) { + return new AdaptableValueMapEntry(source, + adapter); + } + + public static SortedMap adaptSortedMap( + SortedMap source, IAdapter adapter) { + return new AdaptableValueSortedMap(source, + adapter); + } + + /***************************************************************************** + * + ****************************************************************************/ + + private static final class IdentityAdapter implements IAdapter, + Serializable { + + private static final long serialVersionUID = 1L; + + public T adapt(T source) { + return source; + } + } + +} diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/adapter/IAdapter.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/adapter/IAdapter.java new file mode 100644 index 000000000..265040b36 --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/adapter/IAdapter.java @@ -0,0 +1,20 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections.adapter; + +public interface IAdapter { + public TO adapt(FROM source); +} diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/adapter/IterableAdapter.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/adapter/IterableAdapter.java new file mode 100644 index 000000000..fef484ebe --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/adapter/IterableAdapter.java @@ -0,0 +1,38 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections.adapter; + +import java.io.Serializable; +import java.util.Iterator; + +public class IterableAdapter implements Iterable, Serializable { + + private static final long serialVersionUID = 1L; + + private Iterable _source; + + private IAdapter _adapter; + + public IterableAdapter(Iterable source, IAdapter adapter) { + _source = source; + _adapter = adapter; + } + + public Iterator iterator() { + return new IteratorAdapter(_source.iterator(),_adapter); + } + +} diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/adapter/IteratorAdapter.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/adapter/IteratorAdapter.java new file mode 100644 index 000000000..42eeb1664 --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/adapter/IteratorAdapter.java @@ -0,0 +1,42 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections.adapter; + +import java.util.Iterator; + +class IteratorAdapter implements Iterator { + + private final Iterator _it; + + private final IAdapter _adapter; + + public IteratorAdapter(Iterator it, IAdapter adapter) { + _it = it; + _adapter = adapter; + } + + public boolean hasNext() { + return _it.hasNext(); + } + + public TO next() { + return _adapter.adapt(_it.next()); + } + + public void remove() { + _it.remove(); + } +} \ No newline at end of file diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/adapter/ListAdapter.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/adapter/ListAdapter.java new file mode 100644 index 000000000..70297e46b --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/adapter/ListAdapter.java @@ -0,0 +1,52 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections.adapter; + +import java.util.AbstractList; +import java.util.List; + +/** + * Create an adapted {@link List} instance that adapts a list of type FROM to + * type TO using a {@link IAdapter} instance. The adapted list will be immutable + * but will reflect changes to the underlying list. + * + * @author bdferris + * + * @param + * @param + */ +public class ListAdapter extends AbstractList { + + private final List _source; + + private final IAdapter _adapter; + + public ListAdapter(List source, IAdapter adapter) { + _source = source; + _adapter = adapter; + } + + @Override + public TO get(int index) { + FROM v = _source.get(index); + return _adapter.adapt(v); + } + + @Override + public int size() { + return _source.size(); + } +} diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/adapter/MapEntryValueAdapter.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/adapter/MapEntryValueAdapter.java new file mode 100644 index 000000000..31a2a6bf2 --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/adapter/MapEntryValueAdapter.java @@ -0,0 +1,61 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections.adapter; + +import java.util.Map.Entry; + +class MapEntryValueAdapter implements + IAdapter, Entry> { + + private IAdapter _adapter; + + public MapEntryValueAdapter(IAdapter adapter) { + _adapter = adapter; + } + + @Override + public Entry adapt(Entry source) { + TO_VALUE v = AdapterLibrary.apply(_adapter, source.getValue()); + return new EntryImpl(source.getKey(), v); + } + + private static class EntryImpl implements Entry { + + private final K _key; + private final V _value; + + public EntryImpl(K key, V value) { + _key = key; + _value = value; + } + + @Override + public K getKey() { + return _key; + } + + @Override + public V getValue() { + return _value; + } + + @Override + public V setValue(V value) { + throw new UnsupportedOperationException(); + } + } + +} diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/beans/DefaultPropertyMethodResolver.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/beans/DefaultPropertyMethodResolver.java new file mode 100644 index 000000000..22608f33a --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/beans/DefaultPropertyMethodResolver.java @@ -0,0 +1,125 @@ +/** + * Copyright (C) 2012 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections.beans; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.github.classgraph.ClassGraph; +import io.github.classgraph.ClassInfo; +import io.github.classgraph.ScanResult; + +public class DefaultPropertyMethodResolver implements PropertyMethodResolver { + + private static final String OBA_IFACE_PATH = "org.onebusaway.transit_data_federation.services.transit_graph."; + private static final String OBA_IMPL_PATH = "org.onebusaway.transit_data_federation.impl.transit_graph."; + private static Map> interfaceMethodsByKey = new HashMap(); + private static Map interfaceToImplMap; + static { + // TODO inject these somehow + interfaceToImplMap = new HashMap<>(); + // support GTFS interface corner case + interfaceToImplMap.put("org.onebusaway.gtfs.model.StopLocation", "org.onebusaway.gtfs.model.Stop"); + // support OBA interfaces + String[] entryInterfaces = {"AgencyEntry", "BlockConfigurationEntry", "BlockEntry", "BlockStopTimeEntry", + "BlockTripEntry", "FrequencyBlockStopTimeEntry", "FrequencyEntry", "RouteCollectionEntry", + "RouteEntry", "StopEntry", "StopTimeEntry", "TripEntry"}; + for (String interfaceName : entryInterfaces) { + interfaceToImplMap.put(OBA_IFACE_PATH + interfaceName, + OBA_IMPL_PATH + interfaceName + "Impl"); + } + } + + @Override + public PropertyMethod getPropertyMethod(Class targetType, + String propertyName) { + String methodName = "get"; + for(String part : propertyName.split(" |_")) { + methodName += part.substring(0, 1).toUpperCase() + + part.substring(1); + } + Method method = null; + try { + if(targetType.isInterface()) { + List methods = getCachedInterfaceMethods(targetType, propertyName, methodName); + if(methods.size() == 1) + method = methods.get(0); + else { + throw new IllegalStateException("Ambiguous implementation set for interface: " + + targetType + " /" + + methodName + + " with potentials: " + methods + + " and " + interfaceToImplMap.keySet() + " known interface mappings"); + } + } else + method = targetType.getMethod(methodName); + } catch (Exception ex) { + throw new IllegalStateException("error introspecting class: " + + targetType, ex); + } + if (method == null) { + throw new IllegalStateException("could not find property \"" + + propertyName + "\" for type " + targetType.getName()); + } + method.setAccessible(true); + return new PropertyMethodImpl(method); + } + + + private List getCachedInterfaceMethods(Class targetType, String propertyName, String methodName) { + String key = hash(targetType, propertyName); + if (interfaceMethodsByKey.containsKey(key)) { + return interfaceMethodsByKey.get(key); + } + + ScanResult scanResult = new ClassGraph() + .acceptPackages("org.onebusaway") + .enableClassInfo() + .scan(); + + List methods = new ArrayList(); + for (ClassInfo ci : scanResult.getClassesImplementing(targetType.getCanonicalName())) { + try { + if (matches(ci.getName(), targetType)) { + methods.add(Class.forName(ci.getName()).getMethod(methodName)); + } + } catch(Exception e) { + continue; + } + } + interfaceMethodsByKey.put(key, methods); + + return methods; + } + + + private boolean matches(String reflectedTypeName, Class targetType) { + String targetTypeName = targetType.getName(); + if (interfaceToImplMap.containsKey(targetTypeName)) { + String implName = interfaceToImplMap.get(targetTypeName); + return implName.equals(reflectedTypeName); + } + return targetTypeName.equals(reflectedTypeName); + } + + private String hash(Class targetType, String propertyName) { + return targetType.getName() + "." + propertyName; + } + +} diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/beans/PropertyInvocationResult.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/beans/PropertyInvocationResult.java new file mode 100644 index 000000000..dee30b396 --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/beans/PropertyInvocationResult.java @@ -0,0 +1,28 @@ +/** + * Copyright (C) 2012 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections.beans; + +public class PropertyInvocationResult { + public final Object parent; + public final String propertyName; + public final Object value; + + public PropertyInvocationResult(Object parent, String propertyName, Object value) { + this.parent = parent; + this.propertyName = propertyName; + this.value = value; + } +} \ No newline at end of file diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/beans/PropertyMethod.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/beans/PropertyMethod.java new file mode 100644 index 000000000..bb76776a5 --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/beans/PropertyMethod.java @@ -0,0 +1,46 @@ +/** + * Copyright (C) 2012 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections.beans; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * A wrapper interface that provides methods similar to + * {@link Method#invoke(Object, Object...)} for Java bean property getter + * methods (aka takes no arguments), but allows us to more easily swap in + * different underlying method implementations. + * + * @author bdferris + * @see PropertyMethodResolver + */ +public interface PropertyMethod { + + /** + * Invoke the property method on the specified target object and return the + * resulting value. + * + * @param target the target bean to invoke the property method on. + * @return the value resulting from the method invocation. + * @throws IllegalArgumentException + * @throws IllegalAccessException + * @throws InvocationTargetException + */ + public Object invoke(Object target) throws IllegalArgumentException, + IllegalAccessException, InvocationTargetException; + + public Class getReturnType(); +} \ No newline at end of file diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/beans/PropertyMethodImpl.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/beans/PropertyMethodImpl.java new file mode 100644 index 000000000..0814d9db1 --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/beans/PropertyMethodImpl.java @@ -0,0 +1,46 @@ +/** + * Copyright (C) 2012 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections.beans; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * An implementation of {@link PropertyMethod} that uses a {@link Method} for + * the underlying invocation. + * + * @author bdferris + * @see PropertyMethod + */ +class PropertyMethodImpl implements PropertyMethod { + + private final Method _method; + + public PropertyMethodImpl(Method method) { + _method = method; + } + + @Override + public Object invoke(Object value) throws IllegalArgumentException, + IllegalAccessException, InvocationTargetException { + return _method.invoke(value); + } + + @Override + public Class getReturnType() { + return _method.getReturnType(); + } +} diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/beans/PropertyMethodResolver.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/beans/PropertyMethodResolver.java new file mode 100644 index 000000000..63ccbf683 --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/beans/PropertyMethodResolver.java @@ -0,0 +1,36 @@ +/** + * Copyright (C) 2012 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections.beans; + +/** + * A strategy interface for resolving {@link PropertyMethod} instances for a + * target class and property name. + * + * @author bdferris + * @see PropertyMethod + */ +public interface PropertyMethodResolver { + + /** + * Resolve a property method for the target type and property name. + * + * @param targetType + * @param propertyName + * @return a property method implementation, or null if not found. + */ + public PropertyMethod getPropertyMethod(Class targetType, + String propertyName); +} diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/beans/PropertyPathCollectionExpression.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/beans/PropertyPathCollectionExpression.java new file mode 100644 index 000000000..5804c9622 --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/beans/PropertyPathCollectionExpression.java @@ -0,0 +1,174 @@ +/** + * Copyright (C) 2012 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections.beans; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Simple support for Java bean property path expression parsing and evaluation, + * where the resulting evaluation produces multiple values. + * + * Example: + * + * interface Container { public List getEntries(); } + * + * interface Entry { public String getName(); } + * + * If you call {@link #evaluate(Object, String)} with a Container object and + * property path of "entries.name", the expression will iterate over each Entry + * in the Container, collecting the name property of each entry in the results. + * + * @author bdferris + */ +public final class PropertyPathCollectionExpression { + + private String[] _properties; + + private PropertyMethod[] _methods = null; + + private PropertyMethodResolver _resolver = new DefaultPropertyMethodResolver(); + + public static void evaluate(Object target, String query, + Collection values) { + PropertyPathCollectionExpression expression = new PropertyPathCollectionExpression( + query); + expression.invoke(target, values); + } + + public static List evaluate(Object target, String query) { + PropertyPathCollectionExpression expression = new PropertyPathCollectionExpression( + query); + List values = new ArrayList(); + expression.invoke(target, values); + return values; + } + + /** + * + * @param query the property path expression to evaluate + */ + public PropertyPathCollectionExpression(String query) { + _properties = query.split("\\."); + _methods = new PropertyMethod[_properties.length]; + } + + public void setPropertyMethodResolver(PropertyMethodResolver resolver) { + _resolver = resolver; + } + + public String getPath() { + StringBuilder b = new StringBuilder(); + for (int i = 0; i < _properties.length; i++) { + if (i > 0) + b.append('.'); + b.append(_properties[i]); + } + return b.toString(); + } + + /** + * Invoke the property path expression against the specified object value + * + * @param value the target bean to start the property path expression against. + * @param results the output collection where evaluated results will be + * stored. + */ + public void invoke(Object value, Collection results) { + invoke(null, value, 0, new ValueResultCollector(results)); + } + + public void invokeReturningFullResult(Object value, + Collection results) { + invoke(null, value, 0, new FullResultCollector(results)); + } + + private void invoke(Object parent, Object value, int methodIndex, + ResultCollector collector) { + if (methodIndex == _methods.length) { + String propertyName = _properties.length == 0 ? null + : _properties[_properties.length - 1]; + collector.addResult(parent, propertyName, value); + return; + } + if (value == null) { + return; + } + PropertyMethod m = getPropertyMethod(value.getClass(), methodIndex); + Object result = null; + try { + result = m.invoke(value); + } catch (Exception ex) { + throw new IllegalStateException("error invoking property reader: obj=" + + value + " property=" + _properties[methodIndex], ex); + } + if (result instanceof Iterable) { + Iterable iterable = (Iterable) result; + for (Object child : iterable) { + invoke(value, child, methodIndex + 1, collector); + } + } else if (result instanceof Object[]) { + Object[] values = (Object[]) result; + for (Object child : values) { + invoke(value, child, methodIndex + 1, collector); + } + } else { + invoke(value, result, methodIndex + 1, collector); + } + } + + private PropertyMethod getPropertyMethod(Class valueType, int methodIndex) { + PropertyMethod method = _methods[methodIndex]; + if (method == null) { + method = _resolver.getPropertyMethod(valueType, _properties[methodIndex]); + _methods[methodIndex] = method; + } + return method; + } + + private interface ResultCollector { + public void addResult(Object parent, String propertyName, Object value); + } + + private static class ValueResultCollector implements ResultCollector { + + private final Collection values; + + public ValueResultCollector(Collection values) { + this.values = values; + } + + @Override + public void addResult(Object parent, String propertyName, Object value) { + values.add(value); + } + } + + private static class FullResultCollector implements ResultCollector { + + private final Collection results; + + public FullResultCollector(Collection results) { + this.results = results; + } + + @Override + public void addResult(Object parent, String propertyName, Object value) { + results.add(new PropertyInvocationResult(parent, propertyName, value)); + } + } +} \ No newline at end of file diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/beans/PropertyPathExpression.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/beans/PropertyPathExpression.java new file mode 100644 index 000000000..14daffd93 --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/beans/PropertyPathExpression.java @@ -0,0 +1,165 @@ +/** + * Copyright (C) 2011 Brian Ferris + * Copyright (C) 2011 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections.beans; + +/** + * Simple support for Java bean property path expression parsing and evaluation. + * + * Consider a simple Order bean class with a property named {@code customer} of + * type Customer that has its own property named {@code name}. A path expression + * of {@code customer.name}, evaluated on an Order instance will internally make + * a call to the {@code getCustomer()} method on the Order object, and then make + * a call to the {@code getName()} method on the Customer object returned in the + * previous method call. The result of the expression will be the cusomter's + * name. + * + * Instances of {@link PropertyPathExpression} are thread-safe for concurrent + * use across threads with one restriction. A call to {@link #initialize(Class)} + * must be made in advance of concurrent access to ensure that class + * introspection has been completed. + * + * @author bdferris + * + */ +public final class PropertyPathExpression { + + private String[] _properties; + + private transient PropertyMethod[] _methods = null; + + private PropertyMethodResolver _resolver = new DefaultPropertyMethodResolver(); + + /** + * A static convenience method for evaluating a property path expression on a + * target object. If you need to repeatedly evaluate the same property path + * expression, consider creating a {@link PropertyPathExpression} object + * directly so that bean introspection information can be cached. + * + * @param target the target bean instance to evaluate against + * @param query the property path expression to evaluate + * @return the result of the evaluation of the property path expression + */ + public static Object evaluate(Object target, String query) { + return new PropertyPathExpression(query).invoke(target); + } + + /** + * + * @param query the property path expression to evaluate + */ + public PropertyPathExpression(String query) { + _properties = query.split("\\."); + } + + public void setPropertyMethodResolver(PropertyMethodResolver resolver) { + _resolver = resolver; + } + + public String getPath() { + StringBuilder b = new StringBuilder(); + for (int i = 0; i < _properties.length; i++) { + if (i > 0) + b.append('.'); + b.append(_properties[i]); + } + return b.toString(); + } + + /** + * Opportunistically complete and cache bean introspection given a source + * value target type. + * + * @param sourceValueType the class of objects that will be passed in calls to + * {@link #invoke(Object)} + * @return the final return type of the evaluated path expression + * @throws IllegalStateException on introspection errors + */ + public Class initialize(Class sourceValueType) { + + if (_methods != null) { + if (_methods.length == 0) + return sourceValueType; + return _methods[_methods.length - 1].getReturnType(); + } + + _methods = new PropertyMethod[_properties.length]; + + for (int i = 0; i < _properties.length; i++) { + _methods[i] = _resolver.getPropertyMethod(sourceValueType, _properties[i]); + sourceValueType = _methods[i].getReturnType(); + } + + return sourceValueType; + } + + /** + * Returns the type of the parent class containing the property to be + * evaluated. For simple property path expressions containing just one + * property, the parent class will be equal to the "sourceValueType" + * parameter. For compound property path expressions, the parent class is + * equal to the class from which the property value will ultimately be + * accessed. + * + * @param sourceValueType + * @return + */ + public Class getParentType(Class sourceValueType) { + initialize(sourceValueType); + if (_methods.length < 2) { + return sourceValueType; + } + return _methods[_methods.length - 2].getReturnType(); + } + + /** + * @return the last property in the compound property path expression + */ + public String getLastProperty() { + return _properties[_properties.length - 1]; + } + + /** + * Invoke the property path expression against the specified object value + * + * @param value the target bean to start the property path expression against + * @return the result of the property path expression evaluation + * @throws IllegalStateException on introspection and evaluation errors + */ + public Object invoke(Object value) { + return invokeReturningFullResult(value).value; + } + + public PropertyInvocationResult invokeReturningFullResult(Object value) { + if (_methods == null) + initialize(value.getClass()); + + Object parent = null; + String propertyName = null; + for (int i = 0; i < _properties.length; i++) { + parent = value; + propertyName = _properties[i]; + PropertyMethod m = _methods[i]; + try { + value = m.invoke(value); + } catch (Exception ex) { + throw new IllegalStateException("error invoking property reader: obj=" + + value + " property=" + _properties[i], ex); + } + } + return new PropertyInvocationResult(parent, propertyName, value); + } +} \ No newline at end of file diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/combinations/CombinationIterator.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/combinations/CombinationIterator.java new file mode 100644 index 000000000..198e7cb62 --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/combinations/CombinationIterator.java @@ -0,0 +1,85 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections.combinations; + +import java.io.Serializable; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +import org.onebusaway.collections.tuple.Pair; +import org.onebusaway.collections.tuple.Tuples; + +/*********************************************************************************************************************** + * Internal Classes + **********************************************************************************************************************/ + +class CombinationIterator implements Iterator>, Serializable { + + private static final long serialVersionUID = 1L; + + private List _readings; + + private boolean _includeReflexive; + + private int _indexI = 0; + + private int _indexJ = 0; + + private Pair _next = null; + + public CombinationIterator(List readings, boolean includeReflexive) { + + _readings = readings; + _includeReflexive = includeReflexive; + + _indexI = 0; + _indexJ = _includeReflexive ? _indexI : _indexI + 1; + + tryNext(); + } + + public boolean hasNext() { + return _next != null; + } + + public Pair next() { + + if (!hasNext()) + throw new NoSuchElementException(); + + Pair n = _next; + tryNext(); + return n; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + private void tryNext() { + if (_indexI < _readings.size() && _indexJ < _readings.size()) { + _next = Tuples.pair(_readings.get(_indexI), _readings.get(_indexJ)); + _indexJ++; + if (_indexJ >= _readings.size()) { + _indexI++; + _indexJ = _includeReflexive ? _indexI : _indexI + 1; + } + } else { + _next = null; + } + } +} \ No newline at end of file diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/combinations/Combinations.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/combinations/Combinations.java new file mode 100644 index 000000000..bcb5206cd --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/combinations/Combinations.java @@ -0,0 +1,129 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections.combinations; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.onebusaway.collections.tuple.Pair; + +public abstract class Combinations { + + public static Iterable> getCombinationsReflexive( + Iterable objects) { + return getCombinations(objects, true); + } + + /** + * Given elements {a,b,c} will return {{a,b},{a,c},{b,c}}. + * + * @param + * @param objects + * @return + */ + public static Iterable> getCombinationsNonReflexive( + Iterable objects) { + return getCombinations(objects, false); + } + + public static Iterable> getCombinations(Iterable objects, + boolean includeReflexive) { + List elements = new ArrayList(); + for (T element : objects) + elements.add(element); + return new CombinationsIterable(elements, includeReflexive); + } + + public static Iterable> getPermutations(final Iterable objects) { + return new Iterable>() { + public Iterator> iterator() { + return new PermutationIterator(objects); + } + }; + } + + public static Iterable> getPermutations( + final Iterable objectsA, final Iterable objectsB) { + return new Iterable>() { + public Iterator> iterator() { + return new PermutationIterator(objectsA, objectsB); + } + }; + } + + public static Iterable> getSequentialPairs( + final Iterable objects) { + return new Iterable>() { + public Iterator> iterator() { + return new SequentialPairIterator(objects); + } + }; + } + + public static List> getGroupCombinations(List elements, + int groupSize) { + if (groupSize > elements.size()) + throw new IllegalStateException( + "group size is larger than number of available elements"); + List> lists = new ArrayList>(); + List current = new ArrayList(); + getGroupCombinations(elements, groupSize, 0, lists, current); + return lists; + } + + /******************************************************************************************************************* + * Private Methods + ******************************************************************************************************************/ + + private static void getGroupCombinations(List elements, int groupSize, + int index, List> lists, List current) { + + if (current.size() == groupSize) { + lists.add(current); + return; + } + + int g = groupSize - current.size(); + + for (int i = index; i < elements.size() - g + 1; i++) { + List c = new ArrayList(current.size() + 1); + c.addAll(current); + c.add(elements.get(i)); + getGroupCombinations(elements, groupSize, i + 1, lists, c); + } + } + + private static class CombinationsIterable implements Iterable>, + Serializable { + + private static final long serialVersionUID = 1L; + + private List _elements; + + private boolean _includeReflexive; + + public CombinationsIterable(List elements, boolean includeReflexive) { + _elements = elements; + _includeReflexive = includeReflexive; + } + + public Iterator> iterator() { + return new CombinationIterator(_elements, _includeReflexive); + } + } +} diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/combinations/PermutationIterator.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/combinations/PermutationIterator.java new file mode 100644 index 000000000..fe6e1769c --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/combinations/PermutationIterator.java @@ -0,0 +1,70 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + */ +package org.onebusaway.collections.combinations; + +import java.util.Iterator; + +import org.onebusaway.collections.tuple.Pair; +import org.onebusaway.collections.tuple.Tuples; + +class PermutationIterator implements Iterator> { + + private Iterable _elementsB; + + private Iterator _iteratorA; + + private Iterator _iteratorB; + + private T _elementA = null; + + public PermutationIterator(Iterable elements) { + this(elements, elements); + } + + public PermutationIterator(Iterable elementsA, Iterable elementsB) { + + _iteratorA = elementsA.iterator(); + _iteratorB = elementsB.iterator(); + + _elementsB = elementsB; + + if (_iteratorA.hasNext()) + _elementA = _iteratorA.next(); + } + + public boolean hasNext() { + return _iteratorA.hasNext() || _iteratorB.hasNext(); + } + + public Pair next() { + if (!hasNext()) + throw new IndexOutOfBoundsException(); + + if (!_iteratorB.hasNext()) { + _elementA = _iteratorA.next(); + _iteratorB = _elementsB.iterator(); + } + + return Tuples.pair(_elementA, _iteratorB.next()); + } + + public void remove() { + throw new UnsupportedOperationException(); + } +} \ No newline at end of file diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/combinations/SequentialPairIterator.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/combinations/SequentialPairIterator.java new file mode 100644 index 000000000..f54e13a1d --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/combinations/SequentialPairIterator.java @@ -0,0 +1,63 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections.combinations; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +import org.onebusaway.collections.tuple.Pair; +import org.onebusaway.collections.tuple.Tuples; + +public class SequentialPairIterator implements Iterator> { + + private T _prev = null; + + private T _current = null; + + private Iterator _iterator; + + public SequentialPairIterator(Iterable elements) { + _iterator = elements.iterator(); + if (_iterator.hasNext()) + _current = _iterator.next(); + getNext(); + } + + public boolean hasNext() { + return _current != null; + } + + public Pair next() { + if (!hasNext()) + throw new NoSuchElementException(); + Pair pair = Tuples.pair(_prev, _current); + getNext(); + return pair; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + private void getNext() { + _prev = _current; + if (_iterator.hasNext()) + _current = _iterator.next(); + else + _current = null; + } + +} diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/tuple/Pair.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/tuple/Pair.java new file mode 100644 index 000000000..73ecf7f62 --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/tuple/Pair.java @@ -0,0 +1,59 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections.tuple; + +import java.util.NoSuchElementException; + +/** + * An extension of the two-member {@link T2} tuple type where both members of the + * tuple are of the same type. See {@link Tuples#pair(Object, Object)} for a + * factory method to create a Pair object. + * + * @author bdferris + * @see Tuples#pair(Object, Object) + * @see PairImpl + */ +public interface Pair extends T2 { + + /** + * @return true if both members of the pair are equal + */ + public boolean isReflexive(); + + /** + * @param element the target element to test + * @return true if either member of the pair is equal to the target element + */ + public boolean contains(T element); + + /** + * Return {@link #getFirst()} if element equals {@link #getSecond()}, returns + * {@link #getSecond()} if element equals {@link #getFirst()}, and throws + * {@link NoSuchElementException} if element is equal to neither. + * + * @param element + * @return returns + * @throws NoSuchElementException if element is not equal to either member of + * the pair + */ + public T getOpposite(T element) throws NoSuchElementException; + + /** + * @return a Pair whose members are equal to this pair's, but have been + * swapped in order + */ + public Pair swap(); +} \ No newline at end of file diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/tuple/PairImpl.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/tuple/PairImpl.java new file mode 100644 index 000000000..31c37a955 --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/tuple/PairImpl.java @@ -0,0 +1,97 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections.tuple; + +import java.io.Serializable; +import java.util.NoSuchElementException; + +/** + * An implementation class for the {@link Pair} interface. To create an instance + * of {@link Pair}, please use the {@link Tuples#pair(Object, Object)} factory + * method. + * + * @author bdferris + * @see Pair + * @see Tuples#pair(Object, Object) + */ +final class PairImpl implements Pair, Serializable { + + private static final long serialVersionUID = 1L; + + private final T _first; + + private final T _second; + + public PairImpl(T first, T second) { + _first = first; + _second = second; + } + + public T getFirst() { + return _first; + } + + public T getSecond() { + return _second; + } + + public boolean isReflexive() { + return Tuples.equals(_first, _second); + } + + public boolean contains(T element) { + return Tuples.equals(_first, element) || Tuples.equals(_second, element); + } + + public T getOpposite(T element) { + if (Tuples.equals(_first, element)) + return _second; + if (Tuples.equals(_second, element)) + return _first; + throw new NoSuchElementException(); + } + + public PairImpl swap() { + return new PairImpl(_second, _first); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((_first == null) ? 0 : _first.hashCode()); + result = prime * result + ((_second == null) ? 0 : _second.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + PairImpl other = (PairImpl) obj; + return Tuples.equals(_first, other._first) + && Tuples.equals(_second, other._second); + } + + @Override + public String toString() { + return "Pair(" + _first + "," + _second + ")"; + } +} \ No newline at end of file diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/tuple/T2.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/tuple/T2.java new file mode 100644 index 000000000..0fbf8900a --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/tuple/T2.java @@ -0,0 +1,40 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections.tuple; + +/** + * A two-member member tuple class used to represent a collection of two objects + * with unique types. Useful, for example, in cases where you want to return + * more than one object reference from a method. See + * {@link Tuples#tuple(Object, Object)} for a factory method to create a T2 + * object. + * + * @author bdferris + * @see Tuples#tuple(Object, Object) + * @see T2Impl + */ +public interface T2 { + + /** + * @return the first member of the tuple collection + */ + public S1 getFirst(); + + /** + * @return the second member of the tuple collection + */ + public S2 getSecond(); +} \ No newline at end of file diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/tuple/T2Impl.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/tuple/T2Impl.java new file mode 100644 index 000000000..76813908b --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/tuple/T2Impl.java @@ -0,0 +1,77 @@ +/** + * Copyright (C) 2011 Brian Ferris + * Copyright (C) 2012 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections.tuple; + +import java.io.Serializable; + +/** + * An implementation class for the {@link T2} interface. To create an instance + * of {@link T2}, please use the {@link Tuples#tuple(Object, Object)} factory + * method. + * + * @author bdferris + * @see T2 + * @see Tuples#tuple(Object, Object) + */ +final class T2Impl implements T2, Serializable { + + private static final long serialVersionUID = 1L; + + private final S1 _first; + + private final S2 _second; + + public T2Impl(S1 first, S2 second) { + _first = first; + _second = second; + } + + public S1 getFirst() { + return _first; + } + + public S2 getSecond() { + return _second; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((_first == null) ? 0 : _first.hashCode()); + result = prime * result + ((_second == null) ? 0 : _second.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + T2Impl other = (T2Impl) obj; + return Tuples.equals(_first, other._first) + && Tuples.equals(_second, other._second); + } + + @Override + public String toString() { + return _first + ", " + _second; + } +} diff --git a/onebusaway-collections/src/main/java/org/onebusaway/collections/tuple/Tuples.java b/onebusaway-collections/src/main/java/org/onebusaway/collections/tuple/Tuples.java new file mode 100644 index 000000000..ba4793a5c --- /dev/null +++ b/onebusaway-collections/src/main/java/org/onebusaway/collections/tuple/Tuples.java @@ -0,0 +1,67 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections.tuple; + +/** + * A collection of factory methods for creating tuple objects. Tuples are typed + * ordered collection of objects where individual elements can have distinct + * types. We currently support a two-member tuple {@link T2} with distinct + * member types and sub-class {@link Pair} where both member types are the same. + * + * @author bdferris + * @see T2 + * @see Tuple + */ +public abstract class Tuples { + + private Tuples() { + + } + + /** + * A convenience factory method for constructing a {@link Pair} object. + * + * @param first the first member of the pair tuple + * @param second the second member of the pair tuple + * @return a new {@link Pair} object with the specified members + */ + public static Pair pair(T first, T second) { + return new PairImpl(first, second); + } + + /** + * A convenience factory method for constructing a {@link T2} object. + * + * @param first the first member of the tuple + * @param second the second member of the tuple + * @return a new {@link T2} object with the specified members + */ + public static T2 tuple(S1 first, S2 second) { + return new T2Impl(first, second); + } + + /** + * A convenience method to test for object equality that correctly handles + * null objects. + * + * @param a the first object to test for equality + * @param b the second object to test for equality + * @return true if (a == null && b == null) || (a.equals(b)) + */ + public static final boolean equals(Object a, Object b) { + return a == null ? (b == null) : (a.equals(b)); + } +} diff --git a/onebusaway-collections/src/test/java/org/onebusaway/collections/CollectionsLibraryTest.java b/onebusaway-collections/src/test/java/org/onebusaway/collections/CollectionsLibraryTest.java new file mode 100644 index 000000000..2343d3e48 --- /dev/null +++ b/onebusaway-collections/src/test/java/org/onebusaway/collections/CollectionsLibraryTest.java @@ -0,0 +1,39 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections; + +import static org.junit.Assert.assertEquals; + +import java.util.HashSet; +import java.util.Set; + +import org.junit.Test; + +public class CollectionsLibraryTest { + + @Test + public void testSet() { + + Set expected = new HashSet(); + expected.add("a"); + expected.add("b"); + expected.add("c"); + + Set actual = CollectionsLibrary.set("a", "b", "c", "a", "b"); + + assertEquals(expected, actual); + } +} diff --git a/onebusaway-collections/src/test/java/org/onebusaway/collections/ConcurrentCollectionsLibraryTest.java b/onebusaway-collections/src/test/java/org/onebusaway/collections/ConcurrentCollectionsLibraryTest.java new file mode 100644 index 000000000..efeff7d36 --- /dev/null +++ b/onebusaway-collections/src/test/java/org/onebusaway/collections/ConcurrentCollectionsLibraryTest.java @@ -0,0 +1,85 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.junit.Test; + +public class ConcurrentCollectionsLibraryTest { + + @Test + public void testAddToMapValueList() throws InterruptedException { + + ConcurrentMap> m = new ConcurrentHashMap>(); + + List ops = new ArrayList(); + ops.add(new AddOp(m, "a", "1")); + ops.add(new AddOp(m, "b", "1")); + ops.add(new AddOp(m, "a", "2")); + ops.add(new AddOp(m, "a", "1")); + ops.add(new AddOp(m, "b", "2")); + ops.add(new AddOp(m, "b", "3")); + ops.add(new AddOp(m, "a", "3")); + ops.add(new AddOp(m, "a", "3")); + ops.add(new AddOp(m, "a", "4")); + ops.add(new AddOp(m, "a", "5")); + ops.add(new AddOp(m, "a", "6")); + ops.add(new AddOp(m, "a", "7")); + + ExecutorService service = Executors.newFixedThreadPool(5); + service.invokeAll(ops); + + List values = m.get("a"); + assertEquals(7, values.size()); + assertTrue(values.contains("1")); + assertTrue(values.contains("2")); + assertTrue(values.contains("3")); + assertTrue(values.contains("4")); + assertTrue(values.contains("5")); + assertTrue(values.contains("6")); + assertTrue(values.contains("7")); + } + + private static class AddOp implements Callable { + + private ConcurrentMap> _map; + private String _key; + private String _value; + + public AddOp(ConcurrentMap> map, String key, + String value) { + _map = map; + _key = key; + _value = value; + } + + @Override + public String call() throws Exception { + ConcurrentCollectionsLibrary.addToMapValueList(_map, _key, _value); + return _value; + } + } +} diff --git a/onebusaway-collections/src/test/java/org/onebusaway/collections/FactoryMapTest.java b/onebusaway-collections/src/test/java/org/onebusaway/collections/FactoryMapTest.java new file mode 100644 index 000000000..43c8a3d24 --- /dev/null +++ b/onebusaway-collections/src/test/java/org/onebusaway/collections/FactoryMapTest.java @@ -0,0 +1,59 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; + +public class FactoryMapTest { + + @Test + public void test() { + + FactoryMap> m = new FactoryMap>( + new ArrayList()); + + List list = m.get("a"); + assertEquals(0, list.size()); + list.add("1"); + + list = m.get("b"); + assertEquals(0, list.size()); + list.add("1"); + + list = m.get("a"); + assertEquals(1, list.size()); + list.add("2"); + + list = m.get("b"); + assertEquals(1, list.size()); + assertEquals("1", list.get(0)); + + list = m.get("a"); + assertEquals(2, list.size()); + assertEquals("1", list.get(0)); + assertEquals("2", list.get(1)); + + m.remove("b"); + + list = m.get("b"); + assertEquals(0, list.size()); + } +} diff --git a/onebusaway-collections/src/test/java/org/onebusaway/collections/FunctionalLibraryTest.java b/onebusaway-collections/src/test/java/org/onebusaway/collections/FunctionalLibraryTest.java new file mode 100644 index 000000000..9b81d8ab4 --- /dev/null +++ b/onebusaway-collections/src/test/java/org/onebusaway/collections/FunctionalLibraryTest.java @@ -0,0 +1,75 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections; + +import static org.junit.Assert.*; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +public class FunctionalLibraryTest { + + @Test + public void test() { + + Dummy a = new Dummy("a"); + Dummy b = new Dummy("b"); + Dummy c = new Dummy("c"); + Dummy b2 = new Dummy("b"); + Dummy d = new Dummy("d"); + + List all = Arrays.asList(a, b, c, b2, d); + + List result = FunctionalLibrary.filter(all, "name", "a"); + assertEquals(1, result.size()); + assertSame(a, result.get(0)); + + result = FunctionalLibrary.filter(all, "name", "b"); + assertEquals(2, result.size()); + assertSame(b, result.get(0)); + assertSame(b2, result.get(1)); + + result = FunctionalLibrary.filter(all, "name", "f"); + assertEquals(0, result.size()); + + Dummy r = FunctionalLibrary.filterFirst(all, "name", "a"); + assertSame(a, r); + + r = FunctionalLibrary.filterFirst(all, "name", "b"); + assertSame(b, r); + + r = FunctionalLibrary.filterFirst(all, "name", "f"); + assertNull(r); + } + + public static class Dummy { + private String name; + + public Dummy(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } +} diff --git a/onebusaway-collections/src/test/java/org/onebusaway/collections/MappingLibraryTest.java b/onebusaway-collections/src/test/java/org/onebusaway/collections/MappingLibraryTest.java new file mode 100644 index 000000000..c79de0cce --- /dev/null +++ b/onebusaway-collections/src/test/java/org/onebusaway/collections/MappingLibraryTest.java @@ -0,0 +1,125 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections; + +import static org.junit.Assert.*; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.junit.Test; +import org.onebusaway.collections.tuple.Pair; +import org.onebusaway.collections.tuple.Tuples; + +public class MappingLibraryTest { + + @SuppressWarnings("unchecked") + @Test + public void testMap() { + + List> asList = Arrays.asList(Tuples.pair("a", "b"), + Tuples.pair("c", "d"), Tuples.pair("e", "f")); + + List values = MappingLibrary.map(asList, "first"); + assertEquals(3, values.size()); + assertEquals("a", values.get(0)); + assertEquals("c", values.get(1)); + assertEquals("e", values.get(2)); + + values = MappingLibrary.map(asList, "second"); + assertEquals(3, values.size()); + assertEquals("b", values.get(0)); + assertEquals("d", values.get(1)); + assertEquals("f", values.get(2)); + } + + @SuppressWarnings("unchecked") + @Test + public void testMapToValue() { + + Pair p1 = Tuples.pair("a", "1"); + Pair p2 = Tuples.pair("b", "1"); + Pair p3 = Tuples.pair("a", "2"); + List> asList = Arrays.asList(p1, p2, p3); + + Map> values = MappingLibrary.mapToValue(asList, + "first"); + assertEquals(2, values.size()); + assertEquals(p3, values.get("a")); + assertEquals(p2, values.get("b")); + + values = MappingLibrary.mapToValue(asList, "second"); + assertEquals(2, values.size()); + assertEquals(p2, values.get("1")); + assertEquals(p3, values.get("2")); + } + + @SuppressWarnings("unchecked") + @Test + public void testMapToValueList() { + + Pair p1 = Tuples.pair("a", "1"); + Pair p2 = Tuples.pair("b", "1"); + Pair p3 = Tuples.pair("a", "2"); + Pair p4 = Tuples.pair("b", "1"); + + List> asList = Arrays.asList(p1, p2, p3, p4); + + Map>> values = MappingLibrary.mapToValueList( + asList, "first"); + assertEquals(2, values.size()); + assertEquals(Arrays.asList(p1, p3), values.get("a")); + assertEquals(Arrays.asList(p2, p4), values.get("b")); + + values = MappingLibrary.mapToValueList(asList, "second"); + assertEquals(2, values.size()); + assertEquals(Arrays.asList(p1, p2, p4), values.get("1")); + assertEquals(Arrays.asList(p3), values.get("2")); + } + + @SuppressWarnings("unchecked") + @Test + public void testMapToValueSet() { + + Pair p1 = Tuples.pair("a", "1"); + Pair p2 = Tuples.pair("b", "1"); + Pair p3 = Tuples.pair("a", "2"); + Pair p4 = Tuples.pair("b", "1"); + List> asList = Arrays.asList(p1, p2, p3, p4); + + Map>> values = MappingLibrary.mapToValueSet( + asList, "first"); + assertEquals(2, values.size()); + assertEquals(set(p1, p3), values.get("a")); + assertEquals(set(p2), values.get("b")); + + values = MappingLibrary.mapToValueSet(asList, "second"); + assertEquals(2, values.size()); + assertEquals(set(p1, p2), values.get("1")); + assertEquals(set(p3), values.get("2")); + } + + private Set set(T... objects) { + Set set = new HashSet(); + for (T object : objects) + set.add(object); + return set; + } + +} diff --git a/onebusaway-collections/src/test/java/org/onebusaway/collections/MinTest.java b/onebusaway-collections/src/test/java/org/onebusaway/collections/MinTest.java new file mode 100644 index 000000000..97ea05799 --- /dev/null +++ b/onebusaway-collections/src/test/java/org/onebusaway/collections/MinTest.java @@ -0,0 +1,67 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.List; + +import org.junit.Test; + +public class MinTest { + + @Test + public void test() { + + Min m = new Min(); + + assertTrue(m.isEmpty()); + + m.add(1.0, "a"); + + assertEquals(1.0, m.getMinValue(), 0.0); + assertEquals("a", m.getMinElement()); + List els = m.getMinElements(); + assertEquals(1, els.size()); + assertTrue(els.contains("a")); + + m.add(2.0, "b"); + + assertEquals(1.0, m.getMinValue(), 0.0); + assertEquals("a", m.getMinElement()); + els = m.getMinElements(); + assertEquals(1, els.size()); + assertTrue(els.contains("a")); + + m.add(0.0, "c"); + + assertEquals(0.0, m.getMinValue(), 0.0); + assertEquals("c", m.getMinElement()); + els = m.getMinElements(); + assertEquals(1, els.size()); + assertTrue(els.contains("c")); + + m.add(0.0, "d"); + + assertEquals(0.0, m.getMinValue(), 0.0); + assertEquals("c", m.getMinElement()); + els = m.getMinElements(); + assertEquals(2, els.size()); + assertTrue(els.contains("c")); + assertTrue(els.contains("d")); + } +} diff --git a/onebusaway-collections/src/test/java/org/onebusaway/collections/adapter/AdaptableValueSortedMapTest.java b/onebusaway-collections/src/test/java/org/onebusaway/collections/adapter/AdaptableValueSortedMapTest.java new file mode 100644 index 000000000..d6153e7d0 --- /dev/null +++ b/onebusaway-collections/src/test/java/org/onebusaway/collections/adapter/AdaptableValueSortedMapTest.java @@ -0,0 +1,83 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections.adapter; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.SortedMap; +import java.util.TreeMap; + +import org.junit.Before; +import org.junit.Test; + +public class AdaptableValueSortedMapTest { + + private SortedMap _m2; + private TreeMap _m; + + @Before + public void before() { + + _m = new TreeMap(); + _m.put(1, new TestBean("a")); + _m.put(2, new TestBean("b")); + _m.put(3, new TestBean("c")); + + _m2 = AdapterLibrary.adaptSortedMap(_m, new ValueAdapter()); + } + + @Test + public void testClear() { + _m2.clear(); + assertTrue(_m2.isEmpty()); + assertTrue(_m.isEmpty()); + } + + @Test + public void testContainsKey() { + assertTrue(_m2.containsKey(1)); + assertFalse(_m2.containsKey(4)); + } + + @Test + public void testSubMap() { + SortedMap m = _m2.subMap(1, 2); + assertEquals(1, m.size()); + assertEquals(new Integer(1), m.firstKey()); + assertEquals(new Integer(1), m.lastKey()); + assertEquals("a", m.get(1)); + } + + private static class TestBean { + + private final String _value; + + public TestBean(String value) { + _value = value; + } + } + + private static class ValueAdapter implements IAdapter { + + @Override + public String adapt(TestBean source) { + return source._value; + } + + } +} diff --git a/onebusaway-collections/src/test/java/org/onebusaway/collections/beans/PropertyPathCollectionExpressionTest.java b/onebusaway-collections/src/test/java/org/onebusaway/collections/beans/PropertyPathCollectionExpressionTest.java new file mode 100644 index 000000000..df9b392ad --- /dev/null +++ b/onebusaway-collections/src/test/java/org/onebusaway/collections/beans/PropertyPathCollectionExpressionTest.java @@ -0,0 +1,99 @@ +/** + * Copyright (C) 2012 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections.beans; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; +import org.onebusaway.collections.beans.PropertyPathCollectionExpression; + +public class PropertyPathCollectionExpressionTest { + + @Test + public void test() { + TestObject a = new TestObject("a"); + TestObject b = new TestObject("b"); + TestObject c = new TestObject("c"); + TestObject d = new TestObject("d"); + a.setValues(Arrays.asList(b, c, d)); + b.setValues(Arrays.asList(c, a)); + c.setValues(Arrays.asList(d)); + + assertEquals(Arrays.asList("b", "c", "d"), + PropertyPathCollectionExpression.evaluate(a, "values.value")); + assertEquals(Arrays.asList("c", "a", "d"), + PropertyPathCollectionExpression.evaluate(a, "values.values.value")); + } + + @Test + public void testFullResult() { + TestObject a = new TestObject("a"); + TestObject b = new TestObject("b"); + a.setValues(Arrays.asList(b)); + + { + PropertyPathCollectionExpression expression = new PropertyPathCollectionExpression( + "values"); + List results = new ArrayList(); + expression.invokeReturningFullResult(a, results); + assertEquals(1, results.size()); + PropertyInvocationResult result = results.get(0); + assertSame(a, result.parent); + assertEquals("values", result.propertyName); + assertSame(b, result.value); + } + + { + PropertyPathCollectionExpression expression = new PropertyPathCollectionExpression( + "values.value"); + List results = new ArrayList(); + expression.invokeReturningFullResult(a, results); + assertEquals(1, results.size()); + PropertyInvocationResult result = results.get(0); + assertSame(b, result.parent); + assertEquals("value", result.propertyName); + assertSame("b", result.value); + } + } + + public static class TestObject { + + private String value; + + private List values = new ArrayList(); + + public TestObject(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public List getValues() { + return values; + } + + public void setValues(List values) { + this.values = values; + } + } +} diff --git a/onebusaway-collections/src/test/java/org/onebusaway/collections/beans/PropertyPathExpressionTest.java b/onebusaway-collections/src/test/java/org/onebusaway/collections/beans/PropertyPathExpressionTest.java new file mode 100644 index 000000000..e2a4d04aa --- /dev/null +++ b/onebusaway-collections/src/test/java/org/onebusaway/collections/beans/PropertyPathExpressionTest.java @@ -0,0 +1,156 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections.beans; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +import org.junit.Test; +import org.onebusaway.collections.beans.PropertyPathExpression; + +public class PropertyPathExpressionTest { + + @Test + public void test() { + + A obj = new A(); + + PropertyPathExpression expr = new PropertyPathExpression("stringValue"); + assertEquals("string-value", expr.invoke(obj)); + + expr = new PropertyPathExpression("integerValue"); + assertEquals(new Integer(31), expr.invoke(obj)); + + expr = new PropertyPathExpression("doubleValue"); + assertEquals(new Double(3.14), expr.invoke(obj)); + + expr = new PropertyPathExpression("nullA"); + assertNull(expr.invoke(obj)); + + expr = new PropertyPathExpression("depth"); + assertEquals(new Integer(0), expr.invoke(obj)); + + expr = new PropertyPathExpression("a.depth"); + assertEquals(new Integer(1), expr.invoke(obj)); + + expr = new PropertyPathExpression("a.a.depth"); + assertEquals(new Integer(2), expr.invoke(obj)); + + expr = new PropertyPathExpression("a.doubleValue"); + assertEquals(new Double(3.14), expr.invoke(obj)); + + expr = new PropertyPathExpression("dne"); + + try { + expr.invoke(obj); + fail(); + } catch (IllegalStateException ex) { + + } + + expr = new PropertyPathExpression("nullA.depth"); + + try { + expr.invoke(obj); + fail(); + } catch (IllegalStateException ex) { + + } + + assertEquals(new Double(3.14), + PropertyPathExpression.evaluate(obj, "a.doubleValue")); + + } + + @Test + public void testGetParentType() { + PropertyPathExpression exp = new PropertyPathExpression("a"); + assertEquals(A.class, exp.getParentType(A.class)); + + exp = new PropertyPathExpression("a.a"); + assertEquals(A.class, exp.getParentType(A.class)); + + exp = new PropertyPathExpression("b.a"); + assertEquals(B.class, exp.getParentType(A.class)); + + exp = new PropertyPathExpression("a.b.a"); + assertEquals(B.class, exp.getParentType(A.class)); + } + + public static class Base { + + private final int _depth; + + public Base() { + this(0); + } + + public Base(int depth) { + _depth = depth; + } + + public int getDepth() { + return _depth; + } + + public A getA() { + return new A(_depth + 1); + } + + public B getB() { + return new B(_depth + 1); + } + } + + public static class A extends Base { + + public A() { + super(); + } + + public A(int depth) { + super(depth); + } + + public String getStringValue() { + return "string-value"; + } + + public int getIntegerValue() { + return 31; + } + + public double getDoubleValue() { + return 3.14; + } + + public A getNullA() { + return null; + } + } + + public static class B extends Base { + + public B() { + super(); + } + + public B(int depth) { + super(depth); + } + } +} diff --git a/onebusaway-collections/src/test/java/org/onebusaway/collections/combinations/CombinationsTest.java b/onebusaway-collections/src/test/java/org/onebusaway/collections/combinations/CombinationsTest.java new file mode 100644 index 000000000..a23352bb2 --- /dev/null +++ b/onebusaway-collections/src/test/java/org/onebusaway/collections/combinations/CombinationsTest.java @@ -0,0 +1,79 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections.combinations; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import org.junit.Test; +import org.onebusaway.collections.tuple.Pair; +import org.onebusaway.collections.tuple.Tuples; + +public class CombinationsTest { + + @Test + public void testGetSequentialPairs() { + List values = Arrays.asList("a", "b", "c"); + Iterable> iterable = Combinations.getSequentialPairs(values); + Iterator> it = iterable.iterator(); + assertEquals(Tuples.pair("a", "b"), it.next()); + assertEquals(Tuples.pair("b", "c"), it.next()); + assertFalse(it.hasNext()); + } + + @Test + public void testCominationsReflexive() { + List values = Arrays.asList("a", "b", "c"); + Iterator> it = Combinations.getCombinationsReflexive(values).iterator(); + assertEquals(Tuples.pair("a", "a"), it.next()); + assertEquals(Tuples.pair("a", "b"), it.next()); + assertEquals(Tuples.pair("a", "c"), it.next()); + assertEquals(Tuples.pair("b", "b"), it.next()); + assertEquals(Tuples.pair("b", "c"), it.next()); + assertEquals(Tuples.pair("c", "c"), it.next()); + assertFalse(it.hasNext()); + } + + @Test + public void testCominationsNonReflexive() { + List values = Arrays.asList("a", "b", "c"); + Iterator> it = Combinations.getCombinationsNonReflexive(values).iterator(); + assertEquals(Tuples.pair("a", "b"), it.next()); + assertEquals(Tuples.pair("a", "c"), it.next()); + assertEquals(Tuples.pair("b", "c"), it.next()); + assertFalse(it.hasNext()); + } + + @Test + public void testPermutations() { + List values = Arrays.asList("a", "b", "c"); + Iterator> it = Combinations.getPermutations(values).iterator(); + assertEquals(Tuples.pair("a", "a"), it.next()); + assertEquals(Tuples.pair("a", "b"), it.next()); + assertEquals(Tuples.pair("a", "c"), it.next()); + assertEquals(Tuples.pair("b", "a"), it.next()); + assertEquals(Tuples.pair("b", "b"), it.next()); + assertEquals(Tuples.pair("b", "c"), it.next()); + assertEquals(Tuples.pair("c", "a"), it.next()); + assertEquals(Tuples.pair("c", "b"), it.next()); + assertEquals(Tuples.pair("c", "c"), it.next()); + assertFalse(it.hasNext()); + } +} diff --git a/onebusaway-collections/src/test/java/org/onebusaway/collections/tuple/PairImplTest.java b/onebusaway-collections/src/test/java/org/onebusaway/collections/tuple/PairImplTest.java new file mode 100644 index 000000000..c91510992 --- /dev/null +++ b/onebusaway-collections/src/test/java/org/onebusaway/collections/tuple/PairImplTest.java @@ -0,0 +1,156 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections.tuple; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.NoSuchElementException; + +import org.junit.Test; + +public class PairImplTest { + + @Test + public void testFirstAndSecond() { + + PairImpl p1 = new PairImpl("a", "b"); + assertEquals("a", p1.getFirst()); + assertEquals("b", p1.getSecond()); + + PairImpl p2 = new PairImpl(null, "b"); + assertEquals(null, p2.getFirst()); + assertEquals("b", p2.getSecond()); + + PairImpl p3 = new PairImpl("a", null); + assertEquals("a", p3.getFirst()); + assertEquals(null, p3.getSecond()); + + PairImpl p4 = new PairImpl(null, null); + assertEquals(null, p4.getFirst()); + assertEquals(null, p4.getSecond()); + } + + @Test + public void testContains() { + + PairImpl p1 = new PairImpl("a", "b"); + + assertTrue(p1.contains("a")); + assertTrue(p1.contains("b")); + assertFalse(p1.contains("c")); + assertFalse(p1.contains(null)); + + PairImpl p2 = new PairImpl(null, "b"); + + assertFalse(p2.contains("a")); + assertTrue(p2.contains("b")); + assertFalse(p2.contains("c")); + assertTrue(p2.contains(null)); + + PairImpl p3 = new PairImpl(null, null); + + assertFalse(p3.contains("a")); + assertFalse(p3.contains("b")); + assertFalse(p3.contains("c")); + assertTrue(p3.contains(null)); + } + + @Test + public void testGetOpposite() { + + PairImpl p1 = new PairImpl("a", "b"); + + assertEquals("b", p1.getOpposite("a")); + assertEquals("a", p1.getOpposite("b")); + + try { + p1.getOpposite("c"); + fail(); + } catch (NoSuchElementException ex) { + + } + + try { + p1.getOpposite(null); + fail(); + } catch (NoSuchElementException ex) { + + } + + PairImpl p2 = new PairImpl(null, "b"); + assertEquals("b", p2.getOpposite(null)); + assertEquals(null, p2.getOpposite("b")); + } + + @Test + public void testIsReflexibe() { + + PairImpl p1 = new PairImpl("a", "b"); + assertFalse(p1.isReflexive()); + + PairImpl p2 = new PairImpl("a", "a"); + assertTrue(p2.isReflexive()); + + PairImpl p3 = new PairImpl(null, "a"); + assertFalse(p3.isReflexive()); + + PairImpl p4 = new PairImpl("a", null); + assertFalse(p4.isReflexive()); + + PairImpl p5 = new PairImpl(null, null); + assertTrue(p5.isReflexive()); + } + + @Test + public void testSwap() { + PairImpl p1 = new PairImpl("a", "b"); + PairImpl p2 = p1.swap(); + assertEquals("b", p2.getFirst()); + assertEquals("a", p2.getSecond()); + + PairImpl p3 = new PairImpl(null, "b"); + PairImpl p4 = p3.swap(); + assertEquals("b", p4.getFirst()); + assertEquals(null, p4.getSecond()); + } + + @Test + public void testEquality() { + PairImpl p1 = new PairImpl("a", "b"); + PairImpl p2 = new PairImpl("a", "b"); + PairImpl p3 = new PairImpl(null, "b"); + PairImpl p4 = new PairImpl(null, "b"); + PairImpl p5 = new PairImpl(null, null); + PairImpl p6 = new PairImpl(null, null); + PairImpl p7 = new PairImpl("c", "b"); + + assertEquals(p1, p2); + assertFalse(p2.equals(p3)); + assertEquals(p3, p4); + assertFalse(p1.equals(p7)); + + assertFalse(p1.equals(null)); + assertFalse(p1.equals("a")); + assertEquals(p1, p1); + + assertEquals(p1.hashCode(), p2.hashCode()); + assertEquals(p3.hashCode(), p4.hashCode()); + assertEquals(p5.hashCode(), p6.hashCode()); + } +} diff --git a/onebusaway-collections/src/test/java/org/onebusaway/collections/tuple/T2ImplTest.java b/onebusaway-collections/src/test/java/org/onebusaway/collections/tuple/T2ImplTest.java new file mode 100644 index 000000000..3daf702a5 --- /dev/null +++ b/onebusaway-collections/src/test/java/org/onebusaway/collections/tuple/T2ImplTest.java @@ -0,0 +1,69 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.collections.tuple; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import org.junit.Test; + +public class T2ImplTest { + + @Test + public void testFirstAndSecond() { + + T2Impl p1 = new T2Impl("a", "b"); + assertEquals("a", p1.getFirst()); + assertEquals("b", p1.getSecond()); + + T2Impl p2 = new T2Impl(null, "b"); + assertEquals(null, p2.getFirst()); + assertEquals("b", p2.getSecond()); + + T2Impl p3 = new T2Impl("a", null); + assertEquals("a", p3.getFirst()); + assertEquals(null, p3.getSecond()); + + T2Impl p4 = new T2Impl(null, null); + assertEquals(null, p4.getFirst()); + assertEquals(null, p4.getSecond()); + } + + @Test + public void testEquality() { + T2Impl p1 = new T2Impl("a", "b"); + T2Impl p2 = new T2Impl("a", "b"); + T2Impl p3 = new T2Impl(null, "b"); + T2Impl p4 = new T2Impl(null, "b"); + T2Impl p5 = new T2Impl(null, null); + T2Impl p6 = new T2Impl(null, null); + T2Impl p7 = new T2Impl("c", "b"); + + assertEquals(p1, p1); + assertEquals(p1, p2); + assertFalse(p2.equals(p3)); + assertEquals(p3, p4); + assertFalse(p1.equals(p7)); + assertEquals(p5, p6); + assertFalse(p1.equals(null)); + assertFalse(p1.equals("a")); + + assertEquals(p1.hashCode(), p2.hashCode()); + assertEquals(p3.hashCode(), p4.hashCode()); + assertEquals(p5.hashCode(), p6.hashCode()); + + } +} diff --git a/onebusaway-csv-entities/README.md b/onebusaway-csv-entities/README.md new file mode 100644 index 000000000..7130329a8 --- /dev/null +++ b/onebusaway-csv-entities/README.md @@ -0,0 +1,3 @@ +# onebusaway-csv-entities + +Provides a library for reading and writing Java objects from CSV files. diff --git a/onebusaway-csv-entities/pom.xml b/onebusaway-csv-entities/pom.xml new file mode 100644 index 000000000..b8a85e459 --- /dev/null +++ b/onebusaway-csv-entities/pom.xml @@ -0,0 +1,71 @@ + + 4.0.0 + + + org.onebusaway + onebusaway-gtfs-modules + 5.0.1-openmove-1 + ../pom.xml + + + onebusaway-csv-entities + jar + + onebusaway-csv-entities + A Java library for reading and writing Java objects from comma-separated-values files. + https://github.com/OneBusAway/onebusaway-gtfs-modules/onebusaway-csv-entities/ + + + + commons-beanutils + commons-beanutils + 1.10.0 + + + org.slf4j + slf4j-api + ${slf4j.version} + + + junit + junit + 4.13.2 + test + + + org.mockito + mockito-core + 5.14.2 + test + + + + + + + com.google.cloud.tools + jib-maven-plugin + + + true + + + + + io.github.zlika + reproducible-build-maven-plugin + 0.17 + + + run-when-packaged + + strip-jar + + package + + + + + + + diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/CSVLibrary.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/CSVLibrary.java new file mode 100644 index 000000000..e607b0f15 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/CSVLibrary.java @@ -0,0 +1,133 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.List; + +/** + * Why do we have our own CSV reader when there are a couple of existing Java + * libraries? Mostly because we need to be able to handle some malformed CSV + * that most parsers would choke on but that agencies produce and google seems + * to validate as well. + * + * @author bdferris + * + */ +public class CSVLibrary { + + private final DelimitedTextParser _parser = new DelimitedTextParser(','); + + public static String escapeValue(String value) { + if (value.indexOf(',') != -1 || value.indexOf('"') != -1) + value = "\"" + value.replaceAll("\"", "\"\"") + "\""; + return value; + } + + public static String getArrayAsCSV(double[] args) { + StringBuilder csv = new StringBuilder(); + boolean seenFirst = false; + for (double v : args) { + if (seenFirst) + csv.append(','); + else + seenFirst = false; + csv.append(v); + } + return csv.toString(); + } + + public static String getArrayAsCSV(T[] args) { + StringBuilder csv = new StringBuilder(); + boolean seenFirst = false; + for (T v : args) { + if (seenFirst) + csv.append(','); + else + seenFirst = true; + csv.append(escapeValue(v.toString())); + } + return csv.toString(); + } + + public static String getIterableAsCSV(Iterable args) { + StringBuilder csv = new StringBuilder(); + boolean seenFirst = false; + for (T v : args) { + if (seenFirst) + csv.append(','); + else + seenFirst = true; + csv.append(escapeValue(v.toString())); + } + return csv.toString(); + } + + public static String getAsCSV(Object... args) { + StringBuilder csv = new StringBuilder(); + boolean seenFirst = false; + for (Object v : args) { + if (seenFirst) + csv.append(','); + else + seenFirst = true; + csv.append(escapeValue(v.toString())); + } + return csv.toString(); + } + + public void setTrimInitialWhitespace(boolean trimInitialWhitespace) { + _parser.setTrimInitialWhitespace(trimInitialWhitespace); + } + + public final void parse(InputStream is, CSVListener handler) throws Exception { + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + parse(reader, handler); + } + + public final void parse(File input, CSVListener handler) throws Exception { + BufferedReader r = new BufferedReader(new FileReader(input)); + parse(r, handler); + } + + public void parse(BufferedReader r, CSVListener handler) throws IOException, + Exception { + String line = null; + int lineNumber = 1; + + while ((line = r.readLine()) != null) { + List values = parse(line); + try { + handler.handleLine(values); + } catch (Exception ex) { + throw new Exception("error handling csv record for lineNumber=" + + lineNumber, ex); + } + lineNumber++; + } + + r.close(); + } + + public final List parse(String line) { + return _parser.parse(line); + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/CSVListener.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/CSVListener.java new file mode 100644 index 000000000..0282ad9ab --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/CSVListener.java @@ -0,0 +1,22 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities; + +import java.util.List; + +public interface CSVListener { + public void handleLine(List line) throws Exception; +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/CsvEntityContext.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/CsvEntityContext.java new file mode 100644 index 000000000..b5b81cab9 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/CsvEntityContext.java @@ -0,0 +1,23 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities; + +public interface CsvEntityContext { + + public Object put(Object key, Object value); + + public Object get(Object key); +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/CsvEntityContextImpl.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/CsvEntityContextImpl.java new file mode 100644 index 000000000..1d0f61c4e --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/CsvEntityContextImpl.java @@ -0,0 +1,32 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities; + +import java.util.HashMap; +import java.util.Map; + +public class CsvEntityContextImpl implements CsvEntityContext { + + private Map _params = new HashMap(); + + public Object get(Object key) { + return _params.get(key); + } + + public Object put(Object key, Object value) { + return _params.put(key, value); + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/CsvEntityReader.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/CsvEntityReader.java new file mode 100644 index 000000000..d4d18f774 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/CsvEntityReader.java @@ -0,0 +1,228 @@ +/** + * Copyright (C) 2011 Brian Ferris + * Copyright (C) 2011 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.ZipFile; + +import org.onebusaway.csv_entities.exceptions.CsvEntityIOException; +import org.onebusaway.csv_entities.exceptions.MissingRequiredEntityException; +import org.onebusaway.csv_entities.schema.DefaultEntitySchemaFactory; +import org.onebusaway.csv_entities.schema.EntitySchema; +import org.onebusaway.csv_entities.schema.EntitySchemaFactory; + +public class CsvEntityReader { + + public static final String KEY_CONTEXT = CsvEntityReader.class.getName() + + ".context"; + + private EntitySchemaFactory _entitySchemaFactory = new DefaultEntitySchemaFactory(); + + private EntityHandlerImpl _handler = new EntityHandlerImpl(); + + private CsvEntityContextImpl _context = new CsvEntityContextImpl(); + + private CsvInputSource _source; + + private TokenizerStrategy _tokenizerStrategy = new CsvTokenizerStrategy(); + + private List _handlers = new ArrayList(); + + private boolean _trimValues = false; + + private boolean _internStrings = false; + + private Map _stringTable = new HashMap(); + + /** + * @return the {@link EntitySchemaFactory} that will be used for introspection + * of bean classes + */ + public EntitySchemaFactory getEntitySchemaFactory() { + return _entitySchemaFactory; + } + + public void setEntitySchemaFactory(EntitySchemaFactory entitySchemaFactory) { + _entitySchemaFactory = entitySchemaFactory; + } + + public CsvInputSource getInputSource() { + return _source; + } + + public void setInputSource(CsvInputSource source) { + _source = source; + } + + public void setInputLocation(File path) throws IOException { + if (path.isDirectory()) + _source = new FileCsvInputSource(path); + else + _source = new ZipFileCsvInputSource(new ZipFile(path)); + } + + public void setTokenizerStrategy(TokenizerStrategy tokenizerStrategy) { + _tokenizerStrategy = tokenizerStrategy; + } + + public void setTrimValues(boolean trimValues) { + _trimValues = trimValues; + } + + public void addEntityHandler(EntityHandler handler) { + _handlers.add(handler); + } + + public CsvEntityContext getContext() { + return _context; + } + + public void setInternStrings(boolean internStrings) { + _internStrings = internStrings; + } + + public void readEntities(Class entityClass) throws IOException { + readEntities(entityClass, _source); + } + + public void readEntities(Class entityClass, CsvInputSource source) + throws IOException { + InputStream is = openInputStreamForEntityClass(source, entityClass); + if (is != null) + readEntities(entityClass, is); + } + + public void readEntities(Class entityClass, InputStream is) + throws IOException, CsvEntityIOException { + readEntities(entityClass, new InputStreamReader(is, "UTF-8")); + } + + public void readEntities(Class entityClass, Reader reader) + throws IOException, CsvEntityIOException { + + EntitySchema schema = _entitySchemaFactory.getSchema(entityClass); + + IndividualCsvEntityReader entityLoader = createIndividualCsvEntityReader( + _context, schema, _handler); + entityLoader.setTrimValues(_trimValues); + + BufferedReader lineReader = new BufferedReader(reader); + + /** + * Skip the initial UTF BOM, if present + */ + lineReader.mark(1); + int c = lineReader.read(); + + if (c != 0xFEFF) { + lineReader.reset(); + } + + String line = null; + int lineNumber = 1; + + try { + while ((line = lineReader.readLine()) != null) { + if (line.isEmpty()) + continue; + // TODO: This is a hack of sorts to deal with a malformed data file... + if (line.length() == 1 && line.charAt(0) == 26) + continue; + List values = _tokenizerStrategy.parse(line); + if (_internStrings) + internStrings(values); + entityLoader.handleLine(values); + lineNumber++; + } + } catch (Exception ex) { + throw new CsvEntityIOException(entityClass, schema.getFilename(), + lineNumber, ex); + } finally { + try { + lineReader.close(); + } catch (IOException ex) { + + } + } + } + + protected IndividualCsvEntityReader createIndividualCsvEntityReader( + CsvEntityContext context, EntitySchema schema, EntityHandler handler) { + return new IndividualCsvEntityReader(context, schema, handler); + } + + /** + * Sometimes it may be necessary to inject an instantiated entity directly + * instead of loading it from a CSV source. This method allows you to add a + * new entity, with all handlers called for that entity as if it had just been + * read from a source. + * + * @param entity the entity to be injected + */ + public void injectEntity(Object entity) { + _handler.handleEntity(entity); + } + + public InputStream openInputStreamForEntityClass(CsvInputSource source, + Class entityClass) throws IOException { + + EntitySchema schema = _entitySchemaFactory.getSchema(entityClass); + + String name = schema.getFilename(); + if (!_source.hasResource(name)) { + if (schema.isRequired()) + throw new MissingRequiredEntityException(entityClass, name); + return null; + } + + return _source.getResource(name); + } + + public void close() throws IOException { + if (_source != null) + _source.close(); + } + + private void internStrings(List values) { + for (int i = 0; i < values.size(); i++) { + String value = values.get(i); + String existing = _stringTable.get(value); + if (existing != null) { + values.set(i, existing); + } else { + _stringTable.put(value, value); + } + } + } + + private class EntityHandlerImpl implements EntityHandler { + + public void handleEntity(Object entity) { + for (EntityHandler handler : _handlers) + handler.handleEntity(entity); + } + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/CsvEntityWriter.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/CsvEntityWriter.java new file mode 100644 index 000000000..d1b4939ad --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/CsvEntityWriter.java @@ -0,0 +1,77 @@ +/** + * Copyright (C) 2011 Brian Ferris + * Copyright (C) 2011 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities; + +import java.io.File; +import java.io.IOException; + +import org.onebusaway.csv_entities.schema.DefaultEntitySchemaFactory; +import org.onebusaway.csv_entities.schema.EntitySchemaFactory; +import org.onebusaway.csv_entities.schema.ExcludeOptionalAndMissingEntitySchemaFactory; + +public class CsvEntityWriter implements EntityHandler { + + private EntitySchemaFactory _entitySchemaFactory = new DefaultEntitySchemaFactory(); + + private ExcludeOptionalAndMissingEntitySchemaFactory _excludeOptionalAndMissing = null; + + private CsvEntityContext _context = new CsvEntityContextImpl(); + + private OutputStrategy _outputStrategy = null; + + public EntitySchemaFactory getEntitySchemaFactory() { + return _entitySchemaFactory; + } + + public void setEntitySchemaFactory(EntitySchemaFactory entitySchemaFactory) { + _entitySchemaFactory = entitySchemaFactory; + } + + public void setOutputLocation(File path) { + if (path.getName().endsWith(".zip")) { + _outputStrategy = ZipOutputStrategy.create(path); + } else { + _outputStrategy = new FileOutputStrategy(path); + } + } + + public void excludeOptionalAndMissingFields(Class entityType, + Iterable entities) { + if (_excludeOptionalAndMissing == null) { + _excludeOptionalAndMissing = new ExcludeOptionalAndMissingEntitySchemaFactory( + _entitySchemaFactory); + } + _excludeOptionalAndMissing.scanEntities(entityType, entities); + } + + public void handleEntity(Object entity) { + Class entityType = entity.getClass(); + EntitySchemaFactory schemaFactory = _excludeOptionalAndMissing != null + ? _excludeOptionalAndMissing : _entitySchemaFactory; + IndividualCsvEntityWriter writer = _outputStrategy.getEntityWriter( + schemaFactory, _context, entityType); + writer.handleEntity(entity); + } + + public void flush() throws IOException { + _outputStrategy.flush(); + } + + public void close() throws IOException { + _outputStrategy.close(); + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/CsvEntityWriterFactory.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/CsvEntityWriterFactory.java new file mode 100644 index 000000000..2e9947b95 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/CsvEntityWriterFactory.java @@ -0,0 +1,61 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities; + +import java.io.PrintWriter; +import java.io.Writer; + +import org.onebusaway.csv_entities.schema.DefaultEntitySchemaFactory; +import org.onebusaway.csv_entities.schema.EntitySchema; +import org.onebusaway.csv_entities.schema.EntitySchemaFactory; + +public class CsvEntityWriterFactory { + + private EntitySchemaFactory _entitySchemaFactory = new DefaultEntitySchemaFactory(); + + private CsvEntityContext _context = new CsvEntityContextImpl(); + + private TokenizerStrategy _tokenizerStrategy = new CsvTokenizerStrategy(); + + public EntitySchemaFactory getEntitySchemaFactory() { + return _entitySchemaFactory; + } + + public void setEntitySchemaFactory(EntitySchemaFactory entitySchemaFactory) { + _entitySchemaFactory = entitySchemaFactory; + } + + public CsvEntityContext getContext() { + return _context; + } + + public void setContext(CsvEntityContext context) { + _context = context; + } + + public void setTokenizerStrategy(TokenizerStrategy tokenizerStrategy) { + _tokenizerStrategy = tokenizerStrategy; + } + + public EntityHandler createWriter(Class entityType, Writer writer) { + EntitySchema schema = _entitySchemaFactory.getSchema(entityType); + IndividualCsvEntityWriter entityWriter = new IndividualCsvEntityWriter( + _context, schema, new PrintWriter(writer)); + if (_tokenizerStrategy != null) + entityWriter.setTokenizerStrategy(_tokenizerStrategy); + return entityWriter; + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/CsvInputSource.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/CsvInputSource.java new file mode 100644 index 000000000..1f1a1f15e --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/CsvInputSource.java @@ -0,0 +1,25 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities; + +import java.io.IOException; +import java.io.InputStream; + +public interface CsvInputSource { + public boolean hasResource(String name) throws IOException; + public InputStream getResource(String name) throws IOException; + public void close() throws IOException; +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/CsvTokenizerStrategy.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/CsvTokenizerStrategy.java new file mode 100644 index 000000000..708fc89b0 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/CsvTokenizerStrategy.java @@ -0,0 +1,41 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities; + +import java.util.List; + +public class CsvTokenizerStrategy implements TokenizerStrategy { + + private CSVLibrary _csv = new CSVLibrary(); + + public CSVLibrary getCsvParser() { + return _csv; + } + + public void setCsvParser(CSVLibrary csv) { + _csv = csv; + } + + @Override + public List parse(String line) { + return _csv.parse(line); + } + + @Override + public String format(Iterable tokens) { + return CSVLibrary.getIterableAsCSV(tokens); + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/DelimitedTextParser.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/DelimitedTextParser.java new file mode 100644 index 000000000..95ab1e271 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/DelimitedTextParser.java @@ -0,0 +1,139 @@ +/** + * Copyright (C) 2011 Brian Ferris + * Copyright (C) 2013 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities; + +import java.util.ArrayList; +import java.util.List; + +/** + * Why do we have our own parser for CSV-like data when there are a couple of + * existing Java libraries? Mostly because we need to be able to handle some + * malformed CSV that most parsers would choke on but that agencies produce and + * google seems to validate as well. + * + * @author bdferris + * + */ +public class DelimitedTextParser { + + private enum EParseState { + TRIM_INIT_WHITESPACE, DATA, DATA_IN_QUOTES, END_QUOTE + } + + private final char _delimiter; + + private boolean _trimInitialWhitespace = false; + + public DelimitedTextParser(char delimiter) { + _delimiter = delimiter; + } + + public void setTrimInitialWhitespace(boolean trimInitialWhitespace) { + _trimInitialWhitespace = trimInitialWhitespace; + } + + public final List parse(String line) { + + StringBuilder token = new StringBuilder(); + List tokens = new ArrayList(); + if (line.length() > 0) + tokens.add(token); + + EParseState resetState = _trimInitialWhitespace + ? EParseState.TRIM_INIT_WHITESPACE : EParseState.DATA; + EParseState state = resetState; + + for (int i = 0; i < line.length(); i++) { + char c = line.charAt(i); + switch (state) { + case TRIM_INIT_WHITESPACE: + if (c == _delimiter) { + token = new StringBuilder(); + tokens.add(token); + } else { + switch (c) { + case ' ': + break; + case '"': + if (token.length() == 0) + state = EParseState.DATA_IN_QUOTES; + else + token.append(c); + break; + default: + state = EParseState.DATA; + token.append(c); + break; + } + } + break; + case DATA: + if (c == _delimiter) { + token = new StringBuilder(); + tokens.add(token); + state = resetState; + } else { + switch (c) { + case '"': + if (token.length() == 0) + state = EParseState.DATA_IN_QUOTES; + else + token.append(c); + break; + default: + token.append(c); + break; + } + } + break; + case DATA_IN_QUOTES: + switch (c) { + case '"': + state = EParseState.END_QUOTE; + break; + default: + token.append(c); + break; + } + break; + case END_QUOTE: + if (c == _delimiter) { + token = new StringBuilder(); + tokens.add(token); + state = resetState; + break; + } else { + switch (c) { + case '"': + token.append('"'); + state = EParseState.DATA_IN_QUOTES; + break; + default: + token.append(c); + state = EParseState.DATA; + break; + } + } + break; + } + } + List retro = new ArrayList(tokens.size()); + for (StringBuilder b : tokens) + retro.add(b.toString()); + return retro; + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/DelimiterTokenizerStrategy.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/DelimiterTokenizerStrategy.java new file mode 100644 index 000000000..3b2b6e579 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/DelimiterTokenizerStrategy.java @@ -0,0 +1,66 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities; + +import java.util.Arrays; +import java.util.List; + +public class DelimiterTokenizerStrategy implements TokenizerStrategy { + + private final String _delimiter; + + private boolean _replaceLiteralNullValues = false; + + public DelimiterTokenizerStrategy(String delimiter) { + _delimiter = delimiter; + } + + /** + * This is a bit of a hack + * + * @param replaceLiteralNullValues + */ + public void setReplaceLiteralNullValues(boolean replaceLiteralNullValues) { + _replaceLiteralNullValues = replaceLiteralNullValues; + } + + @Override + public List parse(String line) { + String[] tokens = line.split(_delimiter); + if (_replaceLiteralNullValues) { + for (int i = 0; i < tokens.length; ++i) { + String value = tokens[i].toLowerCase(); + if (value.equals("null")) + tokens[i] = ""; + } + } + return Arrays.asList(tokens); + } + + @Override + public String format(Iterable tokens) { + StringBuilder b = new StringBuilder(); + boolean seenFirst = false; + for (String token : tokens) { + if (seenFirst) + b.append(_delimiter); + else + seenFirst = true; + b.append(token); + } + return b.toString(); + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/EntityHandler.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/EntityHandler.java new file mode 100644 index 000000000..304e03ba7 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/EntityHandler.java @@ -0,0 +1,20 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities; + +public interface EntityHandler { + public void handleEntity(Object bean); +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/FileCsvInputSource.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/FileCsvInputSource.java new file mode 100644 index 000000000..74e1f72a9 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/FileCsvInputSource.java @@ -0,0 +1,44 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class FileCsvInputSource implements CsvInputSource { + + private File _sourceDirectory; + + public FileCsvInputSource(File sourceDirectory) { + _sourceDirectory = sourceDirectory; + } + + public boolean hasResource(String name) throws IOException { + File file = new File(_sourceDirectory, name); + return file.exists(); + } + + public InputStream getResource(String name) throws IOException { + File file = new File(_sourceDirectory, name); + return new FileInputStream(file); + } + + public void close() throws IOException { + + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/FileOutputStrategy.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/FileOutputStrategy.java new file mode 100644 index 000000000..e45544b00 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/FileOutputStrategy.java @@ -0,0 +1,86 @@ +/** + * Copyright (C) 2011 Brian Ferris + * Copyright (C) 2011 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.HashMap; +import java.util.Map; + +import org.onebusaway.csv_entities.exceptions.CsvEntityIOException; +import org.onebusaway.csv_entities.schema.EntitySchema; +import org.onebusaway.csv_entities.schema.EntitySchemaFactory; + +/** + * Implementation of {@link OutputStrategy} that supports writing entities to + * individual files within an output directory. + * + * @author bdferris + * + */ +class FileOutputStrategy implements OutputStrategy { + + private final File _outputDirectory; + + private Map, IndividualCsvEntityWriter> _writersByType = new HashMap, IndividualCsvEntityWriter>(); + + public FileOutputStrategy(File outputDirectory) { + _outputDirectory = outputDirectory; + } + + @Override + public IndividualCsvEntityWriter getEntityWriter( + EntitySchemaFactory entitySchemaFactory, CsvEntityContext context, + Class entityType) { + + IndividualCsvEntityWriter entityWriter = _writersByType.get(entityType); + if (entityWriter == null) { + EntitySchema schema = entitySchemaFactory.getSchema(entityType); + File outputFile = new File(_outputDirectory, schema.getFilename()); + + if (!_outputDirectory.exists()) + _outputDirectory.mkdirs(); + + PrintWriter writer = openOutput(outputFile, entityType); + entityWriter = new IndividualCsvEntityWriter(context, schema, writer); + _writersByType.put(entityType, entityWriter); + } + return entityWriter; + } + + @Override + public void flush() { + for (IndividualCsvEntityWriter writer : _writersByType.values()) + writer.flush(); + } + + @Override + public void close() throws IOException { + for (IndividualCsvEntityWriter writer : _writersByType.values()) + writer.close(); + } + + private PrintWriter openOutput(File outputFile, Class entityType) { + try { + return new PrintWriter(outputFile, "UTF-8"); + } catch (IOException ex) { + throw new CsvEntityIOException(entityType, outputFile.getAbsolutePath(), + 0, ex); + } + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/HasExtensions.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/HasExtensions.java new file mode 100644 index 000000000..201000f5d --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/HasExtensions.java @@ -0,0 +1,101 @@ +/** + * Copyright (C) 2013 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities; + +import org.onebusaway.csv_entities.schema.AbstractEntitySchemaFactoryImpl; +import org.onebusaway.csv_entities.schema.EntitySchemaFactory; + +/** + * Entity types that wish to support "extensions" should implement this type. + * + * Extensions allow users of a CSV-entity-based library to add custom fields to + * an existing entity type without modify the source of the base entity type. + * For example, consider a regular entity type named person: + * + *
{@code
+ * public class Person implements HasExtensions {
+ *   private int id;
+ *   private String name;
+ *   ...
+ * }
+ * }
+ * + * Now consider CSV data for such an entity with an additional custom field + * "age": + * + *
{@code
+ * id,name,age
+ * 1,Alice,27
+ * 2,Bob,45
+ * }
+ * + * Normally, to parse the age field, one would extend the Person + * entity. However, if the entity type is being distributed as part of a + * pre-compiled library and the user wants to extend it without modifying the + * base source code, they can use an extension type instead: + * + *
{@code
+ * public class PersonExtension {
+ *   private int age;
+ *   ...
+ * }
+ * }
+ * + * The extension type can be registered with your {@link EntitySchemaFactory} to + * indicate that the extension type should be used to process additional fields + * when reading or writing instances of the base entity type. + * + *
{@code
+ * DefaultEntitySchemaFactory factory = new DefaultEntitySchemaFactory();
+ * factory.addExtension(Person.class, PersonExtension.class);
+ *   
+ * CsvEntityReader reader = new CsvEntityReader();
+ * reader.setEntitySchemaFactory(factory);
+ * }
+ * + * Now when instances of Person are read, instances of + * PersonExtension will be created and read for each + * Person as well. These extension instances can be accessed via + * the methods of {@link HasExtensions}: + * + *
{@code
+ * Person person = ...
+ * PersonExtension extension = person.getExtension(PersonExtension.class);
+ * }
+ * + * See {@link AbstractEntitySchemaFactoryImpl#addExtension(Class, Class)} for + * more details on registering specific extension types. + * + * @author bdferris + */ +public interface HasExtensions { + + /** + * Add an extension object of the specified type. + * + * @param type + * @param extension + */ + public void putExtension(Class type, Object extension); + + /** + * + * @param type + * @return an extension object of the specified type, or null if none has been + * registered. + */ + public X getExtension(Class type); +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/IndividualCsvEntityReader.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/IndividualCsvEntityReader.java new file mode 100644 index 000000000..6ae2d5e95 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/IndividualCsvEntityReader.java @@ -0,0 +1,159 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + */ +package org.onebusaway.csv_entities; + +import org.onebusaway.csv_entities.exceptions.EntityInstantiationException; +import org.onebusaway.csv_entities.schema.BaseEntitySchema; +import org.onebusaway.csv_entities.schema.BeanWrapper; +import org.onebusaway.csv_entities.schema.BeanWrapperFactory; +import org.onebusaway.csv_entities.schema.EntitySchema; +import org.onebusaway.csv_entities.schema.EntityValidator; +import org.onebusaway.csv_entities.schema.ExtensionEntitySchema; +import org.onebusaway.csv_entities.schema.FieldMapping; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class IndividualCsvEntityReader implements CSVListener { + + private static Logger _log = LoggerFactory.getLogger(IndividualCsvEntityReader.class); + + private EntityHandler _handler; + + private CsvEntityContext _context; + + private EntitySchema _schema; + + private boolean _initialized = false; + + private List _fields; + + private int _line = 1; + + private boolean _verbose = false; + + private boolean _trimValues = false; + + public IndividualCsvEntityReader(CsvEntityContext context, + EntitySchema schema, EntityHandler handler) { + _handler = handler; + _context = context; + _schema = schema; + + List inOrder = _schema.getFieldsInOrder(); + if (!inOrder.isEmpty()) { + _initialized = true; + _fields = inOrder; + } + } + + public IndividualCsvEntityReader(EntityHandler handler, + CsvEntityContext context, EntitySchema schema, List fields) { + this(context, schema, handler); + _initialized = true; + _fields = fields; + } + + public void setVerbose(boolean verbose) { + _verbose = verbose; + } + + public void setTrimValues(boolean trimValues) { + _trimValues = trimValues; + } + + public void handleLine(List line) throws Exception { + + if (line.size() == 0) + return; + + if (_trimValues) { + for (int i = 0; i < line.size(); i++) + line.set(i, line.get(i).trim()); + } + + if (!_initialized) { + readSchema(line); + _initialized = true; + } else { + readEntity(line); + } + _line++; + if (_verbose && _line % 1000 == 0) + System.out.println("entities=" + _line); + } + + private void readSchema(List line) { + _fields = line; + } + + private void readEntity(List line) { + + if (line.size() != _fields.size()) { + _log.warn("expected and actual number of csv fields differ: type=" + + _schema.getEntityClass().getName() + " line # " + _line + + " expected=" + _fields.size() + " actual=" + line.size()); + while (line.size() < _fields.size()) + line.add(""); + } + + Object object = createNewEntityInstance(_schema); + BeanWrapper wrapper = BeanWrapperFactory.wrap(object); + + Map values = new HashMap(); + + for (int i = 0; i < line.size(); i++) { + String csvFieldName = _fields.get(i); + String value = line.get(i); + values.put(csvFieldName, value); + } + + for (FieldMapping mapping : _schema.getFields()) + mapping.translateFromCSVToObject(_context, values, wrapper); + + if (object instanceof HasExtensions) { + HasExtensions hasExtensions = (HasExtensions) object; + for (ExtensionEntitySchema extensionSchema : _schema.getExtensions()) { + Object extension = createNewEntityInstance(extensionSchema); + BeanWrapper extensionWrapper = BeanWrapperFactory.wrap(extension); + for (FieldMapping mapping : extensionSchema.getFields()) { + mapping.translateFromCSVToObject(_context, values, extensionWrapper); + } + hasExtensions.putExtension(extensionSchema.getEntityClass(), extension); + } + } + + for (EntityValidator validator : _schema.getValidators()) + validator.validateEntity(_context, values, wrapper); + + _handler.handleEntity(object); + } + + private static Object createNewEntityInstance(BaseEntitySchema schema) { + Class entityClass = schema.getEntityClass(); + try { + return entityClass.newInstance(); + } catch (Exception ex) { + throw new EntityInstantiationException(entityClass, ex); + } + } +} \ No newline at end of file diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/IndividualCsvEntityWriter.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/IndividualCsvEntityWriter.java new file mode 100644 index 000000000..400af867a --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/IndividualCsvEntityWriter.java @@ -0,0 +1,118 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + */ +package org.onebusaway.csv_entities; + +import org.onebusaway.csv_entities.schema.BeanWrapper; +import org.onebusaway.csv_entities.schema.BeanWrapperFactory; +import org.onebusaway.csv_entities.schema.EntitySchema; +import org.onebusaway.csv_entities.schema.ExtensionEntitySchema; +import org.onebusaway.csv_entities.schema.FieldMapping; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +class IndividualCsvEntityWriter implements EntityHandler { + + private final PrintWriter _writer; + + private final List _fieldNames = new ArrayList(); + + private final EntitySchema _schema; + + private final CsvEntityContext _context; + + private TokenizerStrategy _tokenizerStrategy = new CsvTokenizerStrategy(); + + private boolean _seenFirstRecord = false; + + public IndividualCsvEntityWriter(CsvEntityContext context, + EntitySchema schema, PrintWriter writer) { + _writer = writer; + _schema = schema; + _context = context; + + } + + public void setTokenizerStrategy(TokenizerStrategy tokenizerStrategy) { + _tokenizerStrategy = tokenizerStrategy; + } + + public void handleEntity(Object object) { + + if (!_seenFirstRecord) { + + _fieldNames.clear(); + for (FieldMapping field : _schema.getFields()) + field.getCSVFieldNames(_fieldNames); + + if (object instanceof HasExtensions) { + for (ExtensionEntitySchema extension : _schema.getExtensions()) { + for (FieldMapping field : extension.getFields()) { + field.getCSVFieldNames(_fieldNames); + } + } + } + + _writer.println(_tokenizerStrategy.format(_fieldNames)); + + _seenFirstRecord = true; + } + + BeanWrapper wrapper = BeanWrapperFactory.wrap(object); + Map csvValues = new HashMap<>(); + for (FieldMapping field : _schema.getFields()) { + field.translateFromObjectToCSV(_context, wrapper, csvValues); + } + if (object instanceof HasExtensions) { + HasExtensions hasExtensions = (HasExtensions) object; + for (ExtensionEntitySchema extensionSchema : _schema.getExtensions()) { + Object extension = hasExtensions.getExtension(extensionSchema.getEntityClass()); + if (extension != null) { + BeanWrapper extensionWrapper = BeanWrapperFactory.wrap(extension); + for (FieldMapping field : extensionSchema.getFields()) { + field.translateFromObjectToCSV(_context, extensionWrapper, + csvValues); + } + } + } + } + + List values = new ArrayList(csvValues.size()); + for (String fieldName : _fieldNames) { + Object value = csvValues.get(fieldName); + if (value == null) + value = ""; + values.add(value.toString()); + } + String line = _tokenizerStrategy.format(values); + _writer.println(line); + } + + public void flush() { + _writer.flush(); + } + + public void close() { + _writer.close(); + } + +} \ No newline at end of file diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/ListEntityHandler.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/ListEntityHandler.java new file mode 100644 index 000000000..906775e10 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/ListEntityHandler.java @@ -0,0 +1,44 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public class ListEntityHandler implements EntityHandler, Iterable { + + private List _values = new ArrayList(); + + public List getValues() { + return _values; + } + + @Override + public Iterator iterator() { + return _values.iterator(); + } + + /**** + * {@link EntityHandler} Interface + ****/ + + @SuppressWarnings("unchecked") + @Override + public void handleEntity(Object bean) { + _values.add((T) bean); + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/OutputStrategy.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/OutputStrategy.java new file mode 100644 index 000000000..1df7fd3ae --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/OutputStrategy.java @@ -0,0 +1,38 @@ +/** + * Copyright (C) 2011 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities; + +import java.io.IOException; + +import org.onebusaway.csv_entities.schema.EntitySchemaFactory; + +/** + * Generic strategy interface for creating {@link IndividualCsvEntityWriter} + * writers for outputting CSV entities. + * + * @author bdferris + * + */ +interface OutputStrategy { + + public IndividualCsvEntityWriter getEntityWriter( + EntitySchemaFactory entitySchemaFactory, CsvEntityContext context, + Class entityType); + + public void flush() throws IOException; + + public void close() throws IOException; +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/TokenizerStrategy.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/TokenizerStrategy.java new file mode 100644 index 000000000..28a9bf97a --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/TokenizerStrategy.java @@ -0,0 +1,23 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities; + +import java.util.List; + +public interface TokenizerStrategy { + public List parse(String line); + public String format(Iterable tokens); +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/ZipFileCsvInputSource.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/ZipFileCsvInputSource.java new file mode 100644 index 000000000..1bc5cec8b --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/ZipFileCsvInputSource.java @@ -0,0 +1,44 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities; + +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public class ZipFileCsvInputSource implements CsvInputSource { + + private ZipFile _zipFile; + + public ZipFileCsvInputSource(ZipFile zipFile) { + _zipFile = zipFile; + } + + public boolean hasResource(String name) throws IOException { + ZipEntry entry = _zipFile.getEntry(name); + return entry != null; + } + + public InputStream getResource(String name) throws IOException { + ZipEntry entry = _zipFile.getEntry(name); + return _zipFile.getInputStream(entry); + } + + public void close() throws IOException { + _zipFile.close(); + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/ZipOutputStrategy.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/ZipOutputStrategy.java new file mode 100644 index 000000000..fbc98d428 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/ZipOutputStrategy.java @@ -0,0 +1,125 @@ +/** + * Copyright (C) 2011 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.util.HashSet; +import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import org.onebusaway.csv_entities.exceptions.CsvException; +import org.onebusaway.csv_entities.schema.EntitySchema; +import org.onebusaway.csv_entities.schema.EntitySchemaFactory; + +/** + * Implementation of {@link OutputStrategy} that supports writing entities to + * entries within a Zip file. All entities of a particular type must be + * serialized at the same time. That is to say, writing a few entities of type + * A, then type B, and then type A again is not supported, since the Java + * {@link ZipOutputStream} does not support random-access output to different + * zip entries. + * + * @author bdferris + * + */ +class ZipOutputStrategy implements OutputStrategy { + + private final ZipOutputStream _out; + + private final PrintWriter _writer; + + private final Set> _typesWeHaveAlreadySeen = new HashSet>(); + + private Class _currentType = null; + + private IndividualCsvEntityWriter _currentWriter = null; + + public ZipOutputStrategy(ZipOutputStream out, PrintWriter writer) { + _out = out; + _writer = writer; + } + + public static ZipOutputStrategy create(File path) { + try { + ZipOutputStream out = new ZipOutputStream(new FileOutputStream(path)); + PrintWriter writer = new PrintWriter(new OutputStreamWriter(out, "UTF-8")); + return new ZipOutputStrategy(out, writer); + } catch (IOException ex) { + throw new CsvException("Error creating ZipOutputStrategy for path " + + path, ex); + } + } + + @Override + public IndividualCsvEntityWriter getEntityWriter( + EntitySchemaFactory entitySchemaFactory, CsvEntityContext context, + Class entityType) { + + if (_currentType != null && _currentType.equals(entityType)) { + return _currentWriter; + } + closeCurrentEntityWriter(); + if (!_typesWeHaveAlreadySeen.add(entityType)) { + throw new IllegalStateException( + "When writing to a ZIP output feed, entities cannot be written in arbitrary order " + + "but must be grouped by type. You have attempted to write an entity of type " + + entityType + + " but the zip entry for that type has already been closed."); + } + + _currentType = entityType; + EntitySchema schema = entitySchemaFactory.getSchema(entityType); + ZipEntry entry = new ZipEntry(schema.getFilename()); + try { + _out.putNextEntry(entry); + } catch (IOException ex) { + throw new CsvException("Error opening zip entry", ex); + } + + _currentWriter = new IndividualCsvEntityWriter(context, schema, _writer); + return _currentWriter; + } + + @Override + public void flush() throws IOException { + _out.flush(); + } + + @Override + public void close() throws IOException { + closeCurrentEntityWriter(); + _out.close(); + } + + private void closeCurrentEntityWriter() { + if (_currentType != null) { + try { + _writer.flush(); + _out.closeEntry(); + } catch (IOException ex) { + throw new CsvException("Error closing zip entry", ex); + } + _currentWriter = null; + _currentType = null; + } + } + +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/CsvEntityException.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/CsvEntityException.java new file mode 100644 index 000000000..947507d3f --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/CsvEntityException.java @@ -0,0 +1,51 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.exceptions; + +/** + * Extend from {@link Exception} or {@link RuntimeException}? The debate rages + * on, but I chose to extend from {@link RuntimeException} to maintain + * compatibility with existing method signatures and because most of the + * exceptions thrown here are non-recoverable. That is, you typically just log + * them and exit. + * + * @author bdferris + */ +public abstract class CsvEntityException extends CsvException { + + private static final long serialVersionUID = 1L; + + private final Class _entityType; + + public CsvEntityException(Class entityType, String message) { + super(message); + _entityType = entityType; + } + + public CsvEntityException(Class entityType, String message, Throwable cause) { + super(message, cause); + _entityType = entityType; + } + + public CsvEntityException(Class entityType, Throwable cause) { + super(cause); + _entityType = entityType; + } + + public Class getEntityType() { + return _entityType; + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/CsvEntityIOException.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/CsvEntityIOException.java new file mode 100644 index 000000000..ea2af61fd --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/CsvEntityIOException.java @@ -0,0 +1,49 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.exceptions; + +/** + * Indicates an error was thrown when reading / writing CSV. This exception + * provides information about line number and path information about where the + * error occurred. + * + * @author bdferris + * + */ +public class CsvEntityIOException extends CsvEntityException { + + private static final long serialVersionUID = 1L; + + private String _path; + + private int _lineNumber; + + public CsvEntityIOException(Class entityType, String path, int lineNumber, + Throwable cause) { + super(entityType, "io error: entityType=" + entityType.getName() + " path=" + + path + " lineNumber=" + lineNumber, cause); + _path = path; + _lineNumber = lineNumber; + } + + public String getPath() { + return _path; + } + + public int getLineNumber() { + return _lineNumber; + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/CsvException.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/CsvException.java new file mode 100644 index 000000000..dc97786ab --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/CsvException.java @@ -0,0 +1,42 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.exceptions; + +/** + * Extend from {@link Exception} or {@link RuntimeException}? The debate rages + * on, but I chose to extend from {@link RuntimeException} to maintain + * compatibility with existing method signatures and because most of the + * exceptions thrown here are non-recoverable. That is, you typically just log + * them and exit. + * + * @author bdferris + */ +public class CsvException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public CsvException(String message) { + super(message); + } + + public CsvException(String message, Throwable cause) { + super(message, cause); + } + + public CsvException(Throwable cause) { + super(cause); + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/EntityInstantiationException.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/EntityInstantiationException.java new file mode 100644 index 000000000..e09137870 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/EntityInstantiationException.java @@ -0,0 +1,32 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.exceptions; + +/** + * Indicates an error when attempting to instantiate an instance of the + * specified entity type + * + * @author bdferris + */ +public class EntityInstantiationException extends CsvEntityException { + + private static final long serialVersionUID = 1L; + + public EntityInstantiationException(Class entityType, Throwable cause) { + super(entityType, "error instantiating entity of type=" + + entityType.getName(), cause); + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/IntrospectionException.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/IntrospectionException.java new file mode 100644 index 000000000..c3404fa22 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/IntrospectionException.java @@ -0,0 +1,38 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.exceptions; + +import java.beans.Introspector; + +/** + * Indicates that introspection failed for the specified entity type. Usually + * indicates a failure with {@link Introspector#getBeanInfo(Class)}. + * + * @author bdferris + * @see Introspector#getBeanInfo(Class) + */ +public class IntrospectionException extends CsvEntityException { + + private static final long serialVersionUID = 1L; + + public IntrospectionException(Class entityType) { + super(entityType, "introspection error for type " + entityType); + } + + public IntrospectionException(Class entityType, Throwable cause) { + super(entityType, "introspection error for type " + entityType,cause); + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/InvalidValueEntityException.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/InvalidValueEntityException.java new file mode 100644 index 000000000..e3985a0f8 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/InvalidValueEntityException.java @@ -0,0 +1,48 @@ +/** + * Copyright (C) 2011 Brian Ferris + * Copyright (C) 2011 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.exceptions; + +/** + * Indicates that the value of the specified field for the specified entity type + * is invalid. + * + * @author bdferris + */ +public class InvalidValueEntityException extends CsvEntityException { + + private static final long serialVersionUID = 1L; + + private final String _fieldName; + + private final String _fieldValue; + + public InvalidValueEntityException(Class entityType, String fieldName, + String fieldValue) { + super(entityType, "invalid value \"" + fieldValue + "\" for field \"" + + fieldName + "\""); + _fieldName = fieldName; + _fieldValue = fieldValue; + } + + public String getFieldName() { + return _fieldName; + } + + public String getFieldValue() { + return _fieldValue; + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/MethodInvocationException.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/MethodInvocationException.java new file mode 100644 index 000000000..30215a411 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/MethodInvocationException.java @@ -0,0 +1,35 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.exceptions; + +import java.lang.reflect.Method; + +/** + * Indicates an error when attempting to invoke the specified method on an + * instance of the specified entity class + * + * @author bdferris + */ +public class MethodInvocationException extends CsvEntityException { + + private static final long serialVersionUID = 1L; + + public MethodInvocationException(Class entityType, Method method, + Exception ex) { + super(entityType, "error invoking method " + method + " for entityType " + + entityType.getName(), ex); + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/MissingRequiredEntityException.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/MissingRequiredEntityException.java new file mode 100644 index 000000000..9b3f7220f --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/MissingRequiredEntityException.java @@ -0,0 +1,46 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.exceptions; + +import org.onebusaway.csv_entities.schema.EntitySchema; +import org.onebusaway.csv_entities.schema.annotations.CsvFields; +import org.onebusaway.csv_entities.schema.beans.CsvEntityMappingBean; + +/** + * Indicates that the specified entity type is marked as required, but no input + * file or source was found for that entity. + * + * @author bdferris + * @see EntitySchema#isRequired() + * @see CsvFields#required() + * @see CsvEntityMappingBean#isRequired() + */ +public class MissingRequiredEntityException extends CsvEntityException { + + private static final long serialVersionUID = 1L; + + private String _fileName; + + public MissingRequiredEntityException(Class entityType, String fileName) { + super(entityType, "missing required entity: type=" + entityType.getName() + + " filename=" + fileName); + _fileName = fileName; + } + + public String getFileName() { + return _fileName; + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/MissingRequiredFieldException.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/MissingRequiredFieldException.java new file mode 100644 index 000000000..cfe1e3223 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/MissingRequiredFieldException.java @@ -0,0 +1,44 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.exceptions; + +import org.onebusaway.csv_entities.schema.annotations.CsvField; +import org.onebusaway.csv_entities.schema.beans.CsvFieldMappingBean; + +/** + * Indiciates that the specified field for the specified entity type is marked + * as required, but that no value was included in either the CSV source (just an + * empty value) or the entity object (null value). + * + * @author bdferris + * @see CsvField#optional() + * @see CsvFieldMappingBean#isOptional() + */ +public class MissingRequiredFieldException extends CsvEntityException { + + private static final long serialVersionUID = 1L; + + private String _fieldName; + + public MissingRequiredFieldException(Class entityType, String fieldName) { + super(entityType, "missing required field: " + fieldName); + _fieldName = fieldName; + } + + public String getFieldName() { + return _fieldName; + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/NoCsvFieldsAnnotationException.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/NoCsvFieldsAnnotationException.java new file mode 100644 index 000000000..596b62fa9 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/NoCsvFieldsAnnotationException.java @@ -0,0 +1,38 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.exceptions; + +import org.onebusaway.csv_entities.schema.AnnotationDrivenEntitySchemaFactory; +import org.onebusaway.csv_entities.schema.annotations.CsvFields; + +/** + * Indicates that an entity type was passed to + * {@link AnnotationDrivenEntitySchemaFactory} for introspection and the + * specified entity type did not have a {@link CsvFields} class annotation + * + * @author bdferris + * @see CsvFields + * @see AnnotationDrivenEntitySchemaFactory + */ +public class NoCsvFieldsAnnotationException extends CsvEntityException { + + private static final long serialVersionUID = 1L; + + public NoCsvFieldsAnnotationException(Class entityType) { + super(entityType, "No @CsvFields annotation found for entity type " + + entityType.getName()); + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/NoDefaultConverterException.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/NoDefaultConverterException.java new file mode 100644 index 000000000..9a1a0a0eb --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/NoDefaultConverterException.java @@ -0,0 +1,56 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.exceptions; + +import org.apache.commons.beanutils.ConvertUtils; + +/** + * Error indicating that no default converter could be found for converting CSV + * string data into the specified type for the target entity's specified field. + * We use the {@link ConvertUtils#lookup(Class)} method to find a converter. + * + * @author bdferris + * @see ConvertUtils + */ +public class NoDefaultConverterException extends CsvEntityException { + + private static final long serialVersionUID = 1L; + private final String _csvFieldName; + private final String _objFieldName; + private final Class _objFieldType; + + public NoDefaultConverterException(Class entityType, String csvFieldName, + String objFieldName, Class objFieldType) { + super(entityType, "no default converter found: entityType=" + + entityType.getName() + " csvField=" + csvFieldName + " objField=" + + objFieldName + " objType=" + objFieldType); + _csvFieldName = csvFieldName; + _objFieldName = objFieldName; + _objFieldType = objFieldType; + } + + public String getCsvFieldName() { + return _csvFieldName; + } + + public String getObjFieldName() { + return _objFieldName; + } + + public Class getObjFieldType() { + return _objFieldType; + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/NoSuchPropertyException.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/NoSuchPropertyException.java new file mode 100644 index 000000000..6e5ce343e --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/exceptions/NoSuchPropertyException.java @@ -0,0 +1,46 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.exceptions; + +/** + * Indicates that the specified entity type does not have a property with the + * given name, or that there was an error examining the property. + * + * @author bdferris + */ +public class NoSuchPropertyException extends CsvEntityException { + + private static final long serialVersionUID = 1L; + + private String _propertyName; + + public NoSuchPropertyException(Class entityType, String propertyName) { + super(entityType, "no such property \"" + propertyName + "\" for type " + + entityType.getName()); + _propertyName = propertyName; + } + + public NoSuchPropertyException(Class entityType, String propertyName, + Exception ex) { + super(entityType, "no such property \"" + propertyName + "\" for type " + + entityType.getName(), ex); + _propertyName = propertyName; + } + + public String getPropertyName() { + return _propertyName; + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/AbstractEntitySchemaFactoryImpl.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/AbstractEntitySchemaFactoryImpl.java new file mode 100644 index 000000000..cf9582669 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/AbstractEntitySchemaFactoryImpl.java @@ -0,0 +1,486 @@ +/** + * Copyright (C) 2011 Brian Ferris + * Copyright (C) 2012 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.onebusaway.csv_entities.HasExtensions; +import org.onebusaway.csv_entities.exceptions.EntityInstantiationException; +import org.onebusaway.csv_entities.schema.annotations.CsvField; +import org.onebusaway.csv_entities.schema.annotations.CsvFieldNameConvention; +import org.onebusaway.csv_entities.schema.annotations.CsvFields; +import org.onebusaway.csv_entities.schema.beans.CsvEntityMappingBean; +import org.onebusaway.csv_entities.schema.beans.CsvFieldMappingBean; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class AbstractEntitySchemaFactoryImpl implements + EntitySchemaFactory, ListableCsvMappingFactory { + + private static final Logger _log = LoggerFactory.getLogger(AbstractEntitySchemaFactoryImpl.class); + + private boolean _initialized = false; + + private Map, CsvEntityMappingBean> _mappingBeansByClass = new HashMap, CsvEntityMappingBean>(); + + private Map, List>> _extensionsByClass = new HashMap, List>>(); + + private Map, EntitySchema> _schemasByClass = new HashMap, EntitySchema>(); + + /** + * It can be useful to support the reading and writing of additional custom + * fields for a particular entity type without the need to modify the base + * entity type directly. We support this capability through "extensions": + * additional extension entity types, defining their own custom fields, that + * are associated with a base entity type and then processed along with the + * base entity when reading and writing data. Every time a base entity is + * read, an extension entity is also read, and associated with the base entity + * via the {@link HasExtensions} interface, which the base entity must + * implement in order to support extensions. + * + * @param type the base schema entity type to extend + * @param extensionType the extension type, with additional fields to read and + * write + */ + public void addExtension(Class type, + Class extensionType) { + List> extensionTypes = _extensionsByClass.get(type); + if (extensionTypes == null) { + extensionTypes = new ArrayList>(); + _extensionsByClass.put(type, extensionTypes); + } + extensionTypes.add(extensionType); + + _schemasByClass.remove(type); + } + + /**** + * {@link ListableCsvMappingFactory} Interface + ****/ + + public Collection getEntityMappings() { + initialize(); + return new ArrayList(_mappingBeansByClass.values()); + } + + /**** + * {@link EntitySchemaFactory} Interface + ****/ + + public EntitySchema getSchema(Class entityClass) { + + initialize(); + + EntitySchema schema = _schemasByClass.get(entityClass); + + if (schema == null) { + schema = createSchemaForEntityClass(entityClass); + _schemasByClass.put(entityClass, schema); + } + + return schema; + } + + /**** + * Protected Methods + ****/ + + protected abstract void processBeanDefinitions(); + + protected void registerBeanDefinition(CsvEntityMappingBean bean) { + CsvEntityMappingBean existingBean = _mappingBeansByClass.get(bean.getType()); + if (existingBean != null) { + CsvEntityMappingBean merged = new CsvEntityMappingBean(bean.getType()); + mergeBeans(existingBean, merged); + mergeBeans(bean, merged); + bean = merged; + } + _mappingBeansByClass.put(bean.getType(), bean); + } + + protected void applyCsvFieldsAnnotationToBean(Class entityClass, + CsvEntityMappingBean entityBean) { + + CsvFields csvFields = entityClass.getAnnotation(CsvFields.class); + + if (csvFields != null) { + entityBean.setFilename(csvFields.filename()); + if (!csvFields.prefix().equals("")) + entityBean.setPrefix(csvFields.prefix()); + if (csvFields.required()) + entityBean.setRequired(csvFields.required()); + String[] fieldsInOrder = csvFields.fieldOrder(); + if (fieldsInOrder.length != 0) { + for (String fieldInOrder : fieldsInOrder) + entityBean.addFieldInOrder(fieldInOrder); + } + if (csvFields.fieldNameConvention() != CsvFieldNameConvention.UNSPECIFIED) { + entityBean.setFieldNameConvention(csvFields.fieldNameConvention()); + } + } + } + + protected void applyCsvFieldAnnotationToBean(Field field, + CsvFieldMappingBean fieldBean) { + CsvField csvField = field.getAnnotation(CsvField.class); + + if (csvField != null) { + if (!csvField.name().equals("")) + fieldBean.setName(csvField.name()); + if (csvField.ignore()) + fieldBean.setIgnore(csvField.ignore()); + if (csvField.optional()) + fieldBean.setOptional(csvField.optional()); + if (csvField.alwaysIncludeInOutput()) { + fieldBean.setAlwaysIncludeInOutput(csvField.alwaysIncludeInOutput()); + } + if (csvField.order() != Integer.MAX_VALUE) + fieldBean.setOrder(csvField.order()); + if (!csvField.defaultValue().isEmpty()) { + fieldBean.setDefaultValue(csvField.defaultValue()); + } + + Class mapping = csvField.mapping(); + if (!mapping.equals(FieldMappingFactory.class)) { + try { + FieldMappingFactory factory = mapping.newInstance(); + fieldBean.setMapping(factory); + } catch (Exception ex) { + throw new EntityInstantiationException(mapping, ex); + } + } + } + } + + /**** + * Private Methods + ****/ + + private void initialize() { + if (!_initialized) { + processBeanDefinitions(); + _initialized = true; + } + } + + private void mergeBeans(CsvEntityMappingBean source, + CsvEntityMappingBean target) { + if (source.isFilenameSet()) + target.setFilename(source.getFilename()); + if (source.isPrefixSet()) + target.setPrefix(source.getPrefix()); + if (source.isRequiredSet()) + target.setRequired(source.isRequired()); + if (source.isAutoGenerateSchemaSet()) + target.setAutoGenerateSchema(source.isAutoGenerateSchema()); + + List fieldsInOrder = source.getFieldsInOrder(); + if (!fieldsInOrder.isEmpty()) + target.setFieldsInOrder(fieldsInOrder); + + for (FieldMapping mapping : source.getAdditionalFieldMappings()) + target.addAdditionalFieldMapping(mapping); + + Map sourceFields = source.getFields(); + Map targetFields = target.getFields(); + for (Map.Entry entry : sourceFields.entrySet()) { + Field sourceField = entry.getKey(); + CsvFieldMappingBean sourceFieldBean = entry.getValue(); + CsvFieldMappingBean targetFieldBean = targetFields.get(sourceField); + if (targetFieldBean == null) + targetFieldBean = sourceFieldBean; + else + mergeFields(sourceFieldBean, targetFieldBean); + targetFields.put(sourceField, targetFieldBean); + } + } + + private void mergeFields(CsvFieldMappingBean source, + CsvFieldMappingBean target) { + if (source.isNameSet()) + target.setName(source.getName()); + if (source.isIgnoreSet()) + target.setIgnore(target.isIgnore()); + if (source.isMappingSet()) + target.setMapping(source.getMapping()); + if (source.isOptionalSet()) + target.setOptional(source.isOptional()); + if (source.isAlwaysIncludeInOutput()) { + target.setAlwaysIncludeInOutput(source.isAlwaysIncludeInOutput()); + } + if (source.isOrderSet()) + target.setOrder(source.getOrder()); + if (source.getDefaultValue() != null) { + target.setDefaultValue(source.getDefaultValue()); + } + } + + private EntitySchema createSchemaForEntityClass(Class entityClass) { + CsvEntityMappingBean mappingBean = getMappingBeanForEntityType(entityClass); + + String name = getEntityClassAsEntityName(entityClass); + if (mappingBean.isFilenameSet()) + name = mappingBean.getFilename(); + + boolean required = false; + if (mappingBean.isRequiredSet()) + required = mappingBean.isRequired(); + + EntitySchema schema = new EntitySchema(entityClass, name, required); + + fillSchemaForEntityClass(entityClass, mappingBean, schema); + + List fieldsInOrder = mappingBean.getFieldsInOrder(); + if (!fieldsInOrder.isEmpty()) + schema.setFieldsInOrder(fieldsInOrder); + + List> extensionTypes = _extensionsByClass.get(entityClass); + if (extensionTypes != null) { + for (Class extensionType : extensionTypes) { + CsvEntityMappingBean extensionMappingBean = getMappingBeanForEntityType(extensionType); + ExtensionEntitySchema extensionSchema = new ExtensionEntitySchema( + extensionType); + fillSchemaForEntityClass(extensionType, extensionMappingBean, + extensionSchema); + schema.addExtension(extensionSchema); + } + } + + return schema; + } + + private CsvEntityMappingBean getMappingBeanForEntityType(Class entityClass) { + CsvEntityMappingBean mappingBean = _mappingBeansByClass.get(entityClass); + if (mappingBean == null) { + mappingBean = new CsvEntityMappingBean(entityClass); + applyCsvFieldsAnnotationToBean(entityClass, mappingBean); + } + return mappingBean; + } + + private void fillSchemaForEntityClass(Class entityClass, + CsvEntityMappingBean mappingBean, BaseEntitySchema schema) { + Map existingFieldBeans = mappingBean.getFields(); + List fieldMappings = new ArrayList(); + + String prefix = ""; + if (mappingBean.isPrefixSet()) + prefix = mappingBean.getPrefix(); + + CsvFieldNameConvention fieldNameConvention = CsvFieldNameConvention.UNSPECIFIED; + if (mappingBean.getFieldNameConvention() != null) + fieldNameConvention = mappingBean.getFieldNameConvention(); + if (fieldNameConvention == CsvFieldNameConvention.UNSPECIFIED) + fieldNameConvention = CsvFieldNameConvention.UNDERSCORE; + + boolean autoGenerateSchema = true; + if (mappingBean.isAutoGenerateSchemaSet()) + autoGenerateSchema = mappingBean.isAutoGenerateSchema(); + + if (autoGenerateSchema) { + Set remainingFields = new LinkedHashSet(); + for (Field field : entityClass.getDeclaredFields()) { + remainingFields.add(field); + } + // We add known fields first so that we can maintain field order. + for (Map.Entry entry : existingFieldBeans.entrySet()) { + Field field = entry.getKey(); + if (!remainingFields.remove(field)) { + _log.warn("field found in mapping but not in class: " + field); + continue; + } + addFieldMapping(entityClass, prefix, fieldNameConvention, field, + entry.getValue(), fieldMappings); + } + // We add any remaining fields next. + for (Field field : remainingFields) { + CsvFieldMappingBean fieldMappingBean = new CsvFieldMappingBean(field); + applyCsvFieldAnnotationToBean(field, fieldMappingBean); + + // Ignore static or final fields + boolean ignore = (field.getModifiers() & (Modifier.FINAL | Modifier.STATIC)) != 0; + if (ignore) + fieldMappingBean.setIgnore(ignore); + + addFieldMapping(entityClass, prefix, fieldNameConvention, field, + fieldMappingBean, fieldMappings); + } + } + + for (FieldMapping fieldMapping : mappingBean.getAdditionalFieldMappings()) + fieldMappings.add(fieldMapping); + + List sortableMappings = new ArrayList(); + List unsortableMappings = new ArrayList(); + for (FieldMapping fieldMapping : fieldMappings) { + if (fieldMapping.getOrder() == Integer.MAX_VALUE) { + unsortableMappings.add(fieldMapping); + } else { + sortableMappings.add(fieldMapping); + } + } + if (!sortableMappings.isEmpty()) { + Collections.sort(sortableMappings, new FieldMappingComparator()); + fieldMappings.clear(); + fieldMappings.addAll(sortableMappings); + fieldMappings.addAll(unsortableMappings); + } + + for (FieldMapping mapping : fieldMappings) + schema.addField(mapping); + + List validators = new ArrayList(); + validators.addAll(mappingBean.getValidators()); + + Collections.sort(validators, new ValidatorComparator()); + + for (EntityValidator validator : validators) + schema.addValidator(validator); + } + + private void addFieldMapping(Class entityClass, String prefix, + CsvFieldNameConvention fieldNameConvention, Field field, + CsvFieldMappingBean fieldMappingBean, List fieldMappings) { + if (fieldMappingBean.isIgnoreSet() && fieldMappingBean.isIgnore()) + return; + FieldMapping mapping = getFieldMapping(entityClass, field, + fieldMappingBean, prefix, fieldNameConvention); + fieldMappings.add(mapping); + } + + private FieldMapping getFieldMapping(Class entityClass, Field field, + CsvFieldMappingBean fieldMappingBean, String prefix, + CsvFieldNameConvention fieldNameConvention) { + + FieldMapping mapping = null; + + String objFieldName = field.getName(); + Class objFieldType = field.getType(); + + String csvFieldName = prefix + + getObjectFieldNameAsCSVFieldName(objFieldName, fieldNameConvention); + boolean required = true; + + if (fieldMappingBean.isOptionalSet()) + required = !fieldMappingBean.isOptional(); + + if (fieldMappingBean.isNameSet()) + csvFieldName = fieldMappingBean.getName(); + + if (fieldMappingBean.isMappingSet()) { + FieldMappingFactory factory = fieldMappingBean.getMapping(); + mapping = factory.createFieldMapping(this, entityClass, csvFieldName, + objFieldName, objFieldType, required); + } + + if (mapping == null) { + DefaultFieldMapping m = new DefaultFieldMapping(entityClass, + csvFieldName, objFieldName, objFieldType, required); + + mapping = m; + } + + if (mapping instanceof AbstractFieldMapping) { + AbstractFieldMapping fm = (AbstractFieldMapping) mapping; + if (fieldMappingBean.isOrderSet()) + fm.setOrder(fieldMappingBean.getOrder()); + if (fieldMappingBean.isAlwaysIncludeInOutputSet()) { + fm.setAlwaysIncludeInOutput(fieldMappingBean.isAlwaysIncludeInOutput()); + } + if (fieldMappingBean.getDefaultValue() != null) { + fm.setDefaultValue(fieldMappingBean.getDefaultValue()); + } + + try { + String name = field.getName(); + String isFieldSet = "is" + Character.toUpperCase(name.charAt(0)) + + name.substring(1) + "Set"; + + Method method = entityClass.getMethod(isFieldSet); + if (method != null + && (method.getReturnType() == Boolean.class || method.getReturnType() == Boolean.TYPE)) { + fm.setIsSetMethod(method); + } + } catch (Exception ex) { + // We ignore this + } + + } + + return mapping; + } + + private String getEntityClassAsEntityName(Class entityClass) { + String name = entityClass.getName(); + int index = name.lastIndexOf("."); + if (index != -1) + name = name.substring(index + 1); + return name; + } + + private String getObjectFieldNameAsCSVFieldName(String fieldName, + CsvFieldNameConvention fieldNameConvention) { + + if (fieldNameConvention == CsvFieldNameConvention.CAMEL_CASE) + return fieldName; + + if (fieldNameConvention == CsvFieldNameConvention.CAPITALIZED_CAMEL_CASE) { + return fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); + } + + StringBuilder b = new StringBuilder(); + boolean wasUpperCase = false; + + for (int i = 0; i < fieldName.length(); i++) { + char c = fieldName.charAt(i); + boolean isUpperCase = Character.isUpperCase(c); + if (isUpperCase) + c = Character.toLowerCase(c); + if (isUpperCase && !wasUpperCase) + b.append('_'); + b.append(c); + wasUpperCase = isUpperCase; + } + + return b.toString(); + } + + private static class FieldMappingComparator implements + Comparator { + public int compare(FieldMapping o1, FieldMapping o2) { + return o1.getOrder() - o2.getOrder(); + } + } + + private static class ValidatorComparator implements + Comparator { + public int compare(EntityValidator o1, EntityValidator o2) { + return o1.getOrder() - o2.getOrder(); + } + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/AbstractEntityValidator.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/AbstractEntityValidator.java new file mode 100644 index 000000000..ca2f70845 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/AbstractEntityValidator.java @@ -0,0 +1,42 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema; + +import java.util.Map; + +import org.onebusaway.csv_entities.CsvEntityContext; + +public class AbstractEntityValidator implements EntityValidator { + + private int _order = 0; + + public int getOrder() { + return _order; + } + + public void setOrder(int order) { + _order = order; + } + + public void validateCSV(CsvEntityContext context, BeanWrapper object, Map csvValues) { + + } + + public void validateEntity(CsvEntityContext context, Map csvValues, BeanWrapper object) { + + } + +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/AbstractFieldMapping.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/AbstractFieldMapping.java new file mode 100644 index 000000000..da3726310 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/AbstractFieldMapping.java @@ -0,0 +1,162 @@ +/** + * Copyright (C) 2011 Brian Ferris + * Copyright (C) 2011 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema; + +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Map; + +import org.onebusaway.csv_entities.exceptions.MethodInvocationException; +import org.onebusaway.csv_entities.exceptions.MissingRequiredFieldException; + +public abstract class AbstractFieldMapping implements SingleFieldMapping { + + protected final Class _entityType; + + protected final String _csvFieldName; + + protected final String _objFieldName; + + protected final boolean _required; + + protected int _order = Integer.MAX_VALUE; + + protected boolean _alwaysIncludeInOutput = false; + + protected String _defaultValue = null; + + protected Method _isSetMethod = null; + + public AbstractFieldMapping(Class entityType, String csvFieldName, + String objFieldName, boolean required) { + _entityType = entityType; + _csvFieldName = csvFieldName; + _objFieldName = objFieldName; + _required = required; + } + + public void setOrder(int order) { + _order = order; + } + + public void setDefaultValue(String defaultValue) { + _defaultValue = defaultValue; + } + + public void setAlwaysIncludeInOutput(boolean alwaysIncludeInOutput) { + _alwaysIncludeInOutput = alwaysIncludeInOutput; + } + + public void setIsSetMethod(Method isSetMethod) { + _isSetMethod = isSetMethod; + } + + /**** + * {@link SingleFieldMapping} + ****/ + + public String getCsvFieldName() { + return _csvFieldName; + } + + public String getObjFieldName() { + return _objFieldName; + } + + /*** + * {@link FieldMapping} Interface + ****/ + + @Override + public void getCSVFieldNames(Collection names) { + names.add(_csvFieldName); + } + + @Override + public int getOrder() { + return _order; + } + + @Override + public boolean isMissingAndOptional(Map csvValues) { + + boolean missing = isMissing(csvValues); + + if (_required && missing) + throw new MissingRequiredFieldException(_entityType, _csvFieldName); + + return missing; + } + + @Override + public boolean isMissingAndOptional(BeanWrapper object) { + boolean missing = isMissing(object); + + if (_required && missing) + throw new MissingRequiredFieldException(_entityType, _objFieldName); + + return missing; + } + + @Override + public boolean isAlwaysIncludeInOutput() { + return _alwaysIncludeInOutput; + } + + /**** + * Protected Methods + ****/ + + protected boolean isMissing(Map csvValues) { + return isMissing(csvValues, _csvFieldName); + } + + protected static boolean isMissing(Map csvValues, + String csvFieldName) { + return !(csvValues.containsKey(csvFieldName) && csvValues.get(csvFieldName).toString().length() > 0); + } + + protected boolean isMissing(BeanWrapper object) { + if (_isSetMethod != null) { + Object instance = object.getWrappedInstance(Object.class); + try { + Object r = _isSetMethod.invoke(instance); + if (r != null && r instanceof Boolean) { + Boolean b = (Boolean) r; + return !b.booleanValue(); + } + } catch (Exception ex) { + throw new MethodInvocationException(_entityType, _isSetMethod, ex); + } + } else { + Object obj = object.getPropertyValue(_objFieldName); + if (obj == null) { + return true; + } + if (_defaultValue != null && !_defaultValue.isEmpty()) { + return _defaultValue.equals(obj.toString()); + } + return (obj instanceof String && obj.toString().isEmpty()); + } + return false; + } + + protected boolean isOptional() { + return !_required; + } + +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/AnnotationDrivenEntitySchemaFactory.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/AnnotationDrivenEntitySchemaFactory.java new file mode 100644 index 000000000..7d4bf4f02 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/AnnotationDrivenEntitySchemaFactory.java @@ -0,0 +1,303 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema; + +import org.onebusaway.csv_entities.exceptions.NoCsvFieldsAnnotationException; +import org.onebusaway.csv_entities.schema.annotations.CsvFields; +import org.onebusaway.csv_entities.schema.beans.CsvEntityMappingBean; +import org.onebusaway.csv_entities.schema.beans.CsvFieldMappingBean; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +public class AnnotationDrivenEntitySchemaFactory extends AbstractEntitySchemaFactoryImpl { + + /** URL prefix for loading from the file system: "file:" */ + private static final String FILE_URL_PREFIX = "file:"; + + /** URL protocol for a file in the file system: "file" */ + private static final String URL_PROTOCOL_FILE = "file"; + + /** URL protocol for an entry from a jar file: "jar" */ + private static final String URL_PROTOCOL_JAR = "jar"; + + /** URL protocol for an entry from a zip file: "zip" */ + private static final String URL_PROTOCOL_ZIP = "zip"; + + /** URL protocol for an entry from a WebSphere jar file: "wsjar" */ + private static final String URL_PROTOCOL_WSJAR = "wsjar"; + + /** URL protocol for an entry from an OC4J jar file: "code-source" */ + private static final String URL_PROTOCOL_CODE_SOURCE = "code-source"; + + /** Separator between JAR URL and file path within the JAR */ + private static final String JAR_URL_SEPARATOR = "!/"; + + private final Logger _log = LoggerFactory.getLogger(AnnotationDrivenEntitySchemaFactory.class); + + private List _packagesToScan = new ArrayList(); + + private List> _classesToScan = new ArrayList>(); + + public void addPackageToScan(String packageToScan) { + _packagesToScan.add(packageToScan); + } + + public void addEntityClass(Class classToScan) { + _classesToScan.add(classToScan); + } + + @Override + protected void processBeanDefinitions() { + + for (Class entityClass : _classesToScan) { + CsvEntityMappingBean bean = getEntityMappingBeanForEntityClass(entityClass); + registerBeanDefinition(bean); + } + + try { + scanPackages(); + } catch (IOException ex) { + _log.warn("error scanning classpath for classes", ex); + } + } + + /**** + * Private Methods + ****/ + + private void go(String cName) { + try { + Class entityClass = Class.forName(cName); + CsvFields csvFields = entityClass.getAnnotation(CsvFields.class); + if (csvFields != null) { + CsvEntityMappingBean mappingBean = getEntityMappingBeanForEntityClass(entityClass); + registerBeanDefinition(mappingBean); + } + } catch (ClassNotFoundException ex) { + + } + } + + private CsvEntityMappingBean getEntityMappingBeanForEntityClass(Class entityClass) { + + CsvFields csvFields = entityClass.getAnnotation(CsvFields.class); + + if (csvFields == null) + throw new NoCsvFieldsAnnotationException(entityClass); + + CsvEntityMappingBean bean = new CsvEntityMappingBean(entityClass); + applyCsvFieldsAnnotationToBean(entityClass, bean); + + for (Field field : entityClass.getDeclaredFields()) { + + // Skip static final fields + if ((field.getModifiers() & (Modifier.FINAL | Modifier.STATIC)) != 0) + continue; + + CsvFieldMappingBean fieldBean = new CsvFieldMappingBean(field); + applyCsvFieldAnnotationToBean(field, fieldBean); + + bean.addField(fieldBean); + } + return bean; + } + + private void scanPackages() throws IOException { + + ClassLoader cl = AnnotationDrivenEntitySchemaFactory.class.getClassLoader(); + + for (String packageToScan : _packagesToScan) { + + if (packageToScan != null) { + + String pkg = packageToScan.replace('.', '/'); + + for (Enumeration en = cl.getResources(pkg); en.hasMoreElements();) { + URL url = en.nextElement(); + + if (isJarURL(url)) { + + URL jarURL = extractJarFileURL(url); + File jarPath = getFile(jarURL); + JarFile jar = new JarFile(jarPath); + + for (Enumeration en2 = jar.entries(); en2.hasMoreElements();) { + JarEntry entry = en2.nextElement(); + + String name = entry.getName(); + if (name.startsWith(pkg) && name.endsWith(".class")) { + String cName = name.replace(".class", "").replace('/', '.'); + go(cName); + } + } + } else { + String path = URLDecoder.decode(url.getPath(), "UTF-8"); + + String root = new File(path.replace(pkg, "")).getAbsolutePath(); + if (!root.endsWith(File.separator)) + root += File.separator; + + scanFile(root, new File(path)); + } + } + } + } + } + + private void scanFile(String root, File f) { + if (f.isDirectory()) { + File[] files = f.listFiles(); + if (files != null) { + for (File fChild : files) + scanFile(root, fChild); + } + } else if (f.getName().endsWith(".class")) { + String cName = f.getAbsolutePath().replace(root, "").replace(".class", "").replace('/', '.'); + go(cName); + } + } + + /*************************************************************************** + * Classpath URL Wrangling Methods as pulled from + * + * org.springframework.util.ResourceUtils + * + * in the Spring Framework + * + * Copyright 2002-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + **************************************************************************/ + + /** + * Determine whether the given URL points to a resource in a jar file, that + * is, has protocol "jar", "zip", "wsjar" or "code-source". + *

+ * "zip" and "wsjar" are used by BEA WebLogic Server and IBM WebSphere, + * respectively, but can be treated like jar files. The same applies to + * "code-source" URLs on Oracle OC4J, provided that the path contains a jar + * separator. + * + * @param url the URL to check + * @return whether the URL has been identified as a JAR URL + */ + private static boolean isJarURL(URL url) { + String protocol = url.getProtocol(); + return (URL_PROTOCOL_JAR.equals(protocol) || URL_PROTOCOL_ZIP.equals(protocol) + || URL_PROTOCOL_WSJAR.equals(protocol) || (URL_PROTOCOL_CODE_SOURCE.equals(protocol) && url.getPath().indexOf( + JAR_URL_SEPARATOR) != -1)); + } + + /** + * Extract the URL for the actual jar file from the given URL (which may point + * to a resource in a jar file or to a jar file itself). + * + * @param jarUrl the original URL + * @return the URL for the actual jar file + * @throws MalformedURLException if no valid jar file URL could be extracted + */ + private static URL extractJarFileURL(URL jarUrl) throws MalformedURLException { + String urlFile = jarUrl.getFile(); + int separatorIndex = urlFile.indexOf(JAR_URL_SEPARATOR); + if (separatorIndex != -1) { + String jarFile = urlFile.substring(0, separatorIndex); + try { + return new URL(jarFile); + } catch (MalformedURLException ex) { + // Probably no protocol in original jar URL, like + // "jar:C:/mypath/myjar.jar". + // This usually indicates that the jar file resides in the file + // system. + if (!jarFile.startsWith("/")) { + jarFile = "/" + jarFile; + } + return new URL(FILE_URL_PREFIX + jarFile); + } + } else { + return jarUrl; + } + } + + private static File getFile(URL resourceUrl) throws FileNotFoundException { + if (!URL_PROTOCOL_FILE.equals(resourceUrl.getProtocol())) { + throw new FileNotFoundException("url cannot be resolved to absolute file path " + + "because it does not reside in the file system: " + resourceUrl); + } + try { + return new File(toURI(resourceUrl).getSchemeSpecificPart()); + } catch (URISyntaxException ex) { + // Fallback for URLs that are not valid URIs (should hardly ever + // happen). + return new File(resourceUrl.getFile()); + } + } + + /** + * Create a URI instance for the given URL, replacing spaces with "%20" quotes + * first. + *

+ * Furthermore, this method works on JDK 1.4 as well, in contrast to the + * URL.toURI() method. + * + * @param url the URL to convert into a URI instance + * @return the URI instance + * @throws URISyntaxException if the URL wasn't a valid URI + * @see java.net.URL#toURI() + */ + private static URI toURI(URL url) throws URISyntaxException { + return toURI(url.toString()); + } + + /** + * Create a URI instance for the given location String, replacing spaces with + * "%20" quotes first. + * + * @param location the location String to convert into a URI instance + * @return the URI instance + * @throws URISyntaxException if the location wasn't a valid URI + */ + private static URI toURI(String location) throws URISyntaxException { + return new URI(location.replaceAll(" ", "%20")); + } + +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/BaseEntitySchema.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/BaseEntitySchema.java new file mode 100644 index 000000000..c1df1893e --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/BaseEntitySchema.java @@ -0,0 +1,70 @@ +/** + * Copyright (C) 2011 Brian Ferris + * Copyright (C) 2013 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema; + +import java.util.ArrayList; +import java.util.List; + +public class BaseEntitySchema { + + private List _fields = new ArrayList(); + + private List _validators = new ArrayList(); + + private Class _entityClass; + + private List _fieldsInOrder = new ArrayList(); + + public BaseEntitySchema(Class entityClass) { + _entityClass = entityClass; + } + + public BaseEntitySchema(BaseEntitySchema schema) { + _fields.addAll(schema._fields); + _validators.addAll(schema._validators); + _entityClass = schema._entityClass; + _fieldsInOrder.addAll(schema._fieldsInOrder); + } + + public void addField(FieldMapping field) { + _fields.add(field); + } + + public void addValidator(EntityValidator entityValidator) { + _validators.add(entityValidator); + } + + public Class getEntityClass() { + return _entityClass; + } + + public List getFields() { + return _fields; + } + + public List getValidators() { + return _validators; + } + + public void setFieldsInOrder(List fieldsInOrder) { + _fieldsInOrder = fieldsInOrder; + } + + public List getFieldsInOrder() { + return _fieldsInOrder; + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/BeanWrapper.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/BeanWrapper.java new file mode 100644 index 000000000..e1e720cff --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/BeanWrapper.java @@ -0,0 +1,50 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema; + + +public interface BeanWrapper { + + public Class getPropertyType(String propertyName); + + /** + * Get the current value of the specified property. + * + * @param propertyName the name of the property to get the value of (may be a + * nested path and/or an indexed/mapped property) + * @return the value of the property + * @throws InvalidPropertyException if there is no such property or if the + * property isn't readable + * @throws PropertyAccessException if the property was valid but the accessor + * method failed + */ + Object getPropertyValue(String propertyName); + + /** + * Set the specified value as current property value. + * + * @param propertyName the name of the property to set the value of (may be a + * nested path and/or an indexed/mapped property) + * @param value the new value + * @throws InvalidPropertyException if there is no such property or if the + * property isn't writable + * @throws PropertyAccessException if the property was valid but the accessor + * method failed or a type mismatch occured + */ + void setPropertyValue(String propertyName, Object value); + + public T getWrappedInstance(Class type); +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/BeanWrapperFactory.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/BeanWrapperFactory.java new file mode 100644 index 000000000..54fbaa61b --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/BeanWrapperFactory.java @@ -0,0 +1,124 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema; + +import java.beans.BeanInfo; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import org.onebusaway.csv_entities.exceptions.IntrospectionException; +import org.onebusaway.csv_entities.exceptions.MethodInvocationException; +import org.onebusaway.csv_entities.exceptions.NoSuchPropertyException; + +public class BeanWrapperFactory { + + private static Map, BeanClassWrapperImpl> _classWrappers = new HashMap, BeanClassWrapperImpl>(); + + public static BeanWrapper wrap(Object object) { + Class c = object.getClass(); + BeanClassWrapperImpl classWrapper = _classWrappers.get(c); + if (classWrapper == null) { + try { + BeanInfo beanInfo = java.beans.Introspector.getBeanInfo(c); + classWrapper = new BeanClassWrapperImpl(beanInfo); + _classWrappers.put(c, classWrapper); + } catch (Exception ex) { + throw new IntrospectionException(c); + } + } + + return new BeanWrapperImpl(classWrapper, object); + } + + private static class BeanClassWrapperImpl { + + private Map _readMethods = new HashMap(); + + private Map _writeMethods = new HashMap(); + + public BeanClassWrapperImpl(BeanInfo info) { + PropertyDescriptor[] properties = info.getPropertyDescriptors(); + for (PropertyDescriptor property : properties) { + String name = property.getName(); + _readMethods.put(name, property.getReadMethod()); + _writeMethods.put(name, property.getWriteMethod()); + } + } + + public Class getPropertyType(Object object, String propertyName) { + Method method = _readMethods.get(propertyName); + if (method == null) + throw new NoSuchPropertyException(object.getClass(), propertyName); + return method.getReturnType(); + } + + public Object getPropertyValue(Object object, String propertyName) { + Method method = _readMethods.get(propertyName); + if (method == null) + throw new NoSuchPropertyException(object.getClass(), propertyName); + try { + return method.invoke(object); + } catch (Exception ex) { + throw new MethodInvocationException(object.getClass(), method, ex); + } + } + + public void setPropertyValue(Object object, String propertyName, + Object value) { + Method method = _writeMethods.get(propertyName); + if (method == null) + throw new NoSuchPropertyException(object.getClass(), propertyName); + try { + method.invoke(object, value); + } catch (Exception ex) { + throw new MethodInvocationException(object.getClass(), method, ex); + } + } + } + + private static class BeanWrapperImpl implements BeanWrapper { + + private BeanClassWrapperImpl _classWrapper; + + private Object _wrappedInstance; + + public BeanWrapperImpl(BeanClassWrapperImpl classWrapper, + Object wrappedInstance) { + _classWrapper = classWrapper; + _wrappedInstance = wrappedInstance; + } + + @SuppressWarnings("unchecked") + public T getWrappedInstance(Class type) { + return (T) _wrappedInstance; + } + + public Class getPropertyType(String propertyName) { + return _classWrapper.getPropertyType(_wrappedInstance, propertyName); + } + + public Object getPropertyValue(String propertyName) { + return _classWrapper.getPropertyValue(_wrappedInstance, propertyName); + } + + public void setPropertyValue(String propertyName, Object value) { + _classWrapper.setPropertyValue(_wrappedInstance, propertyName, value); + } + + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/DateFieldMappingFactory.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/DateFieldMappingFactory.java new file mode 100644 index 000000000..aae15fa8d --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/DateFieldMappingFactory.java @@ -0,0 +1,146 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Field; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Map; + +import org.onebusaway.csv_entities.CsvEntityContext; +import org.onebusaway.csv_entities.exceptions.CsvEntityException; +import org.onebusaway.csv_entities.exceptions.IntrospectionException; + +public class DateFieldMappingFactory implements FieldMappingFactory { + + @Override + public FieldMapping createFieldMapping(EntitySchemaFactory schemaFactory, + Class entityType, String csvFieldName, String objFieldName, + Class objFieldType, boolean required) { + + Field field = null; + try { + field = entityType.getDeclaredField(objFieldName); + } catch (Exception ex) { + throw new IntrospectionException(entityType,ex); + } + + DateFormatAnnotation formatAnnotation = field.getAnnotation(DateFormatAnnotation.class); + + if (formatAnnotation == null) { + throw new DateFieldMappingException(entityType, + "missing required @DateFormatAnnotation for field " + objFieldName + + " of type " + entityType); + } + + boolean isLongType = false; + + if (objFieldType == Long.class || objFieldType == Long.TYPE) + isLongType = true; + else if (objFieldType != Date.class) + throw new DateFieldMappingException(entityType, "expected that field " + + objFieldName + " of type " + entityType + + " is Date or long, but instead was " + objFieldType); + + DateFormat dateFormat = new SimpleDateFormat(formatAnnotation.value()); + return new FieldMappingImpl(entityType, csvFieldName, objFieldName, + required, dateFormat, isLongType); + } + + @Retention(value = RetentionPolicy.RUNTIME) + @Target(value = ElementType.FIELD) + public @interface DateFormatAnnotation { + public String value(); + } + + public static class DateFieldMappingException extends CsvEntityException { + + private static final long serialVersionUID = 1L; + + public DateFieldMappingException(Class entityType, String message) { + super(entityType, message); + } + + public DateFieldMappingException(Class entityType, String message, + Throwable cause) { + super(entityType, message, cause); + } + } + + private static class FieldMappingImpl extends AbstractFieldMapping { + + private DateFormat _dateFormat; + private boolean _isLongType; + + public FieldMappingImpl(Class entityType, String csvFieldName, + String objFieldName, boolean required, DateFormat dateFormat, + boolean isLongType) { + super(entityType, csvFieldName, objFieldName, required); + _dateFormat = dateFormat; + _isLongType = isLongType; + } + + @Override + public void translateFromCSVToObject(CsvEntityContext context, + Map csvValues, BeanWrapper object) + throws CsvEntityException { + + if (isMissingAndOptional(csvValues)) + return; + + String dateAsString = (String) csvValues.get(_csvFieldName); + + try { + Date value = _dateFormat.parse(dateAsString); + + if (_isLongType) + object.setPropertyValue(_objFieldName, value.getTime()); + else + object.setPropertyValue(_objFieldName, value); + + } catch (ParseException e) { + throw new DateFieldMappingException(_entityType, + "error parsing data value " + dateAsString, e); + } + } + + @Override + public void translateFromObjectToCSV(CsvEntityContext context, + BeanWrapper object, Map csvValues) + throws CsvEntityException { + + if (isMissingAndOptional(object)) + return; + + Object obj = object.getPropertyValue(_objFieldName); + + Date date = null; + if (_isLongType) + date = new Date((Long) obj); + else + date = (Date) obj; + + String dateAsString = _dateFormat.format(date); + csvValues.put(_csvFieldName, dateAsString); + } + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/DecimalFieldMappingFactory.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/DecimalFieldMappingFactory.java new file mode 100644 index 000000000..8f73f6089 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/DecimalFieldMappingFactory.java @@ -0,0 +1,139 @@ +/** + * Copyright (C) 2011 Brian Ferris + * Copyright (C) 2011 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Field; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.NumberFormat; +import java.util.Locale; +import java.util.Map; + +import org.onebusaway.csv_entities.CsvEntityContext; +import org.onebusaway.csv_entities.exceptions.CsvEntityException; +import org.onebusaway.csv_entities.exceptions.IntrospectionException; + +public class DecimalFieldMappingFactory implements FieldMappingFactory { + + private String _format; + + private Locale _locale = Locale.US; + + public DecimalFieldMappingFactory() { + + } + + public DecimalFieldMappingFactory(String format) { + _format = format; + } + + public DecimalFieldMappingFactory(String format, Locale locale) { + _format = format; + _locale = locale; + } + + @Override + public FieldMapping createFieldMapping(EntitySchemaFactory schemaFactory, + Class entityType, String csvFieldName, String objFieldName, + Class objFieldType, boolean required) { + + NumberFormat numberFormat = getFormat(entityType, objFieldName); + + return new FieldMappingImpl(entityType, csvFieldName, objFieldName, + objFieldType, required, numberFormat); + } + + private NumberFormat getFormat(Class entityType, String objFieldName) { + String format = determineFormat(entityType, objFieldName); + if (_locale == null) { + return new DecimalFormat(format); + } else { + return new DecimalFormat(format, new DecimalFormatSymbols(_locale)); + } + } + + @Retention(value = RetentionPolicy.RUNTIME) + @Target(value = ElementType.FIELD) + public @interface NumberFormatAnnotation { + String value(); + } + + private String determineFormat(Class entityType, String objFieldName) { + if (_format != null) { + return _format; + } + + Field field = null; + try { + field = entityType.getDeclaredField(objFieldName); + } catch (Exception ex) { + throw new IntrospectionException(entityType, ex); + } + NumberFormatAnnotation formatAnnotation = field.getAnnotation(NumberFormatAnnotation.class); + + if (formatAnnotation == null) { + throw new DateFieldMappingException(entityType, + "missing required @DateFormatAnnotation for field " + objFieldName + + " of type " + entityType); + } + return formatAnnotation.value(); + } + + public static class DateFieldMappingException extends CsvEntityException { + + private static final long serialVersionUID = 1L; + + public DateFieldMappingException(Class entityType, String message) { + super(entityType, message); + } + + public DateFieldMappingException(Class entityType, String message, + Throwable cause) { + super(entityType, message, cause); + } + } + + private static class FieldMappingImpl extends DefaultFieldMapping { + + private NumberFormat _numberFormat; + + public FieldMappingImpl(Class entityType, String csvFieldName, + String objFieldName, Class objFieldType, boolean required, + NumberFormat numberFormat) { + super(entityType, csvFieldName, objFieldName, objFieldType, required); + _numberFormat = numberFormat; + } + + @Override + public void translateFromObjectToCSV(CsvEntityContext context, + BeanWrapper object, Map csvValues) + throws CsvEntityException { + + if (isMissingAndOptional(object)) + return; + + Number n = (Number) object.getPropertyValue(_objFieldName); + + String dateAsString = _numberFormat.format(n); + csvValues.put(_csvFieldName, dateAsString); + } + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/DefaultConverter.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/DefaultConverter.java new file mode 100644 index 000000000..a7a30a15c --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/DefaultConverter.java @@ -0,0 +1,28 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema; + +import org.apache.commons.beanutils.Converter; + +public class DefaultConverter implements Converter { + + @Override + public Object convert(@SuppressWarnings("rawtypes") Class type, Object value) { + if (value == null) + return ""; + return value.toString(); + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/DefaultEntitySchemaFactory.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/DefaultEntitySchemaFactory.java new file mode 100644 index 000000000..2e1eea88b --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/DefaultEntitySchemaFactory.java @@ -0,0 +1,79 @@ +/** + * Copyright (C) 2011 Brian Ferris + * Copyright (C) 2013 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema; + +import org.onebusaway.csv_entities.schema.beans.CsvEntityMappingBean; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class DefaultEntitySchemaFactory extends AbstractEntitySchemaFactoryImpl { + + private List _sources = new ArrayList(); + + public void addBean(CsvEntityMappingBean bean) { + _sources.add(new CsvEntityMappingBeanSource(bean)); + } + + public void addFactory(ListableCsvMappingFactory factory) { + _sources.add(new ListableCsvMappingFactorySource(factory)); + } + + /**** + * {@link AbstractEntitySchemaFactoryImpl} Interface + ****/ + + @Override + protected void processBeanDefinitions() { + for (BeanDefinitionSource source : _sources) + source.processBeanDefinitions(); + } + + private interface BeanDefinitionSource { + public void processBeanDefinitions(); + } + + private class CsvEntityMappingBeanSource implements BeanDefinitionSource { + + private CsvEntityMappingBean _bean; + + public CsvEntityMappingBeanSource(CsvEntityMappingBean bean) { + _bean = bean; + } + + public void processBeanDefinitions() { + registerBeanDefinition(_bean); + } + } + + private class ListableCsvMappingFactorySource implements BeanDefinitionSource { + + private ListableCsvMappingFactory _factory; + + public ListableCsvMappingFactorySource(ListableCsvMappingFactory factory) { + _factory = factory; + } + + public void processBeanDefinitions() { + Collection beans = _factory.getEntityMappings(); + for (CsvEntityMappingBean bean : beans) + registerBeanDefinition(bean); + } + } + +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/DefaultFieldMapping.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/DefaultFieldMapping.java new file mode 100644 index 000000000..4408a0fd2 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/DefaultFieldMapping.java @@ -0,0 +1,72 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema; + +import java.util.Map; + +import org.apache.commons.beanutils.ConvertUtils; +import org.apache.commons.beanutils.Converter; +import org.onebusaway.csv_entities.CsvEntityContext; +import org.onebusaway.csv_entities.exceptions.NoDefaultConverterException; + +public class DefaultFieldMapping extends AbstractFieldMapping { + + protected Class _objFieldType; + + private Converter _converter; + + public DefaultFieldMapping(Class entityType, String csvFieldName, + String objFieldName, Class objFieldType, boolean required) { + super(entityType, csvFieldName, objFieldName, required); + _objFieldType = objFieldType; + _converter = ConvertUtils.lookup(objFieldType); + if (_converter == null && objFieldType.equals(Object.class)) + _converter = new DefaultConverter(); + } + + public void translateFromCSVToObject(CsvEntityContext context, + Map csvValues, BeanWrapper object) { + + if (isMissingAndOptional(csvValues)) + return; + + Object csvValue = csvValues.get(_csvFieldName); + Object objValue = convertCsvValue(csvValue); + object.setPropertyValue(_objFieldName, objValue); + } + + public void translateFromObjectToCSV(CsvEntityContext context, + BeanWrapper object, Map csvValues) { + + if (isMissingAndOptional(object)) + return; + + Object objValue = object.getPropertyValue(_objFieldName); + csvValues.put(_csvFieldName, objValue); + } + + private Object convertCsvValue(Object csvValue) { + if (_converter != null) { + return _converter.convert(_objFieldType, csvValue); + } else if (csvValue != null + && _objFieldType.isAssignableFrom(csvValue.getClass())) { + return csvValue; + } else { + throw new NoDefaultConverterException(_entityType, _csvFieldName, + _objFieldName, _objFieldType); + } + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/EntitySchema.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/EntitySchema.java new file mode 100644 index 000000000..960675049 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/EntitySchema.java @@ -0,0 +1,57 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema; + +import java.util.ArrayList; +import java.util.List; + +public class EntitySchema extends BaseEntitySchema { + + private final String _filename; + + private final boolean _required; + + private List _extensions = new ArrayList(); + + public EntitySchema(Class entityClass, String filename, boolean required) { + super(entityClass); + _filename = filename; + _required = required; + } + + public EntitySchema(EntitySchema schema) { + super(schema); + _filename = schema._filename; + _required = schema._required; + _extensions = new ArrayList(schema._extensions); + } + + public String getFilename() { + return _filename; + } + + public boolean isRequired() { + return _required; + } + + public List getExtensions() { + return _extensions; + } + + public void addExtension(ExtensionEntitySchema extension) { + _extensions.add(extension); + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/EntitySchemaFactory.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/EntitySchemaFactory.java new file mode 100644 index 000000000..cc4936b7e --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/EntitySchemaFactory.java @@ -0,0 +1,22 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema; + +public interface EntitySchemaFactory { + + public EntitySchema getSchema(Class entityClass); + +} \ No newline at end of file diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/EntitySchemaFactoryHelper.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/EntitySchemaFactoryHelper.java new file mode 100644 index 000000000..0eae66cf0 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/EntitySchemaFactoryHelper.java @@ -0,0 +1,142 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema; + +import java.lang.reflect.Field; + +import org.onebusaway.csv_entities.exceptions.NoSuchPropertyException; +import org.onebusaway.csv_entities.schema.beans.CsvEntityMappingBean; +import org.onebusaway.csv_entities.schema.beans.CsvFieldMappingBean; + +public class EntitySchemaFactoryHelper { + + private DefaultEntitySchemaFactory _factory; + + public EntitySchemaFactoryHelper(DefaultEntitySchemaFactory factory) { + _factory = factory; + } + + public CsvEntityMappingBean addEntity(Class entityClass) { + CsvEntityMappingBean bean = new CsvEntityMappingBean(entityClass); + _factory.addBean(bean); + return bean; + } + + public CsvEntityMappingBean addEntity(Class entityClass, String filename) { + CsvEntityMappingBean bean = addEntity(entityClass); + bean.setFilename(filename); + return bean; + } + + public CsvEntityMappingBean addEntity(Class entityClass, String filename, String prefix) { + CsvEntityMappingBean bean = addEntity(entityClass, filename); + bean.setPrefix(prefix); + return bean; + } + + public CsvFieldMappingBean addField(CsvEntityMappingBean entityBean, String fieldName) { + Class entityClass = entityBean.getType(); + try { + Field field = entityClass.getDeclaredField(fieldName); + CsvFieldMappingBean fieldBean = new CsvFieldMappingBean(field); + entityBean.addField(fieldBean); + return fieldBean; + } catch (Exception ex) { + throw new NoSuchPropertyException(entityClass, fieldName,ex); + } + } + + public CsvFieldMappingBean[] addFields(CsvEntityMappingBean entityBean, String... fieldNames){ + CsvFieldMappingBean[] fields = new CsvFieldMappingBean[fieldNames.length]; + for( int i=0; i + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema; + +import java.util.Map; + +import org.onebusaway.csv_entities.CsvEntityContext; + +public interface EntityValidator { + + public int getOrder(); + + public void setOrder(int order); + + public void validateEntity(CsvEntityContext context, Map csvValues, BeanWrapper object); + + public void validateCSV(CsvEntityContext context, BeanWrapper object, Map csvValues); +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/EntityValidatorFactory.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/EntityValidatorFactory.java new file mode 100644 index 000000000..ec5fec0ef --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/EntityValidatorFactory.java @@ -0,0 +1,20 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema; + +public interface EntityValidatorFactory { + public EntityValidator createEntityValidator(EntitySchemaFactory schemaFactory); +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/EnumFieldMappingFactory.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/EnumFieldMappingFactory.java new file mode 100644 index 000000000..e7eccf547 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/EnumFieldMappingFactory.java @@ -0,0 +1,57 @@ +/** + * Copyright (C) 2011 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema; + +import java.util.Map; + +import org.onebusaway.csv_entities.CsvEntityContext; +import org.onebusaway.csv_entities.exceptions.CsvException; + +public class EnumFieldMappingFactory implements FieldMappingFactory { + + public FieldMapping createFieldMapping(EntitySchemaFactory schemaFactory, + Class entityType, String csvFieldName, String objFieldName, + Class objFieldType, boolean required) { + + if (!objFieldType.isEnum()) { + throw new CsvException("expected enum type but found " + objFieldType); + } + + return new FieldMappingImpl(entityType, csvFieldName, objFieldName, + objFieldType, required); + } + + private class FieldMappingImpl extends DefaultFieldMapping { + + public FieldMappingImpl(Class entityType, String csvFieldName, + String objFieldName, Class objFieldType, boolean required) { + super(entityType, csvFieldName, objFieldName, objFieldType, required); + } + + @Override + public void translateFromCSVToObject(CsvEntityContext context, + Map csvValues, BeanWrapper object) { + @SuppressWarnings("rawtypes") + Class objFieldType = _objFieldType; + if (isMissingAndOptional(csvValues)) + return; + String value = csvValues.get(_csvFieldName).toString(); + @SuppressWarnings("unchecked") + Object v = Enum.valueOf(objFieldType, value); + object.setPropertyValue(_objFieldName, v); + } + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/ExcludeOptionalAndMissingEntitySchemaFactory.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/ExcludeOptionalAndMissingEntitySchemaFactory.java new file mode 100644 index 000000000..3b8ee3ffa --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/ExcludeOptionalAndMissingEntitySchemaFactory.java @@ -0,0 +1,112 @@ +/** + * Copyright (C) 2012 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.onebusaway.csv_entities.HasExtensions; +import org.onebusaway.csv_entities.exceptions.MissingRequiredEntityException; + +public class ExcludeOptionalAndMissingEntitySchemaFactory implements + EntitySchemaFactory { + + private final EntitySchemaFactory _source; + + private Map, EntitySchema> _schemas = new HashMap, EntitySchema>(); + + public ExcludeOptionalAndMissingEntitySchemaFactory(EntitySchemaFactory source) { + _source = source; + } + + public void scanEntities(Class entityClass, Iterable entities) { + EntitySchema schema = _source.getSchema(entityClass); + if (schema == null) { + return; + } + schema = new EntitySchema(schema); + List fields = schema.getFields(); + for (Iterator it = fields.iterator(); it.hasNext();) { + FieldMapping field = it.next(); + if (!field.isAlwaysIncludeInOutput() && allValuesAreMissingAndOptional(field, entities)) { + it.remove(); + } + } + for (ExtensionEntitySchema extensionSchema : schema.getExtensions()) { + fields = extensionSchema.getFields(); + for (Iterator it = fields.iterator(); it.hasNext();) { + FieldMapping field = it.next(); + if (!field.isAlwaysIncludeInOutput() && allExtensionValuesAreMissingAndOptional(field, extensionSchema.getEntityClass(), entities)) { + it.remove(); + } + } + } + _schemas.put(entityClass, schema); + } + + /**** + * {@link EntitySchemaFactory} + ****/ + + @Override + public EntitySchema getSchema(Class entityClass) { + EntitySchema schema = _schemas.get(entityClass); + if (schema != null) { + return schema; + } + return _source.getSchema(entityClass); + } + + /**** + * Private Methods + ****/ + + private boolean allValuesAreMissingAndOptional(FieldMapping field, + Iterable entities) { + for (Object entity : entities) { + if (fieldIsNotMissingOrOptional(field, entity)) return false; + } + return true; + } + + private boolean allExtensionValuesAreMissingAndOptional(FieldMapping field, + Class extensionType, + Iterable entities) { + for (Object entity : entities) { + if (entity instanceof HasExtensions) { + Object extension = ((HasExtensions) entity).getExtension(extensionType); + if (extension != null) { + if (fieldIsNotMissingOrOptional(field, extension)) return false; + } + } + } + return true; + } + + private boolean fieldIsNotMissingOrOptional(FieldMapping field, Object entity) { + BeanWrapper wrapped = BeanWrapperFactory.wrap(entity); + try { + if (!field.isMissingAndOptional(wrapped)) { + return true; + } + } catch (MissingRequiredEntityException ex) { + return true; + } + return false; + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/ExtensionEntitySchema.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/ExtensionEntitySchema.java new file mode 100644 index 000000000..9e3671ff0 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/ExtensionEntitySchema.java @@ -0,0 +1,28 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema; + + +public class ExtensionEntitySchema extends BaseEntitySchema { + + public ExtensionEntitySchema(Class entityClass) { + super(entityClass); + } + + public ExtensionEntitySchema(ExtensionEntitySchema schema) { + super(schema); + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/FieldMapping.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/FieldMapping.java new file mode 100644 index 000000000..08d19532f --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/FieldMapping.java @@ -0,0 +1,43 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema; + +import java.util.Collection; +import java.util.Map; + +import org.onebusaway.csv_entities.CsvEntityContext; +import org.onebusaway.csv_entities.exceptions.CsvEntityException; + +public interface FieldMapping { + + public int getOrder(); + + public void getCSVFieldNames(Collection names); + + public void translateFromCSVToObject(CsvEntityContext context, + Map csvValues, BeanWrapper object) + throws CsvEntityException; + + public void translateFromObjectToCSV(CsvEntityContext context, + BeanWrapper object, Map csvValues) + throws CsvEntityException; + + public boolean isMissingAndOptional(Map csvValues); + + public boolean isMissingAndOptional(BeanWrapper object); + + public boolean isAlwaysIncludeInOutput(); +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/FieldMappingFactory.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/FieldMappingFactory.java new file mode 100644 index 000000000..e59ae4220 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/FieldMappingFactory.java @@ -0,0 +1,23 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema; + + +public interface FieldMappingFactory { + public FieldMapping createFieldMapping(EntitySchemaFactory schemaFactory, + Class entityType, String csvFieldName, String objFieldName, + Class objFieldType, boolean required); +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/FlattenFieldMapping.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/FlattenFieldMapping.java new file mode 100644 index 000000000..33b261cab --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/FlattenFieldMapping.java @@ -0,0 +1,73 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + */ +package org.onebusaway.csv_entities.schema; + +import java.util.Collection; +import java.util.Map; + +import org.onebusaway.csv_entities.CsvEntityContext; +import org.onebusaway.csv_entities.exceptions.EntityInstantiationException; + +class FlattenFieldMapping extends AbstractFieldMapping { + + private Class _objFieldType; + + private EntitySchema _schema; + + public FlattenFieldMapping(Class entityType, String csvFieldName, + String objFieldName, Class objFieldType, boolean required, + EntitySchema schema) { + super(entityType, csvFieldName, objFieldName, required); + _objFieldType = objFieldType; + _schema = schema; + } + + public void getCSVFieldNames(Collection names) { + for (FieldMapping mapping : _schema.getFields()) + mapping.getCSVFieldNames(names); + } + + public void translateFromCSVToObject(CsvEntityContext context, + Map csvValues, BeanWrapper object) { + + Object id = getInstance(_objFieldType); + BeanWrapper wrapper = BeanWrapperFactory.wrap(id); + for (FieldMapping mapping : _schema.getFields()) + mapping.translateFromCSVToObject(context, csvValues, wrapper); + object.setPropertyValue(_objFieldName, id); + } + + public void translateFromObjectToCSV(CsvEntityContext context, + BeanWrapper object, Map csvValues) { + if( isMissingAndOptional(object)) + return; + Object id = object.getPropertyValue(_objFieldName); + BeanWrapper wrapper = BeanWrapperFactory.wrap(id); + for (FieldMapping mapping : _schema.getFields()) + mapping.translateFromObjectToCSV(context, wrapper, csvValues); + } + + private Object getInstance(Class type) { + try { + return type.newInstance(); + } catch (Exception ex) { + throw new EntityInstantiationException(type, ex); + } + } +} \ No newline at end of file diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/FlattenFieldMappingFactory.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/FlattenFieldMappingFactory.java new file mode 100644 index 000000000..0e96d4dd2 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/FlattenFieldMappingFactory.java @@ -0,0 +1,26 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema; + +public class FlattenFieldMappingFactory implements FieldMappingFactory { + + public FieldMapping createFieldMapping(EntitySchemaFactory schemaFactory, Class entityType, String csvFieldName, + String objFieldName, Class objFieldType, boolean required) { + + EntitySchema schema = schemaFactory.getSchema(objFieldType); + return new FlattenFieldMapping(entityType, csvFieldName, objFieldName, objFieldType, required, schema); + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/ListableCsvMappingFactory.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/ListableCsvMappingFactory.java new file mode 100644 index 000000000..d45fd43fd --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/ListableCsvMappingFactory.java @@ -0,0 +1,24 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema; + +import org.onebusaway.csv_entities.schema.beans.CsvEntityMappingBean; + +import java.util.Collection; + +public interface ListableCsvMappingFactory { + public Collection getEntityMappings(); +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/SingleFieldMapping.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/SingleFieldMapping.java new file mode 100644 index 000000000..225d59587 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/SingleFieldMapping.java @@ -0,0 +1,38 @@ +/** + * Copyright (C) 2011 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema; + +/** + * Defines a mapping between a single column of a CSV table and a single field + * of a Java bean. + * + * @author bdferris + * + */ +public interface SingleFieldMapping extends FieldMapping { + + /** + * + * @return the CSV name of the mapped field. + */ + public String getCsvFieldName(); + + /** + * + * @return the Java bean name of the mapped field. + */ + public String getObjFieldName(); +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/annotations/CsvField.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/annotations/CsvField.java new file mode 100644 index 000000000..aa0c85aca --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/annotations/CsvField.java @@ -0,0 +1,120 @@ +/** + * Copyright (C) 2011 Brian Ferris + * Copyright (C) 2013 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.onebusaway.csv_entities.schema.FieldMapping; +import org.onebusaway.csv_entities.schema.FieldMappingFactory; + +/** + * Annotates a field of a CSV entity Java class definition, defining how the + * Java field is serialized to and from a corresponding CSV field. + * + * For example, a Java field is considered "required" by default, meaning it + * must have a corresponding value in a CSV file. However, you can mark the + * field as "optional" with an annotation. + * + *
+ * {@literal @}CsvField(optional = true)
+ * private String someField;
+ * 
+ * + * See the various fields defined below for more details on how you can control + * the CSV <=> Java field mapping process. + * + * @author bdferris + * @see CsvFields + */ +@Retention(value = RetentionPolicy.RUNTIME) +@Target(value = ElementType.FIELD) +public @interface CsvField { + /** + * If specified, the Java field value will be mapped from a CSV field with the + * specified name (as opposed to the default name automatically computed from + * the Java field name). + * + * @return + * @see CsvFieldNameConvention + * @see CsvFields#fieldNameConvention() + * @see CsvFields#prefix() + */ + String name() default ""; + + /** + * If true, the specified Java field will be ignored when mapping the object + * to and from CSV values. + * + * @return + */ + boolean ignore() default false; + + /** + * Specify a {@link FieldMappingFactory} class that will be used to construct + * a {@link FieldMapping} instance for mapping this Java field value to and + * from CSV values. The factory class must have a default constructor. + * + * @return + */ + Class mapping() default FieldMappingFactory.class; + + /** + * If false (the default case), a field is considered required and an error + * will be thrown if the field is missing in either the CSV or Java object + * when converting between the two. If true, then the field is considered + * optional and can be missing in the CSV or Java object. + * + * @return + */ + boolean optional() default false; + + /** + * Determines the order in which fields are processed, where fields with a + * smaller order value are processed first. When it is necessary to process + * one field before another (possibly due to inter-field dependencies), this + * override can be useful for setting the relative processing order of two + * fields. + * + * @return + */ + int order() default Integer.MAX_VALUE; + + /** + * Determines the default value for a field. Some CSV fields assume a default + * value when no value is specified in the CSV. By defining that default value + * for the field, we can automatically generate an empty value (or possibly + * exclude an entire column) when writing output CSV if all Java fields have + * the default value. + * + * Note: this does NOT set a default value in the Java object when the Csv + * field value is empty. + * + * @return + */ + String defaultValue() default ""; + + /** + * Determines if a field should always be included in CSV output, even if the + * Java field value is missing or matches the default empty value. + * + * @return + */ + boolean alwaysIncludeInOutput() default false; +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/annotations/CsvFieldNameConvention.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/annotations/CsvFieldNameConvention.java new file mode 100644 index 000000000..911270921 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/annotations/CsvFieldNameConvention.java @@ -0,0 +1,47 @@ +/** + * Copyright (C) 2011 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema.annotations; + +/** + * Control how object field names are converted to CSV column header names. + * + * @author bdferris + * + */ +public enum CsvFieldNameConvention { + + /** + * The field name conversion is left unspecified. This is used as a default + * value for {@link CsvFields#fieldNameConvention()} and typically means the + * default behavior will be used: {@link #UNDERSCORE}. + */ + UNSPECIFIED, + + /** + * A field name like "thisIsTheName" is converted to "this_is_the_name". + */ + UNDERSCORE, + + /** + * A field name like "thisIsTheName" is left as "thisIsTheName". + */ + CAMEL_CASE, + + /** + * A field name like "thisIsTheName" is converted to "ThisIsTheName". + */ + CAPITALIZED_CAMEL_CASE +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/annotations/CsvFields.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/annotations/CsvFields.java new file mode 100644 index 000000000..67235866d --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/annotations/CsvFields.java @@ -0,0 +1,80 @@ +/** + * Copyright (C) 2011 Brian Ferris + * Copyright (C) 2013 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotates a CSV entity Java class definition, control how entities of the + * specified type are serialized to and from a CSV file. + * + * @author bdferris + * @see CsvField + */ +@Retention(value = RetentionPolicy.RUNTIME) +@Target(value = ElementType.TYPE) +public @interface CsvFields { + + /** + * Defines the CSV filename from which entities of this type should be read. + * + * @return + */ + String filename(); + + /** + * Defines the csv field name convention that determines how CSV field names + * are mapped to Java field names. + * + * @return + */ + CsvFieldNameConvention fieldNameConvention() default CsvFieldNameConvention.UNDERSCORE; + + /** + * Define a prefix string that will be prepended to ALL csv field names + * generated from Java field names for the class. For example, given a Java + * field name of "id" and a prefix of "stop_", the resulting CSV field name + * for the field would be "stop_id". Note that the prefix string is not used + * for any field defining a {@link CsvField#name()} annotation. + * + * @return a prefix string that will be prefix to ALL CSV field names + * generated from Java field names for the class. + */ + String prefix() default ""; + + /** + * If true, the corresponding CSV file for this Java entity type must exist in + * the input file set. If false, the CSV file is considered optional and may + * be unspecified. + * + * @return true if the CSV file / Java entity type is required. + */ + boolean required() default true; + + /** + * If a CSV file does not include a field name header as its first line, you + * may optionally define the header here. Specify a list of field names + * corresponding to each column of the CSV file. These CSV field names will be + * used when mapping the column values to Java fields. + * + * @return + */ + String[] fieldOrder() default {}; +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/beans/CsvEntityMappingBean.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/beans/CsvEntityMappingBean.java new file mode 100644 index 000000000..51e9d02dd --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/beans/CsvEntityMappingBean.java @@ -0,0 +1,162 @@ +/** + * Copyright (C) 2011 Brian Ferris + * Copyright (C) 2012 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema.beans; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.onebusaway.csv_entities.schema.EntityValidator; +import org.onebusaway.csv_entities.schema.FieldMapping; +import org.onebusaway.csv_entities.schema.annotations.CsvFieldNameConvention; + +public class CsvEntityMappingBean { + + private final Class type; + + private boolean filenameSet = false; + + private String filename; + + private boolean prefixSet = false; + + private String prefix; + + private boolean requiredSet = false; + + private boolean required; + + private boolean autoGenerateSchemaSet = false; + + private boolean autoGenerateSchema; + + private CsvFieldNameConvention fieldNameConvention; + + private List _validators = new ArrayList(); + + private Map fields = new LinkedHashMap(); + + private List fieldsInOrder = new ArrayList(); + + private List additionalFieldMappings = new ArrayList(); + + public CsvEntityMappingBean(Class type) { + this.type = type; + } + + public Class getType() { + return type; + } + + public boolean isFilenameSet() { + return filenameSet; + } + + public String getFilename() { + return filename; + } + + public void setFilename(String filename) { + this.filenameSet = true; + this.filename = filename; + } + + public boolean isPrefixSet() { + return prefixSet; + } + + public String getPrefix() { + return prefix; + } + + public void setPrefix(String prefix) { + this.prefixSet = true; + this.prefix = prefix; + } + + public boolean isRequiredSet() { + return requiredSet; + } + + public boolean isRequired() { + return required; + } + + public void setRequired(boolean required) { + this.requiredSet = true; + this.required = required; + } + + public boolean isAutoGenerateSchemaSet() { + return autoGenerateSchemaSet; + } + + public boolean isAutoGenerateSchema() { + return autoGenerateSchema; + } + + public void setAutoGenerateSchema(boolean autoGenerateSchema) { + this.autoGenerateSchemaSet = true; + this.autoGenerateSchema = autoGenerateSchema; + } + + public CsvFieldNameConvention getFieldNameConvention() { + return fieldNameConvention; + } + + public void setFieldNameConvention(CsvFieldNameConvention fieldNameConvention) { + this.fieldNameConvention = fieldNameConvention; + } + + public void addField(CsvFieldMappingBean field) { + this.fields.put(field.getField(), field); + } + + public Map getFields() { + return fields; + } + + public void addValidator(EntityValidator validator) { + _validators.add(validator); + } + + public List getValidators() { + return _validators; + } + + public void addAdditionalFieldMapping(FieldMapping fieldMapping) { + additionalFieldMappings.add(fieldMapping); + } + + public List getAdditionalFieldMappings() { + return additionalFieldMappings; + } + + public void addFieldInOrder(String fieldName) { + fieldsInOrder.add(fieldName); + } + + public List getFieldsInOrder() { + return fieldsInOrder; + } + + public void setFieldsInOrder(List fieldsInOrder) { + this.fieldsInOrder = fieldsInOrder; + } +} diff --git a/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/beans/CsvFieldMappingBean.java b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/beans/CsvFieldMappingBean.java new file mode 100644 index 000000000..aa2916185 --- /dev/null +++ b/onebusaway-csv-entities/src/main/java/org/onebusaway/csv_entities/schema/beans/CsvFieldMappingBean.java @@ -0,0 +1,138 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema.beans; + +import java.lang.reflect.Field; + +import org.onebusaway.csv_entities.schema.FieldMappingFactory; + +public class CsvFieldMappingBean { + + private final Field field; + + private boolean nameSet = false; + private String name; + + private boolean ignoreSet = false; + private boolean ignore; + + private boolean optionalSet = false; + private boolean optional; + + private boolean alwaysIncludeInOutputSet = false; + private boolean alwaysIncludeInOutput = false; + + private boolean mappingSet = false; + private FieldMappingFactory mapping; + + private boolean orderSet = false; + private int order; + + private String defaultValue; + + public CsvFieldMappingBean(Field field) { + this.field = field; + } + + public Field getField() { + return field; + } + + public boolean isNameSet() { + return nameSet; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.nameSet = true; + this.name = name; + } + + public boolean isIgnoreSet() { + return ignoreSet; + } + + public boolean isIgnore() { + return ignore; + } + + public void setIgnore(boolean ignore) { + this.ignoreSet = true; + this.ignore = ignore; + } + + public boolean isOptionalSet() { + return optionalSet; + } + + public boolean isOptional() { + return optional; + } + + public void setOptional(boolean optional) { + this.optionalSet = true; + this.optional = optional; + } + + public boolean isAlwaysIncludeInOutputSet() { + return alwaysIncludeInOutputSet; + } + + public boolean isAlwaysIncludeInOutput() { + return alwaysIncludeInOutput; + } + + public void setAlwaysIncludeInOutput(boolean alwaysIncludeInOutput) { + this.alwaysIncludeInOutputSet = true; + this.alwaysIncludeInOutput = alwaysIncludeInOutput; + } + + public boolean isMappingSet() { + return mappingSet; + } + public FieldMappingFactory getMapping() { + return mapping; + } + + public void setMapping(FieldMappingFactory mapping) { + this.mappingSet = true; + this.mapping = mapping; + } + + public boolean isOrderSet() { + return orderSet; + } + + public int getOrder() { + return order; + } + + public void setOrder(int order) { + this.orderSet = true; + this.order = order; + } + + public String getDefaultValue() { + return defaultValue; + } + + public void setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + } +} diff --git a/onebusaway-csv-entities/src/main/javadoc/overview.html b/onebusaway-csv-entities/src/main/javadoc/overview.html new file mode 100644 index 000000000..551633a22 --- /dev/null +++ b/onebusaway-csv-entities/src/main/javadoc/overview.html @@ -0,0 +1,115 @@ + + + +

The onebusaway-csv-entities project provides a library for + mapping between CSV data files and Java objects, both for reading and + writing.

+

Example

+ +

Consider the following CSV data file, "employees.csv": +

id,first_name,last_name,office,phone
+123,Alice,Brown,New York,555-1234
+456,Bob,Smith,San Francisco,
+ +

Here, our CSV data file defines a number of employee records, + with a header defining the fields of the file and each row + representing an employee. Let's say we wanted to map those employee + records to the following class:

+ +
class Employee {
+  private int id;
+  private String firstName;
+  private String lastName;
+  private String office;
+  private String phone;
+}
+ +

Schema Annotations

+ +

With a few simple annotations, we can define how to map the Java + class to and from CSV:

+ +
{@literal @}CsvFields(filename="employees.csv")
+class Employee {
+  private int id;
+  private String firstName;
+  private String lastName;
+  private String office;
+  {@literal @}CsvField(optional=true)
+  private String phone;
+}
+ +

+ The primary annotation is @CsvFields, + which indicates that the Java file can be mapped to and from entries + in a CSV file. We even define the name of the file that should be + written and read. +

+ +

+ By default, we map each field of the Java file to a field in the CSV + file. Each field is considered required by default, which means the + parser will throw an exception if a blank field value is found in the + input CSV (or in the Java object, when writing CSV). However, in our + example, not all phone number fields have been specified. So we + annotate the field with a @CsvField + annotation, marking the field as optional. @CsvField annotations can + be used to control how fields are serialized to and from CSV. +

+ +

Reading Objects

+ +

Now that we've annotated our Java class, let's see how to read + and build Java object instances from CSV data.

+ +
CsvEntityReader reader = new CsvEntityReader();
+reader.addEntityHandler(new EntityHandler() { ... });
+reader.readEntities(Employee.class, new FileReader("/path/to/employees.csv"));
+ +

+ First, we create a new CsvEntityReader + object, which will do the heavy-lifting of reading CSV data and + converting it into Java objects. Second, we register a new EntityHandler + instance. EntityHandler is just an interface with a single method, + "handleEntity", that gets called for each new Java entity created from + a CSV data row. Provide your own implementation to handle entities as + they are read and created. Finally, we direct the reader to read + entities of our annotated type from the specified file. +

+ +

Writing Objects

+ +

Writing entities is just as simple:

+ +
CsvEntityWriterFactory factory = new CsvEntityWriterFactory();
+CsvEntityWriter writer = factory.createWriter(Employee.class, new FileWriter("/path/to/employees.csv"));
+writer.handleEntity(new Employee());
+writer.close();
+ +

Next Steps

+ +

The OneBusAway CSV entities library can do a whole lot more than + reading and writing simple Java objects from CSV. Read the JavaDoc + documentation for more details.

+ + \ No newline at end of file diff --git a/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/AnnotatedTestBean.java b/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/AnnotatedTestBean.java new file mode 100644 index 000000000..fc3654853 --- /dev/null +++ b/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/AnnotatedTestBean.java @@ -0,0 +1,45 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities; + +import org.onebusaway.csv_entities.schema.annotations.CsvField; +import org.onebusaway.csv_entities.schema.annotations.CsvFields; + +@CsvFields(filename = "test_beans") +public class AnnotatedTestBean { + + @CsvField(optional = false) + private String name; + + @CsvField(optional = true) + private String value; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} \ No newline at end of file diff --git a/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/CSVLibraryTest.java b/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/CSVLibraryTest.java new file mode 100644 index 000000000..300ba84a9 --- /dev/null +++ b/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/CSVLibraryTest.java @@ -0,0 +1,136 @@ +/** + * Copyright (C) 2011 Brian Ferris + * Copyright (C) 2012 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities; + +import static org.junit.Assert.assertEquals; + +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +public class CSVLibraryTest { + + private CSVLibrary _csv; + + @Before + public void before() { + _csv = new CSVLibrary(); + } + + @Test + public void testParse() { + + List tokens = _csv.parse("a,b,c"); + assertEquals(3, tokens.size()); + assertEquals("a", tokens.get(0)); + assertEquals("b", tokens.get(1)); + assertEquals("c", tokens.get(2)); + + tokens = _csv.parse("a,\"b b\",\"c,c\""); + assertEquals(3, tokens.size()); + assertEquals("a", tokens.get(0)); + assertEquals("b b", tokens.get(1)); + assertEquals("c,c", tokens.get(2)); + + tokens = _csv.parse("b\"b"); + assertEquals(1, tokens.size()); + assertEquals("b\"b", tokens.get(0)); + } + + @Test + public void testParseWikipedia() { + + List tokens = _csv.parse("1997,Ford,E350"); + assertEquals(3, tokens.size()); + assertEquals("1997", tokens.get(0)); + assertEquals("Ford", tokens.get(1)); + assertEquals("E350", tokens.get(2)); + + tokens = _csv.parse("1997, Ford , E350"); + assertEquals(3, tokens.size()); + assertEquals("1997", tokens.get(0)); + assertEquals(" Ford ", tokens.get(1)); + assertEquals(" E350", tokens.get(2)); + + tokens = _csv.parse("1997,Ford,E350,\"Super, luxurious truck\""); + assertEquals(4, tokens.size()); + assertEquals("1997", tokens.get(0)); + assertEquals("Ford", tokens.get(1)); + assertEquals("E350", tokens.get(2)); + assertEquals("Super, luxurious truck", tokens.get(3)); + + tokens = _csv.parse("1997,Ford,E350,\"Super \"\"luxurious\"\" truck\""); + assertEquals(4, tokens.size()); + assertEquals("1997", tokens.get(0)); + assertEquals("Ford", tokens.get(1)); + assertEquals("E350", tokens.get(2)); + assertEquals("Super \"luxurious\" truck", tokens.get(3)); + + tokens = _csv.parse("1997,Ford,E350,\" Super luxurious truck \""); + assertEquals(4, tokens.size()); + assertEquals("1997", tokens.get(0)); + assertEquals("Ford", tokens.get(1)); + assertEquals("E350", tokens.get(2)); + assertEquals(" Super luxurious truck ", tokens.get(3)); + + tokens = _csv.parse("\"1997\",\"Ford\",\"E350\""); + assertEquals(3, tokens.size()); + assertEquals("1997", tokens.get(0)); + assertEquals("Ford", tokens.get(1)); + assertEquals("E350", tokens.get(2)); + } + + @Test + public void testParseWhitespace() { + List tokens = _csv.parse(" \"g\" "); + assertEquals(" \"g\" ", tokens.get(0)); + + tokens = _csv.parse(" \" h \" "); + assertEquals(" \" h \" ", tokens.get(0)); + + tokens = _csv.parse(" \" \"\" i \"\" \" "); + assertEquals(" \" \"\" i \"\" \" ", tokens.get(0)); + + tokens = _csv.parse(" \"a,b\",c"); + assertEquals(3, tokens.size()); + assertEquals(" \"a", tokens.get(0)); + assertEquals("b\"", tokens.get(1)); + assertEquals("c", tokens.get(2)); + } + + @Test + public void testTrimInitialWhitespace() { + + _csv.setTrimInitialWhitespace(true); + + List tokens = _csv.parse(" \"g\" "); + assertEquals("g ", tokens.get(0)); + + tokens = _csv.parse(" \" h \" "); + assertEquals(" h ", tokens.get(0)); + + tokens = _csv.parse(" \" \"\" i \"\" \" "); + assertEquals(" \" i \" ", tokens.get(0)); + + tokens = _csv.parse(" \"a,b\", c, \"d\""); + assertEquals(3, tokens.size()); + assertEquals("a,b", tokens.get(0)); + assertEquals("c", tokens.get(1)); + assertEquals("d", tokens.get(2)); + } +} diff --git a/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/CsvEntityReaderTest.java b/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/CsvEntityReaderTest.java new file mode 100644 index 000000000..f8e2a19c0 --- /dev/null +++ b/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/CsvEntityReaderTest.java @@ -0,0 +1,75 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.io.StringReader; +import java.util.List; + +import org.junit.Test; +import org.onebusaway.csv_entities.exceptions.CsvEntityIOException; +import org.onebusaway.csv_entities.schema.AnnotationDrivenEntitySchemaFactory; + +public class CsvEntityReaderTest { + + @Test + public void testBadLine() { + + CsvEntityReader reader = new CsvEntityReader(); + + AnnotationDrivenEntitySchemaFactory entitySchemaFactory = new AnnotationDrivenEntitySchemaFactory(); + entitySchemaFactory.addEntityClass(AnnotatedTestBean.class); + reader.setEntitySchemaFactory(entitySchemaFactory); + + String content = "name,value\na,b\n,d\n"; + StringReader source = new StringReader(content); + + try { + reader.readEntities(AnnotatedTestBean.class, source); + fail(); + } catch (CsvEntityIOException e) { + assertEquals(AnnotatedTestBean.class, e.getEntityType()); + assertEquals("test_beans", e.getPath()); + assertEquals(3, e.getLineNumber()); + } catch (IOException e) { + fail(); + } + } + + @Test + public void testInternString() throws CsvEntityIOException, IOException { + + ListEntityHandler handler = new ListEntityHandler(); + + CsvEntityReader reader = new CsvEntityReader(); + reader.setInternStrings(true); + reader.addEntityHandler(handler); + + String content = "name,value\na,b\nc,b\n"; + StringReader source = new StringReader(content); + reader.readEntities(AnnotatedTestBean.class, source); + + List values = handler.getValues(); + AnnotatedTestBean a = values.get(0); + AnnotatedTestBean b = values.get(1); + assertSame(a.getValue(), b.getValue()); + } + +} diff --git a/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/ExtensionsTest.java b/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/ExtensionsTest.java new file mode 100644 index 000000000..db74f97b0 --- /dev/null +++ b/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/ExtensionsTest.java @@ -0,0 +1,182 @@ +/** + * Copyright (C) 2013 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities; + +import org.junit.Test; +import org.onebusaway.csv_entities.schema.DefaultEntitySchemaFactory; +import org.onebusaway.csv_entities.schema.ExcludeOptionalAndMissingEntitySchemaFactory; +import org.onebusaway.csv_entities.schema.annotations.CsvField; +import org.onebusaway.csv_entities.schema.annotations.CsvFields; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringReader; +import java.util.Collections; +import java.util.List; +import java.util.zip.ZipFile; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class ExtensionsTest { + + @Test + public void testReadExtensions() throws IOException { + DefaultEntitySchemaFactory factory = new DefaultEntitySchemaFactory(); + factory.addExtension(BaseBean.class, ExtensionBean.class); + + ListEntityHandler handler = new ListEntityHandler(); + + CsvEntityReader reader = new CsvEntityReader(); + reader.setEntitySchemaFactory(factory); + reader.addEntityHandler(handler); + + String content = "name,value\nCats,untrustworthy\nDogs,awesome\n"; + reader.readEntities(BaseBean.class, new StringReader(content)); + + List beans = handler.getValues(); + assertEquals(2, beans.size()); + + { + BaseBean bean = beans.get(0); + assertEquals("Cats", bean.getName()); + ExtensionBean extension = bean.getExtension(ExtensionBean.class); + assertTrue(extension != null); + assertEquals("untrustworthy", extension.getValue()); + } + { + BaseBean bean = beans.get(1); + assertEquals("Dogs", bean.getName()); + ExtensionBean extension = bean.getExtension(ExtensionBean.class); + assertTrue(extension != null); + assertEquals("awesome", extension.getValue()); + } + } + + @Test + public void testWriteExtensions() throws IOException { + DefaultEntitySchemaFactory factory = new DefaultEntitySchemaFactory(); + factory.addExtension(BaseBean.class, ExtensionBean.class); + + CsvEntityWriter writer = new CsvEntityWriter(); + writer.setEntitySchemaFactory(factory); + + File output = File.createTempFile("ExtensionsText", ".zip"); + output.delete(); + output.deleteOnExit(); + + writer.setOutputLocation(output); + + BaseBean bean = new BaseBean(); + bean.setName("Birds"); + ExtensionBean extension = new ExtensionBean(); + extension.setValue("flighty"); + bean.putExtension(ExtensionBean.class, extension); + + writer.handleEntity(bean); + writer.close(); + + ZipFile zip = new ZipFile(output); + String data = read(zip.getInputStream(zip.getEntry("animals.csv"))); + assertEquals("name,value\nBirds,flighty\n", data); + } + + @Test + public void testWriteEmptyExtensions() throws IOException { + BaseBean bean = new BaseBean(); + bean.setName("Birds"); + ExtensionBean extension = new ExtensionBean(); + extension.setValue("flighty"); + bean.putExtension(ExtensionBean.class, extension); + ExtensionBean2 extension2 = new ExtensionBean2(); + bean.putExtension(ExtensionBean2.class, extension2); + + DefaultEntitySchemaFactory factory = new DefaultEntitySchemaFactory(); + factory.addExtension(BaseBean.class, ExtensionBean.class); + factory.addExtension(BaseBean.class, ExtensionBean2.class); + + ExcludeOptionalAndMissingEntitySchemaFactory excludeFactory = new ExcludeOptionalAndMissingEntitySchemaFactory(factory); + excludeFactory.scanEntities(BaseBean.class, Collections.singleton(bean)); + + CsvEntityWriter writer = new CsvEntityWriter(); + writer.setEntitySchemaFactory(excludeFactory); + + File output = File.createTempFile("ExtensionsText", ".zip"); + output.delete(); + output.deleteOnExit(); + + writer.setOutputLocation(output); + + writer.handleEntity(bean); + writer.close(); + + ZipFile zip = new ZipFile(output); + String data = read(zip.getInputStream(zip.getEntry("animals.csv"))); + assertEquals("name,value\nBirds,flighty\n", data); + } + + private static String read(InputStream in) throws IOException { + BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + StringBuilder b = new StringBuilder(); + String line = null; + while ((line = reader.readLine()) != null) { + b.append(line).append('\n'); + } + return b.toString(); + } + + @CsvFields(filename = "animals.csv") + public static class BaseBean extends HasExtensionsImpl { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + public static class ExtensionBean { + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + public static class ExtensionBean2 { + @CsvField(defaultValue = "0", optional = true) + private int emptyValue = 0; + + public int getEmptyValue() { + return emptyValue; + } + + public void setEmptyValue(int emptyValue) { + this.emptyValue = emptyValue; + } + } +} diff --git a/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/HasExtensionsImpl.java b/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/HasExtensionsImpl.java new file mode 100644 index 000000000..c229c7d58 --- /dev/null +++ b/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/HasExtensionsImpl.java @@ -0,0 +1,38 @@ +/** + * Copyright (C) 2013 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities; + +import java.util.HashMap; +import java.util.Map; + +import org.onebusaway.csv_entities.schema.annotations.CsvField; + +public class HasExtensionsImpl implements HasExtensions { + + @CsvField(ignore = true) + private Map, Object> extensions = new HashMap, Object>(); + + @Override + public void putExtension(Class type, Object extension) { + extensions.put(type, extension); + } + + @SuppressWarnings("unchecked") + @Override + public X getExtension(Class type) { + return (X) extensions.get(type); + } +} diff --git a/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/IndividualCsvEntityWriterTest.java b/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/IndividualCsvEntityWriterTest.java new file mode 100644 index 000000000..4045d238e --- /dev/null +++ b/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/IndividualCsvEntityWriterTest.java @@ -0,0 +1,121 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities; + +import static org.junit.Assert.assertEquals; + +import java.io.PrintWriter; +import java.io.StringWriter; + +import org.junit.Test; +import org.onebusaway.csv_entities.schema.DefaultEntitySchemaFactory; +import org.onebusaway.csv_entities.schema.EntitySchemaFactoryHelper; +import org.onebusaway.csv_entities.schema.beans.CsvEntityMappingBean; + +public class IndividualCsvEntityWriterTest { + + @Test + public void testOrder() { + + DefaultEntitySchemaFactory factory = new DefaultEntitySchemaFactory(); + EntitySchemaFactoryHelper helper = new EntitySchemaFactoryHelper(factory); + + CsvEntityMappingBean mapping = helper.addEntity(TestBean.class); + helper.addField(mapping, "name"); + helper.addField(mapping, "value"); + + CsvEntityContextImpl context = new CsvEntityContextImpl(); + StringWriter output = new StringWriter(); + + IndividualCsvEntityWriter writer = new IndividualCsvEntityWriter(context, + factory.getSchema(TestBean.class), new PrintWriter(output)); + + TestBean bean = new TestBean(); + bean.setName("alice"); + bean.setValue("a"); + writer.handleEntity(bean); + + bean.setName("bob"); + bean.setValue("b"); + writer.handleEntity(bean); + + writer.close(); + + String content = output.getBuffer().toString(); + assertEquals("name,value\nalice,a\nbob,b\n", content); + } + + @Test + public void testOrderAlternate() { + + DefaultEntitySchemaFactory factory = new DefaultEntitySchemaFactory(); + EntitySchemaFactoryHelper helper = new EntitySchemaFactoryHelper(factory); + + CsvEntityMappingBean mapping = helper.addEntity(TestBean.class); + helper.addField(mapping, "value"); + helper.addField(mapping, "name"); + + CsvEntityContextImpl context = new CsvEntityContextImpl(); + StringWriter output = new StringWriter(); + + IndividualCsvEntityWriter writer = new IndividualCsvEntityWriter(context, + factory.getSchema(TestBean.class), new PrintWriter(output)); + + TestBean bean = new TestBean(); + bean.setName("alice"); + bean.setValue("a"); + writer.handleEntity(bean); + + bean.setName("bob"); + bean.setValue("b"); + writer.handleEntity(bean); + + writer.close(); + + String content = output.getBuffer().toString(); + assertEquals("value,name\na,alice\nb,bob\n", content); + } + + @Test + public void testDefaultValues() { + DefaultEntitySchemaFactory factory = new DefaultEntitySchemaFactory(); + + CsvEntityContextImpl context = new CsvEntityContextImpl(); + StringWriter output = new StringWriter(); + + IndividualCsvEntityWriter writer = new IndividualCsvEntityWriter(context, + factory.getSchema(OptionalFieldTestBean.class), new PrintWriter(output)); + + OptionalFieldTestBean tb = new OptionalFieldTestBean(); + + tb.setIntValue(1234); + tb.setDoubleValue(2345.8); + + writer.handleEntity(tb); + + tb.clearIntValue(); + tb.clearDoubleValue(); + + writer.handleEntity(tb); + + writer.close(); + + String content = output.getBuffer().toString(); + assertEquals("int_value,double_value" + System.lineSeparator() + + "1234,2345.80" + System.lineSeparator() + "," + System.lineSeparator(), content); + } + +} diff --git a/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/OptionalFieldTestBean.java b/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/OptionalFieldTestBean.java new file mode 100644 index 000000000..164ae364c --- /dev/null +++ b/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/OptionalFieldTestBean.java @@ -0,0 +1,68 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onebusaway.csv_entities; + +import org.onebusaway.csv_entities.schema.DecimalFieldMappingFactory; +import org.onebusaway.csv_entities.schema.DecimalFieldMappingFactory.NumberFormatAnnotation; +import org.onebusaway.csv_entities.schema.annotations.CsvField; +import org.onebusaway.csv_entities.schema.annotations.CsvFields; + +@CsvFields(filename = "test_beans") +public class OptionalFieldTestBean { + + private static final int DEFAULT_VALUE = -999; + + @CsvField(optional = true) + private int intValue = DEFAULT_VALUE; + + @CsvField(optional = true, mapping = DecimalFieldMappingFactory.class) + @NumberFormatAnnotation("0.00") + private double doubleValue = DEFAULT_VALUE; + + public boolean isIntValueSet() { + return intValue != DEFAULT_VALUE; + } + + public int getIntValue() { + return intValue; + } + + public void setIntValue(final int intValue) { + this.intValue = intValue; + } + + public void clearIntValue() { + this.intValue = DEFAULT_VALUE; + } + + public boolean isDoubleValueSet() { + return doubleValue != DEFAULT_VALUE; + } + + public double getDoubleValue() { + return doubleValue; + } + + public void setDoubleValue(double doubleValue) { + this.doubleValue = doubleValue; + } + + public void clearDoubleValue() { + this.doubleValue = DEFAULT_VALUE; + } + +} diff --git a/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/TestBean.java b/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/TestBean.java new file mode 100644 index 000000000..97a330e6a --- /dev/null +++ b/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/TestBean.java @@ -0,0 +1,39 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities; + +public class TestBean { + + private String name; + + private String value; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} \ No newline at end of file diff --git a/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/schema/AbstractEntitySchemaFactoryImplTest.java b/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/schema/AbstractEntitySchemaFactoryImplTest.java new file mode 100644 index 000000000..27a6010aa --- /dev/null +++ b/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/schema/AbstractEntitySchemaFactoryImplTest.java @@ -0,0 +1,98 @@ +/** + * Copyright (C) 2012 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema; + +import static org.junit.Assert.*; + +import java.util.List; + +import org.junit.Test; +import org.onebusaway.csv_entities.HasExtensionsImpl; +import org.onebusaway.csv_entities.schema.annotations.CsvField; +import org.onebusaway.csv_entities.schema.annotations.CsvFieldNameConvention; +import org.onebusaway.csv_entities.schema.annotations.CsvFields; + +public class AbstractEntitySchemaFactoryImplTest { + + @Test + public void test() { + SchemaFactory factory = new SchemaFactory(); + EntitySchema schema = factory.getSchema(CapitalizedCamelCaseBean.class); + List fields = schema.getFields(); + assertEquals(1, fields.size()); + SingleFieldMapping fieldMapping = (SingleFieldMapping) fields.get(0); + assertEquals("FirstName", fieldMapping.getCsvFieldName()); + assertEquals("firstName", fieldMapping.getObjFieldName()); + } + + @Test + public void testExtensions() { + SchemaFactory factory = new SchemaFactory(); + factory.addExtension(BaseBean.class, ExtensionBean.class); + + EntitySchema schema = factory.getSchema(BaseBean.class); + List extensions = schema.getExtensions(); + assertEquals(1, extensions.size()); + ExtensionEntitySchema extensionSchema = extensions.get(0); + List fields = extensionSchema.getFields(); + assertEquals(1, fields.size()); + SingleFieldMapping field = (SingleFieldMapping) fields.get(0); + assertEquals("value", field.getCsvFieldName()); + } + + @Test + public void testExtensionsAfterSchemaIsCached() { + SchemaFactory factory = new SchemaFactory(); + EntitySchema entitySchema = factory.getSchema(BaseBean.class); + assertTrue(entitySchema.getExtensions().isEmpty()); + + factory.addExtension(BaseBean.class, ExtensionBean.class); + + entitySchema = factory.getSchema(BaseBean.class); + assertEquals(1, entitySchema.getExtensions().size()); + } + + private class SchemaFactory extends AbstractEntitySchemaFactoryImpl { + @Override + protected void processBeanDefinitions() { + + } + } + + @CsvFields(filename = "file.csv", fieldNameConvention = CsvFieldNameConvention.CAPITALIZED_CAMEL_CASE) + public static class CapitalizedCamelCaseBean { + + private String firstName; + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + } + + public static class BaseBean extends HasExtensionsImpl { + + } + + public static class ExtensionBean { + @CsvField(optional = true) + private String value; + } +} diff --git a/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/schema/BeanWrapperFactoryTest.java b/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/schema/BeanWrapperFactoryTest.java new file mode 100644 index 000000000..b75110550 --- /dev/null +++ b/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/schema/BeanWrapperFactoryTest.java @@ -0,0 +1,77 @@ +/** + * Copyright (C) 2011 Brian Ferris + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.onebusaway.csv_entities.schema.BeanWrapper; +import org.onebusaway.csv_entities.schema.BeanWrapperFactory; + +public class BeanWrapperFactoryTest { + + @Test + public void test() { + AB ab = new AB(); + ab.setA("a"); + ab.setB("b"); + + BeanWrapper wrapper = BeanWrapperFactory.wrap(ab); + assertEquals(ab, wrapper.getWrappedInstance(AB.class)); + + assertEquals("a", wrapper.getPropertyValue("a")); + assertEquals("b", wrapper.getPropertyValue("b")); + + ab.setA("c"); + ab.setB("d"); + + assertEquals("c", wrapper.getPropertyValue("a")); + assertEquals("d", wrapper.getPropertyValue("b")); + + wrapper.setPropertyValue("a", "e"); + wrapper.setPropertyValue("b", "f"); + + assertEquals("e",ab.getA()); + assertEquals("f",ab.getB()); + + assertEquals("e", wrapper.getPropertyValue("a")); + assertEquals("f", wrapper.getPropertyValue("b")); + } + + private static class AB { + + private String a; + + private String b; + + public String getA() { + return a; + } + + public void setA(String a) { + this.a = a; + } + + public String getB() { + return b; + } + + public void setB(String b) { + this.b = b; + } + + } +} diff --git a/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/schema/DecimalFieldMappingFactoryTest.java b/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/schema/DecimalFieldMappingFactoryTest.java new file mode 100644 index 000000000..021b11e3c --- /dev/null +++ b/onebusaway-csv-entities/src/test/java/org/onebusaway/csv_entities/schema/DecimalFieldMappingFactoryTest.java @@ -0,0 +1,80 @@ +/** + * Copyright (C) 2011 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.csv_entities.schema; + +import static org.junit.Assert.assertEquals; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import org.junit.Test; +import org.onebusaway.csv_entities.CsvEntityContext; +import org.onebusaway.csv_entities.CsvEntityContextImpl; + +public class DecimalFieldMappingFactoryTest { + + @Test + public void testWithEnLocale() { + DecimalFieldMappingFactory factory = new DecimalFieldMappingFactory("0.00", Locale.US); + FieldMapping mapping = createFieldMapping(factory); + + Dummy dummy = new Dummy(); + dummy.setValue(3.14159); + + CsvEntityContext context = new CsvEntityContextImpl(); + BeanWrapper wrapped = BeanWrapperFactory.wrap(dummy); + Map values = new HashMap(); + mapping.translateFromObjectToCSV(context, wrapped, values); + assertEquals("3.14", values.get("value")); + } + + @Test + public void testWithFrLocale() { + DecimalFieldMappingFactory factory = new DecimalFieldMappingFactory("0.00", + Locale.FRANCE); + FieldMapping mapping = createFieldMapping(factory); + + Dummy dummy = new Dummy(); + dummy.setValue(3.14159); + + CsvEntityContext context = new CsvEntityContextImpl(); + BeanWrapper wrapped = BeanWrapperFactory.wrap(dummy); + Map values = new HashMap(); + mapping.translateFromObjectToCSV(context, wrapped, values); + assertEquals("3,14", values.get("value")); + } + + private FieldMapping createFieldMapping(DecimalFieldMappingFactory factory) { + EntitySchemaFactory schemaFactory = new DefaultEntitySchemaFactory(); + FieldMapping mapping = factory.createFieldMapping(schemaFactory, + Dummy.class, "value", "value", Double.TYPE, true); + return mapping; + } + + public static class Dummy { + private double value; + + public double getValue() { + return value; + } + + public void setValue(double value) { + this.value = value; + } + + } +} diff --git a/onebusaway-gtfs-hibernate-cli/pom.xml b/onebusaway-gtfs-hibernate-cli/pom.xml index 44593d595..038fcaf0b 100644 --- a/onebusaway-gtfs-hibernate-cli/pom.xml +++ b/onebusaway-gtfs-hibernate-cli/pom.xml @@ -3,18 +3,13 @@ org.onebusaway onebusaway-gtfs-modules - 1.4.15-openmove-5 + 5.0.1-openmove-1 + ../pom.xml onebusaway-gtfs-hibernate-cli onebusaway-gtfs-hibernate-cli A command-line utility for loading GTFS data into a database. - - - false - - org.onebusaway @@ -29,13 +24,13 @@ org.slf4j slf4j-simple - ${slf4j_version} + ${slf4j.version} - + maven-shade-plugin @@ -55,22 +50,7 @@ - - org.apache.maven.plugins - maven-deploy-plugin - - ${skip-deploy-jar} - - - - - oss-distribution - - true - - - diff --git a/onebusaway-gtfs-hibernate/pom.xml b/onebusaway-gtfs-hibernate/pom.xml index 7f88e245d..91ba06b5d 100644 --- a/onebusaway-gtfs-hibernate/pom.xml +++ b/onebusaway-gtfs-hibernate/pom.xml @@ -9,7 +9,8 @@ org.onebusaway onebusaway-gtfs-modules - 1.4.15-openmove-5 + 5.0.1-openmove-1 + ../pom.xml @@ -21,36 +22,63 @@ org.hibernate hibernate-core - 5.4.24.Final + 5.6.15.Final org.hsqldb hsqldb - 2.5.1 + 2.7.4 test com.mysql mysql-connector-j - 8.2.0 + 9.1.0 - junit - junit + org.slf4j + slf4j-simple + ${slf4j.version} test - org.slf4j - slf4j-simple - ${slf4j_version} + org.junit.jupiter + junit-jupiter-api com.sun.xml.bind jaxb-impl runtime - + + + + + com.google.cloud.tools + jib-maven-plugin + + + true + + + + + io.github.zlika + reproducible-build-maven-plugin + 0.17 + + + run-when-packaged + + strip-jar + + package + + + + + diff --git a/onebusaway-gtfs-hibernate/src/main/java/org/onebusaway/gtfs/impl/HibernateGtfsRelationalDaoImpl.java b/onebusaway-gtfs-hibernate/src/main/java/org/onebusaway/gtfs/impl/HibernateGtfsRelationalDaoImpl.java index 18100fdcf..402247178 100644 --- a/onebusaway-gtfs-hibernate/src/main/java/org/onebusaway/gtfs/impl/HibernateGtfsRelationalDaoImpl.java +++ b/onebusaway-gtfs-hibernate/src/main/java/org/onebusaway/gtfs/impl/HibernateGtfsRelationalDaoImpl.java @@ -70,12 +70,12 @@ public T getEntityForId(Class type, Serializable id) { @Override public List getAllAgencies() { - return _ops.find("from Agency"); + return _ops.find("FROM Agency"); } @Override public List getAllBlocks() { - return _ops.find("from Block"); + return _ops.find("FROM Block"); } @Override @@ -161,6 +161,16 @@ public Collection getAllTransfers() { @Override public Collection getAllRiderships() { return _ops.find("FROM Ridership"); } + @Override + public Collection getAllVehicles() { + return _ops.find("FROM Vehicle"); + } + + @Override + public Vehicle getVehicleForId(AgencyAndId id) { + return (Vehicle) _ops.get(Vehicle.class, id); + } + @Override public Collection getAllDirectionEntries() { @@ -284,7 +294,7 @@ public Trip getTripForId(AgencyAndId id) { @Override public Collection getAllAreas() { - return _ops.find("from Area"); + return _ops.find("FROM Area"); } @Deprecated @@ -295,18 +305,18 @@ public Collection getAllLocationGroupElements() { LocationGroupElement locationGroupElement = new LocationGroupElement(); locationGroupElement.setLocationGroupId(group.getId()); locationGroupElement.setName(group.getName()); - locationGroupElement.setLocation(stopLocation); + locationGroupElement.setStop(stopLocation); return locationGroupElement; })).collect(Collectors.toList()); } @Override public Collection getAllStopAreaElements() { - Collection groups = _ops.find("FROM StopArea"); - return groups.stream().flatMap(group -> group.getLocations().stream().map(stopLocation -> { - StopAreaElement stopAreaElement = new StopAreaElement(); - stopAreaElement.setId(group.getId()); - stopAreaElement.setStopLocation(stopLocation); + Collection areas = _ops.find("FROM StopArea"); + return areas.stream().flatMap(area -> area.getStops().stream().map(stopLocation -> { + var stopAreaElement = new StopAreaElement(); + stopAreaElement.setId(area.getId()); + stopAreaElement.setStop(stopLocation); return stopAreaElement; })).collect(Collectors.toList()); } @@ -315,10 +325,7 @@ public Collection getAllStopAreaElements() { public Collection getAllLocationGroups() { return _ops.find("FROM LocationGroup"); } - @Override - public Collection getAllStopAreas() { - return _ops.find("from StopArea"); - } + @Override public Collection getAllLocations() { return _ops.find("FROM Location"); @@ -326,12 +333,17 @@ public Collection getAllLocations() { @Override public Collection getAllBookingRules() { - return _ops.find("from BookingRule"); + return _ops.find("FROM BookingRule"); } @Override public Collection getAllTranslations() { - return _ops.find("from Translation"); + return _ops.find("FROM Translation"); + } + + @Override + public Collection getAllNetworks() { + return _ops.find("FROM Network"); } @Override @@ -533,4 +545,4 @@ public void saveOrUpdateEntity(Object entity) { public void clearAllEntitiesForType(Class type) { _ops.clearAllEntitiesForType(type); } -} \ No newline at end of file +} diff --git a/onebusaway-gtfs-hibernate/src/main/resources/org/onebusaway/gtfs/model/GtfsMapping.hibernate.xml b/onebusaway-gtfs-hibernate/src/main/resources/org/onebusaway/gtfs/model/GtfsMapping.hibernate.xml index d17e6902d..d526058c5 100644 --- a/onebusaway-gtfs-hibernate/src/main/resources/org/onebusaway/gtfs/model/GtfsMapping.hibernate.xml +++ b/onebusaway-gtfs-hibernate/src/main/resources/org/onebusaway/gtfs/model/GtfsMapping.hibernate.xml @@ -312,7 +312,6 @@ - @@ -365,4 +364,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/onebusaway-gtfs-hibernate/src/site/site.xml b/onebusaway-gtfs-hibernate/src/site/site.xml deleted file mode 100644 index 80c56bc38..000000000 --- a/onebusaway-gtfs-hibernate/src/site/site.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/onebusaway-gtfs-hibernate/src/test/java/org/onebusaway/gtfs/impl/GtfsMappingTest.java b/onebusaway-gtfs-hibernate/src/test/java/org/onebusaway/gtfs/impl/GtfsMappingTest.java index d7cd1cb65..4f87d7d70 100644 --- a/onebusaway-gtfs-hibernate/src/test/java/org/onebusaway/gtfs/impl/GtfsMappingTest.java +++ b/onebusaway-gtfs-hibernate/src/test/java/org/onebusaway/gtfs/impl/GtfsMappingTest.java @@ -15,17 +15,17 @@ */ package org.onebusaway.gtfs.impl; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; import java.io.IOException; import java.io.StringReader; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.csv_entities.exceptions.CsvEntityIOException; import org.onebusaway.gtfs.model.Agency; import org.onebusaway.gtfs.model.AgencyAndId; @@ -43,8 +43,8 @@ public class GtfsMappingTest { private static GtfsReader _reader; - @Before - public void setup() throws IOException { + @BeforeEach + public void setup() { Configuration config = new Configuration(); config = config.configure("org/onebusaway/gtfs/hibernate-configuration.xml"); @@ -57,7 +57,7 @@ public void setup() throws IOException { _reader.setEntityStore(_dao); } - @After + @AfterEach public void teardown() { if (_dao != null) _dao.close(); diff --git a/onebusaway-gtfs-hibernate/src/test/java/org/onebusaway/gtfs/impl/HibernateGtfsRelationalDaoImplCaltrainTest.java b/onebusaway-gtfs-hibernate/src/test/java/org/onebusaway/gtfs/impl/HibernateGtfsRelationalDaoImplCaltrainTest.java index 365f7bbe4..1e6cfa937 100644 --- a/onebusaway-gtfs-hibernate/src/test/java/org/onebusaway/gtfs/impl/HibernateGtfsRelationalDaoImplCaltrainTest.java +++ b/onebusaway-gtfs-hibernate/src/test/java/org/onebusaway/gtfs/impl/HibernateGtfsRelationalDaoImplCaltrainTest.java @@ -16,11 +16,11 @@ */ package org.onebusaway.gtfs.impl; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; import java.io.IOException; @@ -30,9 +30,9 @@ import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.model.Agency; import org.onebusaway.gtfs.model.AgencyAndId; import org.onebusaway.gtfs.model.Route; @@ -53,7 +53,7 @@ public class HibernateGtfsRelationalDaoImplCaltrainTest { private static HibernateGtfsRelationalDaoImpl _dao; - @BeforeClass + @BeforeAll public static void setup() throws IOException { Configuration config = new Configuration(); @@ -70,7 +70,7 @@ public static void setup() throws IOException { reader.run(); } - @AfterClass + @AfterAll public static void teardown() { _sessionFactory.close(); } @@ -226,7 +226,6 @@ public void testGetTripById() { assertNull(trip.getBlockId()); assertEquals("0", trip.getDirectionId()); assertEquals(route, trip.getRoute()); - assertNull(trip.getRouteShortName()); assertEquals(aid("WD01272009"), trip.getServiceId()); assertEquals(aid("cal_sj_sf"), trip.getShapeId()); assertEquals("101", trip.getTripShortName()); diff --git a/onebusaway-gtfs-hibernate/src/test/java/org/onebusaway/gtfs/impl/HibernateGtfsRelationalDaoImplTest.java b/onebusaway-gtfs-hibernate/src/test/java/org/onebusaway/gtfs/impl/HibernateGtfsRelationalDaoImplTest.java index c2fbbd614..9d328797a 100644 --- a/onebusaway-gtfs-hibernate/src/test/java/org/onebusaway/gtfs/impl/HibernateGtfsRelationalDaoImplTest.java +++ b/onebusaway-gtfs-hibernate/src/test/java/org/onebusaway/gtfs/impl/HibernateGtfsRelationalDaoImplTest.java @@ -15,8 +15,8 @@ */ package org.onebusaway.gtfs.impl; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.util.HashSet; @@ -25,9 +25,9 @@ import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.model.AgencyAndId; import org.onebusaway.gtfs.model.Stop; @@ -37,7 +37,7 @@ public class HibernateGtfsRelationalDaoImplTest { private static HibernateGtfsRelationalDaoImpl _dao; - @Before + @BeforeEach public void setup() throws IOException { Configuration config = new Configuration(); @@ -48,7 +48,7 @@ public void setup() throws IOException { _dao.open(); } - @After + @AfterEach public void teardown() { if (_dao != null) _dao.close(); diff --git a/onebusaway-gtfs-hibernate/src/test/java/org/onebusaway/gtfs/impl/HibernateGtfsRelationalImplBartTest.java b/onebusaway-gtfs-hibernate/src/test/java/org/onebusaway/gtfs/impl/HibernateGtfsRelationalImplBartTest.java index 9ed08d7b9..85879865e 100644 --- a/onebusaway-gtfs-hibernate/src/test/java/org/onebusaway/gtfs/impl/HibernateGtfsRelationalImplBartTest.java +++ b/onebusaway-gtfs-hibernate/src/test/java/org/onebusaway/gtfs/impl/HibernateGtfsRelationalImplBartTest.java @@ -16,8 +16,8 @@ */ package org.onebusaway.gtfs.impl; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; import java.io.IOException; @@ -25,9 +25,9 @@ import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.model.AgencyAndId; import org.onebusaway.gtfs.model.Frequency; import org.onebusaway.gtfs.model.ServiceCalendar; @@ -44,7 +44,7 @@ public class HibernateGtfsRelationalImplBartTest { private static HibernateGtfsRelationalDaoImpl _dao; - @BeforeClass + @BeforeAll public static void setup() throws IOException { Configuration config = new Configuration(); @@ -61,7 +61,7 @@ public static void setup() throws IOException { reader.run(); } - @AfterClass + @AfterAll public static void teardown() { _sessionFactory.close(); } diff --git a/onebusaway-gtfs-hibernate/src/test/java/org/onebusaway/gtfs/impl/LongRouteDescriptionTest.java b/onebusaway-gtfs-hibernate/src/test/java/org/onebusaway/gtfs/impl/LongRouteDescriptionTest.java index 4b45b0620..8ec801906 100644 --- a/onebusaway-gtfs-hibernate/src/test/java/org/onebusaway/gtfs/impl/LongRouteDescriptionTest.java +++ b/onebusaway-gtfs-hibernate/src/test/java/org/onebusaway/gtfs/impl/LongRouteDescriptionTest.java @@ -15,7 +15,7 @@ */ package org.onebusaway.gtfs.impl; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.File; import java.io.IOException; @@ -23,9 +23,9 @@ import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.model.Agency; import org.onebusaway.gtfs.model.AgencyAndId; import org.onebusaway.gtfs.model.Route; @@ -39,7 +39,7 @@ public class LongRouteDescriptionTest { private static HibernateGtfsRelationalDaoImpl _dao; - @BeforeClass + @BeforeAll public static void setup() throws IOException { Configuration config = new Configuration(); @@ -62,7 +62,7 @@ public static void setup() throws IOException { reader.run(); } - @AfterClass + @AfterAll public static void teardown() { _sessionFactory.close(); } diff --git a/onebusaway-gtfs-hibernate/src/test/java/org/onebusaway/gtfs/impl/VehicleTest.java b/onebusaway-gtfs-hibernate/src/test/java/org/onebusaway/gtfs/impl/VehicleTest.java new file mode 100644 index 000000000..5730ed4db --- /dev/null +++ b/onebusaway-gtfs-hibernate/src/test/java/org/onebusaway/gtfs/impl/VehicleTest.java @@ -0,0 +1,109 @@ +/** + * Copyright (C) 2024 Cambridge Systematics, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.gtfs.impl; + +import org.hibernate.SessionFactory; +import org.hibernate.cfg.Configuration; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.onebusaway.gtfs.model.*; +import org.onebusaway.gtfs.serialization.GtfsReader; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class VehicleTest { + private static SessionFactory _sessionFactory; + + private static final String _agencyId = "agency"; + + private static HibernateGtfsRelationalDaoImpl _dao; + + @BeforeAll + public static void setup() throws IOException { + + Configuration config = new Configuration(); + config = config.configure("org/onebusaway/gtfs/hibernate-configuration.xml"); + _sessionFactory = config.buildSessionFactory(); + + _dao = new HibernateGtfsRelationalDaoImpl(_sessionFactory); + + GtfsReader reader = new GtfsReader(); + reader.setInputLocation(new File("src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext")); + reader.setEntityStore(_dao); + reader.setDefaultAgencyId(_agencyId); + + List> entityClasses = reader.getEntityClasses(); + entityClasses.clear(); + entityClasses.add(Agency.class); + entityClasses.add(Icon.class); + entityClasses.add(Vehicle.class); + + reader.run(); + } + + @AfterAll + public static void teardown() { + _sessionFactory.close(); + } + + @Test + public void testVehicleById() { + Vehicle vehicle = _dao.getVehicleForId(aid("123")); + assertEquals(aid("123"), vehicle.getId()); + assertEquals(2, vehicle.getBikeCapacity()); + assertEquals("vehicle description", vehicle.getDescription()); + assertEquals(2, vehicle.getDoorCount()); + assertEquals("30 ft", vehicle.getDoorWidth()); + assertEquals(40, vehicle.getSeatedCapacity()); + assertEquals(20, vehicle.getStandingCapacity()); + assertEquals(0, vehicle.getLowFloor()); + assertEquals("yes", vehicle.getWheelchairAccess()); + + // Icon + Icon icon = vehicle.getIcon(); + assertEquals(aid("ICO"), icon.getId()); + assertEquals("test icon", icon.getDescription()); + assertEquals("https://iconurl", icon.getUrl()); + + // Larger Vehicle + vehicle = _dao.getVehicleForId(aid("456")); + assertEquals(aid("456"), vehicle.getId()); + assertEquals(4, vehicle.getBikeCapacity()); + assertEquals("larger vehicle description", vehicle.getDescription()); + assertEquals(3, vehicle.getDoorCount()); + assertEquals("40 ft", vehicle.getDoorWidth()); + assertEquals(0, vehicle.getLowFloor()); + assertEquals(65, vehicle.getSeatedCapacity()); + assertEquals(40, vehicle.getStandingCapacity()); + assertEquals("no", vehicle.getWheelchairAccess()); + + // Icon for larger vehicle (many to one relationship) + icon = vehicle.getIcon(); + assertEquals(aid("ICO"), icon.getId()); + assertEquals("test icon", icon.getDescription()); + assertEquals("https://iconurl", icon.getUrl()); + } + + private AgencyAndId aid(String id) { + return new AgencyAndId(_agencyId, id); + } +} + diff --git a/onebusaway-gtfs-hibernate/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/agency.txt b/onebusaway-gtfs-hibernate/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/agency.txt new file mode 100644 index 000000000..31e2a82e4 --- /dev/null +++ b/onebusaway-gtfs-hibernate/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/agency.txt @@ -0,0 +1,2 @@ +agency_id,agency_name,agency_url,agency_timezone +agency,Fake Agency,http://fake.example.com,America/New_York diff --git a/onebusaway-gtfs-hibernate/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/icons.txt b/onebusaway-gtfs-hibernate/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/icons.txt new file mode 100644 index 000000000..7a679e8ab --- /dev/null +++ b/onebusaway-gtfs-hibernate/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/icons.txt @@ -0,0 +1,3 @@ +icon_id,icon_description,icon_url +ICO,test icon,https://iconurl + diff --git a/onebusaway-gtfs-hibernate/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/vehicles.txt b/onebusaway-gtfs-hibernate/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/vehicles.txt new file mode 100644 index 000000000..e119eb1f6 --- /dev/null +++ b/onebusaway-gtfs-hibernate/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/vehicles.txt @@ -0,0 +1,3 @@ +vehicle_id,icon_id,bike_capacity,vehicle_description,door_count,door_width,low_floor,seated_capacity,standing_capacity,wheelchair_access +123,ICO,2,vehicle description,2,30 ft,0,40,20,yes +456,ICO,4,larger vehicle description,3,40 ft,0,65,40,no \ No newline at end of file diff --git a/onebusaway-gtfs-merge-cli/pom.xml b/onebusaway-gtfs-merge-cli/pom.xml index 564ee0151..6853fe000 100644 --- a/onebusaway-gtfs-merge-cli/pom.xml +++ b/onebusaway-gtfs-merge-cli/pom.xml @@ -3,19 +3,13 @@ onebusaway-gtfs-modules org.onebusaway - 1.4.15-openmove-5 + 5.0.1-openmove-1 .. onebusaway-gtfs-merge-cli onebusaway-gtfs-merge-cli Command-line interface to the GTFS merge tool. - - - false - - org.onebusaway @@ -30,7 +24,7 @@ org.slf4j slf4j-simple - ${slf4j_version} + ${slf4j.version} @@ -56,23 +50,7 @@ - - org.apache.maven.plugins - maven-deploy-plugin - - ${skip-deploy-onebusaway-gtfs-merge-cli-jar} - - - - - oss-distribution - - true - - - - diff --git a/onebusaway-gtfs-merge-cli/src/site/site.xml b/onebusaway-gtfs-merge-cli/src/site/site.xml deleted file mode 100644 index edd5d57d6..000000000 --- a/onebusaway-gtfs-merge-cli/src/site/site.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/onebusaway-gtfs-merge/pom.xml b/onebusaway-gtfs-merge/pom.xml index 30d9eda59..80355d58c 100644 --- a/onebusaway-gtfs-merge/pom.xml +++ b/onebusaway-gtfs-merge/pom.xml @@ -3,7 +3,7 @@ onebusaway-gtfs-modules org.onebusaway - 1.4.15-openmove-5 + 5.0.1-openmove-1 .. onebusaway-gtfs-merge @@ -19,18 +19,17 @@ org.onebusaway onebusaway-collections + ${project.version} - - org.slf4j slf4j-simple - ${slf4j_version} + ${slf4j.version} + test - junit - junit - test + org.junit.jupiter + junit-jupiter-api org.mockito @@ -38,4 +37,32 @@ test + + + + + com.google.cloud.tools + jib-maven-plugin + + + true + + + + + io.github.zlika + reproducible-build-maven-plugin + 0.17 + + + run-when-packaged + + strip-jar + + package + + + + + diff --git a/onebusaway-gtfs-merge/src/site/site.xml b/onebusaway-gtfs-merge/src/site/site.xml deleted file mode 100644 index 8cec22b54..000000000 --- a/onebusaway-gtfs-merge/src/site/site.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/onebusaway-gtfs-merge/src/test/java/org/onebusaway/gtfs_merge/GtfsMergerTest.java b/onebusaway-gtfs-merge/src/test/java/org/onebusaway/gtfs_merge/GtfsMergerTest.java index 5beb27339..2344f6e5c 100644 --- a/onebusaway-gtfs-merge/src/test/java/org/onebusaway/gtfs_merge/GtfsMergerTest.java +++ b/onebusaway-gtfs-merge/src/test/java/org/onebusaway/gtfs_merge/GtfsMergerTest.java @@ -15,23 +15,20 @@ */ package org.onebusaway.gtfs_merge; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.impl.GtfsRelationalDaoImpl; -import org.onebusaway.gtfs.model.Agency; -import org.onebusaway.gtfs.model.Route; -import org.onebusaway.gtfs.model.ServiceCalendar; -import org.onebusaway.gtfs.model.Stop; -import org.onebusaway.gtfs.model.Trip; +import org.onebusaway.gtfs.model.*; import org.onebusaway.gtfs.serialization.GtfsReader; import org.onebusaway.gtfs.services.GtfsRelationalDao; import org.onebusaway.gtfs.services.MockGtfs; @@ -59,7 +56,7 @@ public class GtfsMergerTest { private GtfsMerger _merger; - @Before + @BeforeEach public void before() throws IOException { _oldGtfs = MockGtfs.create(); _newGtfs = MockGtfs.create(); @@ -68,7 +65,7 @@ public void before() throws IOException { _merger = new GtfsMerger(); } - @After + @AfterEach public void after() { } @@ -227,7 +224,7 @@ public void testAgencyPreference() throws IOException { pugetStopFound = true; } } - assertTrue("expect a puget stop", pugetStopFound); + assertTrue(pugetStopFound, "expect a puget stop"); } @Test @@ -355,9 +352,115 @@ public void testRenameStrategy() throws IOException { assertTrue("b-sid0".matches("[a-j]-.*")); - assertTrue("expect a puget stop", pugetStopFound); + assertTrue(pugetStopFound, "expect a puget stop"); } +// tests stop, location, and location group + @Test + public void testStopTimeProxies() throws IOException { + // lowest priority feed (first) to highest priority feed (last) + _oldGtfs.putLines("agency.txt", "agency_id,agency_name,agency_url,agency_timezone", + "3,Pierce,http://p.us/,America/Los_Angeles"); + _oldGtfs.putLines("routes.txt", + "route_id,route_short_name,route_long_name,route_type", + "R10,10,The Pierce Ten,3"); + _oldGtfs.putLines("stops.txt", "stop_id,stop_name,stop_lat,stop_lon", + "100,The Stop,47.654403,-122.305211", + "200,Pierce Other Stop,47.668594,-122.298859", + "400,Pierce Only Stop,47.669563,-122.305420"); + _oldGtfs.putLines( + "calendars.txt", + "service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date", + "sid0,1,1,1,1,1,0,0,20110101,20111231"); + _oldGtfs.putLines("trips.txt", "route_id,service_id,trip_id", + "R10,sid0,T10-0", + "R10,sid0,T10-1"); + _oldGtfs.putStopTimes("T10-0", "100,200"); // stop conflict only + _oldGtfs.putStopTimes("T10-1", "100,400"); + _oldGtfs.putLines("location_groups.txt","location_group_id","3", + "33"); + _oldGtfs.putLines("stop_times.txt", + "trip_id,stop_id,stop_sequence,arrival_time,departure_time,location_group_id", + "T10-0,100,0,08:00:00,08:00:00,3", + "T10-0,200,1,09:00:00,09:00:00,33", + "T10-1,100,1,08:00:00,08:00:00,", + "T10-1,400,1,09:00:00,09:00:00,"); + + + _newGtfs.putLines("agency.txt", "agency_id,agency_name,agency_url,agency_timezone", + "1,Metro,http://metro.gov/,America/Los_Angeles", + "3,Pierce,http://p.us/,America/Los_Angeles"); + _newGtfs.putLines("routes.txt", + "agency_id,route_id,route_short_name,route_long_name,route_type", + "1,R10,10,The KCM Ten,3"); + _newGtfs.putLines("stops.txt", "stop_id,stop_name,stop_lat,stop_lon", + "100,The Stop,47.654403,-122.305211", + "200,The Other Stop,47.656303,-122.315436", + "300,The Third Stop,47.668575,-122.283653"); + _newGtfs.putCalendars(1, "mask=1111100", "start_date=20120504", + "end_date=20120608"); + _newGtfs.putLines("trips.txt", "route_id,service_id,trip_id", "R10,sid0,T10-0"); + _newGtfs.putLines("stop_times.txt", + "trip_id,stop_id,stop_sequence,arrival_time,departure_time", + "T10-0,100,0,08:00:00,08:00:00", + "T10-0,200,1,09:00:00,09:00:00", + "T10-0,300,1,10:00:00,10:00:00"); + + _pugetGtfs = MockGtfs.create(); + _pugetGtfs.putLines("agency.txt", "agency_id,agency_name,agency_url,agency_timezone", + "0,Puget Sound Region,http://puget-sound.gov/,America/Los_Angeles"); + _pugetGtfs.putLines("routes.txt", + "route_id,route_short_name,route_long_name,route_type", + "r0,,,3"); + _pugetGtfs.putLines("stops.txt", "stop_id,stop_name,stop_lat,stop_lon",""); + _pugetGtfs.putLines( + "calendars.txt", + "service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date", + "sid0,1,1,1,1,1,0,0,20110101,20111231"); + _pugetGtfs.putCalendars(1, "mask=1111100", "start_date=20120504", + "end_date=20120608"); + _pugetGtfs.putLines("trips.txt", "route_id,service_id,trip_id", + "r0,sid0,t0"); + _pugetGtfs.putLines("locations.geojson", + "{ \"type\": \"FeatureCollection\",", + " \"features\": [", + " { \"type\": \"Feature\",", + " \"id\":\"s0\",", + " \"geometry\": {", + " \"type\": \"Polygon\",", + " \"coordinates\": [", + " [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0],", + " [100.0, 1.0], [100.0, 0.0] ]", + " ]", + "", + " },", + " \"properties\": {", + " }", + " }", + " ]", + " }"); + _pugetGtfs.putLines("stop_times.txt", + "trip_id,stop_id,stop_sequence,arrival_time,departure_time", + "t0,s0,0,01:00:00,01:00:03"); + + + + GtfsRelationalDao dao = merge(); + + // make sure all stoptimes only have stop_id, location_id, or location_group_id + Iterator itt = dao.getAllStopTimes().iterator(); + while(itt.hasNext()){ + StopTime st = itt.next(); + boolean hasST = st.getStop()!=null; + boolean hasLoc = st.getLocation()!=null; + boolean hasLocGroup = st.getLocationGroup()!=null; + assertTrue( !(hasST & hasLoc | hasST && hasLocGroup | hasLoc & hasLocGroup), + "multiple ids found for stop: "+st.getStop()+ + ", location_id: "+st.getLocation()+ + ", location_id: "+st.getLocationGroup() + ); + } + } private GtfsRelationalDao merge() throws IOException { List paths = new ArrayList(); diff --git a/onebusaway-gtfs-merge/src/test/java/org/onebusaway/gtfs_merge/MergeExpectedFilesTest.java b/onebusaway-gtfs-merge/src/test/java/org/onebusaway/gtfs_merge/MergeExpectedFilesTest.java index 02be0698d..9246dda1f 100644 --- a/onebusaway-gtfs-merge/src/test/java/org/onebusaway/gtfs_merge/MergeExpectedFilesTest.java +++ b/onebusaway-gtfs-merge/src/test/java/org/onebusaway/gtfs_merge/MergeExpectedFilesTest.java @@ -15,9 +15,9 @@ */ package org.onebusaway.gtfs_merge; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.impl.FileSupport; import org.onebusaway.gtfs.impl.ZipHandler; @@ -29,7 +29,7 @@ import java.util.List; import java.util.stream.Collectors; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Test appending metadata inputs as part of merge. @@ -40,12 +40,12 @@ public class MergeExpectedFilesTest { private FileSupport _support = new FileSupport(); - @Before + @BeforeEach public void before() throws IOException { _merger = new GtfsMerger(); } - @After + @AfterEach public void after() { _support.cleanup(); } @@ -68,8 +68,8 @@ public void testDirectoryMerge() throws Exception { String modLocation = gtfsDirectory.getAbsolutePath() + File.separator + "modifications.txt"; File expectedFile = new File(modLocation); // verify modifications.txt is there!!!! - assertTrue("expected modifications.txt to be present!", expectedFile.exists()); - assertTrue("expected modifications.txt to be a file!", expectedFile.isFile()); + assertTrue(expectedFile.exists(), "expected modifications.txt to be present!"); + assertTrue(expectedFile.isFile(), "expected modifications.txt to be a file!"); StringBuffer sb = new StringBuffer(); BufferedReader br = new BufferedReader(new FileReader(expectedFile)); sb.append(br.lines().collect(Collectors.joining(System.lineSeparator()))); diff --git a/onebusaway-gtfs-merge/src/test/java/org/onebusaway/gtfs_merge/strategies/AgencyMergeStrategyTest.java b/onebusaway-gtfs-merge/src/test/java/org/onebusaway/gtfs_merge/strategies/AgencyMergeStrategyTest.java index 621f20889..23e793845 100644 --- a/onebusaway-gtfs-merge/src/test/java/org/onebusaway/gtfs_merge/strategies/AgencyMergeStrategyTest.java +++ b/onebusaway-gtfs-merge/src/test/java/org/onebusaway/gtfs_merge/strategies/AgencyMergeStrategyTest.java @@ -15,13 +15,13 @@ */ package org.onebusaway.gtfs_merge.strategies; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; import java.util.Collection; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.impl.GtfsRelationalDaoImpl; import org.onebusaway.gtfs.model.Agency; import org.onebusaway.gtfs.model.AgencyAndId; @@ -40,7 +40,7 @@ public class AgencyMergeStrategyTest extends EntityMergeTestSupport { private GtfsRelationalDaoImpl _target; - @Before + @BeforeEach public void before() { _strategy = new AgencyMergeStrategy(); _target = new GtfsRelationalDaoImpl(); diff --git a/onebusaway-gtfs-merge/src/test/java/org/onebusaway/gtfs_merge/strategies/EntityMergeTestSupport.java b/onebusaway-gtfs-merge/src/test/java/org/onebusaway/gtfs_merge/strategies/EntityMergeTestSupport.java index d1572930f..af128f1f6 100644 --- a/onebusaway-gtfs-merge/src/test/java/org/onebusaway/gtfs_merge/strategies/EntityMergeTestSupport.java +++ b/onebusaway-gtfs-merge/src/test/java/org/onebusaway/gtfs_merge/strategies/EntityMergeTestSupport.java @@ -18,7 +18,7 @@ import java.util.HashMap; import java.util.Map; -import org.junit.Before; +import org.junit.jupiter.api.BeforeEach; import org.onebusaway.gtfs.impl.GtfsRelationalDaoImpl; import org.onebusaway.gtfs_merge.GtfsMergeContext; @@ -26,7 +26,7 @@ public class EntityMergeTestSupport { protected Map entityByRawId; - @Before + @BeforeEach public void setup() { entityByRawId = new HashMap(); } diff --git a/onebusaway-gtfs-merge/src/test/java/org/onebusaway/gtfs_merge/strategies/IntegrationTest.java b/onebusaway-gtfs-merge/src/test/java/org/onebusaway/gtfs_merge/strategies/IntegrationTest.java index fe37d8271..e6655f291 100644 --- a/onebusaway-gtfs-merge/src/test/java/org/onebusaway/gtfs_merge/strategies/IntegrationTest.java +++ b/onebusaway-gtfs-merge/src/test/java/org/onebusaway/gtfs_merge/strategies/IntegrationTest.java @@ -17,7 +17,7 @@ import java.io.IOException; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.services.MockGtfs; public class IntegrationTest { diff --git a/onebusaway-gtfs-merge/src/test/java/org/onebusaway/gtfs_merge/strategies/TripMergeStrategyTest.java b/onebusaway-gtfs-merge/src/test/java/org/onebusaway/gtfs_merge/strategies/TripMergeStrategyTest.java index 5afe3a801..e1e10013a 100644 --- a/onebusaway-gtfs-merge/src/test/java/org/onebusaway/gtfs_merge/strategies/TripMergeStrategyTest.java +++ b/onebusaway-gtfs-merge/src/test/java/org/onebusaway/gtfs_merge/strategies/TripMergeStrategyTest.java @@ -15,24 +15,8 @@ */ package org.onebusaway.gtfs_merge.strategies; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; - -import java.util.Collection; - -import org.junit.Before; -import org.junit.Test; -import org.onebusaway.gtfs.impl.GtfsRelationalDaoImpl; -import org.onebusaway.gtfs.model.Agency; -import org.onebusaway.gtfs.model.AgencyAndId; -import org.onebusaway.gtfs.model.FareAttribute; -import org.onebusaway.gtfs.model.Route; -import org.onebusaway.gtfs.model.ServiceCalendar; -import org.onebusaway.gtfs.model.ServiceCalendarDate; -import org.onebusaway.gtfs.model.ShapePoint; -import org.onebusaway.gtfs.model.Stop; -import org.onebusaway.gtfs.model.Trip; -import org.onebusaway.gtfs_merge.GtfsMergeContext; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; public class TripMergeStrategyTest extends EntityMergeTestSupport { diff --git a/onebusaway-gtfs-merge/src/test/java/org/onebusaway/gtfs_merge/strategies/scoring/DuplicateScoringSupportTest.java b/onebusaway-gtfs-merge/src/test/java/org/onebusaway/gtfs_merge/strategies/scoring/DuplicateScoringSupportTest.java index e6e3008e2..63e0cb2b7 100644 --- a/onebusaway-gtfs-merge/src/test/java/org/onebusaway/gtfs_merge/strategies/scoring/DuplicateScoringSupportTest.java +++ b/onebusaway-gtfs-merge/src/test/java/org/onebusaway/gtfs_merge/strategies/scoring/DuplicateScoringSupportTest.java @@ -15,12 +15,12 @@ */ package org.onebusaway.gtfs_merge.strategies.scoring; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.HashSet; import java.util.Set; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class DuplicateScoringSupportTest { diff --git a/onebusaway-gtfs-transformer-cli/pom.xml b/onebusaway-gtfs-transformer-cli/pom.xml index 1cf1248d0..4038b147d 100644 --- a/onebusaway-gtfs-transformer-cli/pom.xml +++ b/onebusaway-gtfs-transformer-cli/pom.xml @@ -9,15 +9,9 @@ org.onebusaway onebusaway-gtfs-modules - 1.4.15-openmove-5 + 5.0.1-openmove-1 - - - false - - org.onebusaway @@ -32,12 +26,12 @@ org.slf4j slf4j-simple - ${slf4j_version} + ${slf4j.version} - - junit - junit + org.junit.jupiter + junit-jupiter-api + test @@ -67,20 +61,8 @@ org.apache.maven.plugins maven-deploy-plugin - - ${skip-deploy-onebusaway-gtfs-transformer-cli-jar} - - - - oss-distribution - - true - - - - diff --git a/onebusaway-gtfs-transformer-cli/src/main/java/org/onebusaway/gtfs_transformer/GtfsTransformerMain.java b/onebusaway-gtfs-transformer-cli/src/main/java/org/onebusaway/gtfs_transformer/GtfsTransformerMain.java index 0ea998428..a1e107933 100644 --- a/onebusaway-gtfs-transformer-cli/src/main/java/org/onebusaway/gtfs_transformer/GtfsTransformerMain.java +++ b/onebusaway-gtfs-transformer-cli/src/main/java/org/onebusaway/gtfs_transformer/GtfsTransformerMain.java @@ -20,8 +20,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -45,7 +45,7 @@ public class GtfsTransformerMain { - private static Logger _log = LoggerFactory.getLogger(GtfsTransformerMain.class); + private static final Logger LOG = LoggerFactory.getLogger(GtfsTransformerMain.class); /**** * Generic Arguments @@ -87,9 +87,9 @@ public class GtfsTransformerMain { private static final String ARG_OVERWRITE_DUPLICATES = "overwriteDuplicates"; - private static CommandLineParser _parser = new PosixParser(); + private static final CommandLineParser parser = new PosixParser(); - private Options _options = new Options(); + private final Options options = new Options(); public static void main(String[] args) throws IOException { GtfsTransformerMain m = new GtfsTransformerMain(); @@ -98,7 +98,7 @@ public static void main(String[] args) throws IOException { public GtfsTransformerMain() { - buildOptions(_options); + buildOptions(options); } /***************************************************************************** @@ -113,13 +113,9 @@ public void run(String[] args) throws IOException { } try { - CommandLine cli = _parser.parse(_options, args, true); + CommandLine cli = parser.parse(options, args, true); runApplication(cli, args); - } catch (MissingOptionException ex) { - System.err.println("Missing argument: " + ex.getMessage()); - printHelp(); - System.exit(-2); - } catch (MissingArgumentException ex) { + } catch (MissingOptionException | MissingArgumentException ex) { System.err.println("Missing argument: " + ex.getMessage()); printHelp(); System.exit(-2); @@ -177,11 +173,11 @@ protected void buildOptions(Options options) { "overwrite duplicate elements"); } - protected void printHelp(PrintWriter out, Options options) throws IOException { + private void printHelp() throws IOException { InputStream is = getClass().getResourceAsStream("usage.txt"); BufferedReader reader = new BufferedReader(new InputStreamReader(is)); - String line = null; + String line; while ((line = reader.readLine()) != null) { System.err.println(line); @@ -193,22 +189,22 @@ protected void printHelp(PrintWriter out, Options options) throws IOException { protected void runApplication(CommandLine cli, String[] originalArgs) throws Exception { - String[] args = cli.getArgs(); + var args = Arrays.stream(cli.getArgs()).toList(); - if (args.length < 2) { + if (args.size() < 2) { printHelp(); System.exit(-1); } - List paths = new ArrayList(); - for (int i = 0; i < args.length - 1; ++i) { - paths.add(new File(args[i])); - _log.info("input path: " + args[i]); - } + List inputPaths = args.stream().limit(args.size() - 1).map(File::new).toList(); + LOG.info("input paths: {}", inputPaths); + GtfsTransformer transformer = new GtfsTransformer(); - transformer.setGtfsInputDirectories(paths); - transformer.setOutputDirectory(new File(args[args.length - 1])); - _log.info("output path: " + args[args.length - 1]); + transformer.setGtfsInputDirectories(inputPaths); + + var outputPath = new File(args.get(args.size() - 1)); + transformer.setOutputDirectory(outputPath); + LOG.info("output path: {}", outputPath); Option[] options = getOptionsInCommandLineOrder(cli, originalArgs); @@ -371,14 +367,6 @@ private void configureVerifyRoutesFile(GtfsTransformer updater, String file) { updater.addParameter("verifyRoutesFile", file); } - /***************************************************************************** - * Protected Methods - ****************************************************************************/ - - protected void printHelp() throws IOException { - printHelp(new PrintWriter(System.err, true), _options); - } - private boolean needsHelp(String[] args) { for (String arg : args) { if (arg.equals("-h") || arg.equals("--help") || arg.equals("-help")) @@ -389,9 +377,9 @@ private boolean needsHelp(String[] args) { private static class Ordered implements Comparable> { - private T _object; + private final T _object; - private int _order; + private final int _order; public Ordered(T object, int order) { _object = object; diff --git a/onebusaway-gtfs-transformer-cli/src/site/site.xml b/onebusaway-gtfs-transformer-cli/src/site/site.xml deleted file mode 100644 index 2cc85caf3..000000000 --- a/onebusaway-gtfs-transformer-cli/src/site/site.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/onebusaway-gtfs-transformer/pom.xml b/onebusaway-gtfs-transformer/pom.xml index 1f2a73494..7e8c6a3c1 100644 --- a/onebusaway-gtfs-transformer/pom.xml +++ b/onebusaway-gtfs-transformer/pom.xml @@ -9,34 +9,47 @@ org.onebusaway onebusaway-gtfs-modules - 1.4.15-openmove-5 + 5.0.1-openmove-1 + + + central2 + Check central first to avoid a lot of not found warnings + https://repo.maven.apache.org/maven2 + + + releases-camsys-public-repo + https://repo.camsys-apps.com/releases/ + + + org.onebusaway onebusaway-gtfs - 1.4.15-openmove-5 + ${project.version} org.onebusaway onebusaway-collections + ${project.version} org.json json - 20090211 + 20250107 + + + org.junit.jupiter + junit-jupiter-api - junit - junit + org.slf4j + slf4j-simple + ${slf4j.version} test - - org.slf4j - slf4j-simple - ${slf4j_version} - org.mockito mockito-core @@ -44,56 +57,59 @@ - com.kurtraschke - wsf-api - 1.0 + org.onebusaway + onebusaway-cloud-api + ${onebusaway.cloud.version} - - org.onebusaway - onebusaway-cloud-api - ${onebusaway_cloud_version} - org.onebusaway onebusaway-cloud-noop - ${onebusaway_cloud_version} + ${onebusaway.cloud.version} + + + javax.xml.bind + jaxb-api + + + com.sun.xml.bind + jaxb-core + + + com.sun.xml.bind + jaxb-impl + + + org.locationtech.jts + jts-core + 1.20.0 - - javax.xml.bind - jaxb-api - - - com.sun.xml.bind - jaxb-core - - - com.sun.xml.bind - jaxb-impl - - onebusaway-gtfs-transformer - - com.mycila - license-maven-plugin - - - src/test/resources/org/onebusaway/gtfs_transformer/** - - + + com.google.cloud.tools + jib-maven-plugin + + + true + + + + + io.github.zlika + reproducible-build-maven-plugin + 0.17 + + + run-when-packaged + + strip-jar + + package + + - - - org.apache.maven.plugins - maven-compiler-plugin - - 1.8 - 1.8 - - - diff --git a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/RemoveOldCalendarStatements.java b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/RemoveOldCalendarStatements.java index 434672c77..38f369b69 100644 --- a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/RemoveOldCalendarStatements.java +++ b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/RemoveOldCalendarStatements.java @@ -15,19 +15,33 @@ */ package org.onebusaway.gtfs_transformer.impl; +import java.util.HashSet; +import java.util.Set; + +import org.onebusaway.csv_entities.schema.annotations.CsvField; import org.onebusaway.gtfs.model.ServiceCalendar; +import org.onebusaway.gtfs.model.ServiceCalendarDate; import org.onebusaway.gtfs.services.GtfsMutableRelationalDao; import org.onebusaway.gtfs_transformer.services.GtfsTransformStrategy; import org.onebusaway.gtfs_transformer.services.TransformContext; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.HashSet; -import java.util.Set; +import org.onebusaway.gtfs_transformer.util.CalendarFunctions; +/** + * Remove calendar dates that are past. + * + * remove_today: delete today's calendar dates if true. Default value is false + */ public class RemoveOldCalendarStatements implements GtfsTransformStrategy { + @CsvField(optional = true) + private boolean removeToday = false; + + @CsvField(ignore = true) + private CalendarFunctions helper = new CalendarFunctions(); + + public void setRemoveToday(boolean removeToday) { + this.removeToday = removeToday; + } - private final Logger _log = LoggerFactory.getLogger(RemoveOldCalendarStatements.class); @Override public String getName() { return this.getClass().getSimpleName(); @@ -37,14 +51,30 @@ public String getName() { public void run(TransformContext transformContext, GtfsMutableRelationalDao gtfsMutableRelationalDao) { RemoveEntityLibrary removeEntityLibrary = new RemoveEntityLibrary(); Set serviceCalendarsToRemove = new HashSet(); - for (ServiceCalendar calendar: gtfsMutableRelationalDao.getAllCalendars()) { - java.util.Date today = new java.util.Date(); - if (calendar.getEndDate().getAsDate().before(today)){ + java.util.Date today = new java.util.Date(); + + if (!removeToday) { + today = helper.removeTime(today); + } + + for (ServiceCalendar calendar : gtfsMutableRelationalDao.getAllCalendars()) { + if (calendar.getEndDate().getAsDate().before(today)) { serviceCalendarsToRemove.add(calendar); } } for (ServiceCalendar serviceCalendar : serviceCalendarsToRemove) { removeEntityLibrary.removeCalendar(gtfsMutableRelationalDao, serviceCalendar.getServiceId()); } + + Set serviceCalendarDatesToRemove = new HashSet(); + for (ServiceCalendarDate calendarDate : gtfsMutableRelationalDao.getAllCalendarDates()) { + if (calendarDate.getDate().getAsDate().before(today)) { + serviceCalendarDatesToRemove.add(calendarDate); + } + } + for (ServiceCalendarDate serviceCalendarDate : serviceCalendarDatesToRemove) { + // here we can't delete the trips as the serviceid may be active elsewhere + removeEntityLibrary.removeServiceCalendarDate(gtfsMutableRelationalDao, serviceCalendarDate); + } } } \ No newline at end of file diff --git a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/RetainUpFromPolygon.java b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/RetainUpFromPolygon.java new file mode 100644 index 000000000..08945a3de --- /dev/null +++ b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/RetainUpFromPolygon.java @@ -0,0 +1,100 @@ +package org.onebusaway.gtfs_transformer.impl; + +import java.util.ArrayList; +import java.util.List; + +import java.io.Serializable; + +import org.locationtech.jts.io.ParseException; +import org.locationtech.jts.io.WKTReader; +import org.locationtech.jts.geom.*; + +import org.onebusaway.csv_entities.schema.annotations.CsvField; +import org.onebusaway.gtfs.model.IdentityBean; +import org.onebusaway.gtfs.model.Stop; +import org.onebusaway.gtfs.serialization.GtfsEntitySchemaFactory; +import org.onebusaway.gtfs.services.GtfsMutableRelationalDao; +import org.onebusaway.gtfs_transformer.factory.EntityRetentionGraph; +import org.onebusaway.gtfs_transformer.services.GtfsTransformStrategy; +import org.onebusaway.gtfs_transformer.services.TransformContext; + +public class RetainUpFromPolygon implements GtfsTransformStrategy { + + private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory(); + private final WKTReader wktReader = new WKTReader(GEOMETRY_FACTORY); + + @CsvField(optional = false) + private String polygon; + + @CsvField(ignore = true) + private Geometry polygonGeometry; + + public void setPolygon(String polygon) { + this.polygon = polygon; + this.polygonGeometry = buildPolygon(polygon); + + if (this.polygonGeometry == null || !this.polygonGeometry.isValid() || this.polygonGeometry.isEmpty()) { + throw new IllegalArgumentException("The provided polygon is invalid or empty."); + } + } + + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public void run(TransformContext transformContext, GtfsMutableRelationalDao gtfsMutableRelationalDao) { + EntityRetentionGraph graph = new EntityRetentionGraph(gtfsMutableRelationalDao); + graph.setRetainBlocks(false); + // browse all stops and retain only those inside polygon/multipolygon + for (Stop stop : gtfsMutableRelationalDao.getAllStops()) { + if (insidePolygon(polygonGeometry,stop.getLon(),stop.getLat())){ + graph.retain(stop, true); + } + } + + // remove non retained objects + for (Class entityClass : GtfsEntitySchemaFactory.getEntityClasses()) { + List objectsToRemove = new ArrayList(); + for (Object entity : gtfsMutableRelationalDao.getAllEntitiesForType(entityClass)) { + if (!graph.isRetained(entity)){ + objectsToRemove.add(entity); + } + } + for (Object toRemove : objectsToRemove){ + gtfsMutableRelationalDao.removeEntity((IdentityBean) toRemove); + } + } + } + + /* + * Creates a Geometry object (polygon or multi-polygon) from the provided WKT string. + * + * @param polygonWKT The WKT representation of the polygon. + * @return The Geometry object. + * @throws IllegalArgumentException if the WKT string is invalid or cannot be parsed. + */ + private Geometry buildPolygon(String polygonWKT) { + try{ + return wktReader.read(polygonWKT); + } catch (ParseException e){ + throw new IllegalArgumentException( + String.format("Error parsing WKT string: %s", e.getMessage()), e + ); + } + } + /* + * insidePolygon Checks whether a given point (specified by its longitude and latitude) is inside a given polygon or multipolygon. + * + * @param geometry The Geometry object representing the polygon or multipolygon. + * @param lon the longitude of the point to check. + * @param lat the latitude of the point to check. + * @return true if the point is within the boundaries of the geometry; false otherwise. + */ + private boolean insidePolygon(Geometry geometry, double lon, double lat) { + Point point = GEOMETRY_FACTORY.createPoint(new Coordinate(lon, lat)); + return geometry.contains(point); + } + +} \ No newline at end of file diff --git a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/TrimTripFromPolygon.java b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/TrimTripFromPolygon.java new file mode 100644 index 000000000..98ba9e6b0 --- /dev/null +++ b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/TrimTripFromPolygon.java @@ -0,0 +1,138 @@ +package org.onebusaway.gtfs_transformer.impl; + +import java.util.ArrayList; +import java.util.List; + +import org.locationtech.jts.io.ParseException; +import org.locationtech.jts.io.WKTReader; +import org.locationtech.jts.geom.*; + +import org.onebusaway.collections.beans.PropertyPathExpression; +import org.onebusaway.csv_entities.schema.annotations.CsvField; +import org.onebusaway.gtfs.model.Stop; +import org.onebusaway.gtfs.model.StopTime; +import org.onebusaway.gtfs.model.Trip; +import org.onebusaway.gtfs.services.GtfsMutableRelationalDao; +import org.onebusaway.gtfs_transformer.services.GtfsTransformStrategy; +import org.onebusaway.gtfs_transformer.services.TransformContext; +import org.onebusaway.gtfs_transformer.updates.TrimTripTransformStrategy; +import org.onebusaway.gtfs_transformer.updates.TrimTripTransformStrategy.TrimOperation; +import org.onebusaway.gtfs_transformer.match.PropertyValueEntityMatch; +import org.onebusaway.gtfs_transformer.match.SimpleValueMatcher; +import org.onebusaway.gtfs_transformer.match.TypedEntityMatch; + +public class TrimTripFromPolygon implements GtfsTransformStrategy { + private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory(); + private final WKTReader wktReader = new WKTReader(GEOMETRY_FACTORY); + + private final TrimTripTransformStrategy strategy = new TrimTripTransformStrategy(); + private final TransformContext context = new TransformContext(); + + @CsvField(optional = false) + private String polygon; + + @CsvField(ignore = true) + private Geometry polygonGeometry; + + public void setPolygon(String polygon) { + this.polygon = polygon; + this.polygonGeometry = buildPolygon(polygon); + + if (this.polygonGeometry == null || !this.polygonGeometry.isValid() || this.polygonGeometry.isEmpty()) { + throw new IllegalArgumentException("The provided polygon is invalid or empty."); + } + } + + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public void run(TransformContext transformContext, GtfsMutableRelationalDao gtfsMutableRelationalDao) { + + for (Trip trip : gtfsMutableRelationalDao.getAllTrips()){ + // retrieve the list of StopTimes for the current trip + List stopTimes = gtfsMutableRelationalDao.getStopTimesForTrip(trip); + List stopTimesInPolygon = new ArrayList<>(); + + for (StopTime stopTime : stopTimes) { + Stop stop = gtfsMutableRelationalDao.getStopForId(stopTime.getStop().getId()); + + // add StopTime to the list if its Stop is within the polygon boundaries + if (insidePolygon(polygonGeometry, stop.getLon(), stop.getLat())) { + stopTimesInPolygon.add(stopTime); + } + } + + // if some stops are inside the polygon but not all, apply the TrimTrip operation + if (!stopTimesInPolygon.isEmpty() && stopTimesInPolygon.size() < stopTimes.size()) { + applyTrimOperation(gtfsMutableRelationalDao,trip, stopTimesInPolygon); + } + } + // execute the TrimTrip transformation strategy + strategy.run(context, gtfsMutableRelationalDao); + } + + private void applyTrimOperation(GtfsMutableRelationalDao gtfs, Trip trip, List stopTimes) { + // initialize a new TrimOperation object to define parameters + TrimOperation operation = new TrimOperation(); + + StopTime firstStopTime = stopTimes.get(0); + StopTime lastStopTime = stopTimes.get(stopTimes.size() - 1); + + // set 'ToStopId' of the trim operation if the first StopTime is not the first stopTime in the trip + if (firstStopTime.getStopSequence() > 0) { + StopTime previousStop = gtfs.getStopTimesForTrip(trip).get(firstStopTime.getStopSequence() - 1); + operation.setToStopId(previousStop.getStop().getId().getId()); + } + + // set 'FromStopId' of the trim operation if the last StopTime is not the last stopTime in the trip + if (lastStopTime.getStopSequence() < (gtfs.getStopTimesForTrip(trip).size()-1)) { + StopTime nextStop = gtfs.getStopTimesForTrip(trip).get(lastStopTime.getStopSequence() + 1); + operation.setFromStopId(nextStop.getStop().getId().getId()); + } + + // define the matching criteria + operation.setMatch(new TypedEntityMatch( + Trip.class, + new PropertyValueEntityMatch( + new PropertyPathExpression("id"), + new SimpleValueMatcher(trip.getId()) + ) + )); + + // add the TrimOperation to the strategy for later execution + strategy.addOperation(operation); + } + + /* + * Creates a Geometry object (polygon or multi-polygon) from the provided WKT string. + * + * @param polygonWKT The WKT representation of the polygon. + * @return The Geometry object. + * @throws IllegalArgumentException if the WKT string is invalid or cannot be parsed. + */ + private Geometry buildPolygon(String polygonWKT) { + try{ + return wktReader.read(polygonWKT); + } catch (ParseException e){ + throw new IllegalArgumentException( + String.format("Error parsing WKT string: %s", e.getMessage()), e + ); + } + } + /* + * insidePolygon Checks whether a given point (specified by its longitude and latitude) is inside a given polygon or multipolygon. + * + * @param geometry The Geometry object representing the polygon or multipolygon. + * @param lon the longitude of the point to check. + * @param lat the latitude of the point to check. + * @return true if the point is within the boundaries of the geometry; false otherwise. + */ + private boolean insidePolygon(Geometry geometry, double lon, double lat) { + Point point = GEOMETRY_FACTORY.createPoint(new Coordinate(lon, lat)); + return geometry.contains(point); + } + +} \ No newline at end of file diff --git a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/TruncateNewCalendarStatements.java b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/TruncateNewCalendarStatements.java index ef2456493..c4c2bca26 100644 --- a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/TruncateNewCalendarStatements.java +++ b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/TruncateNewCalendarStatements.java @@ -15,6 +15,7 @@ */ package org.onebusaway.gtfs_transformer.impl; +import org.onebusaway.csv_entities.schema.annotations.CsvField; import org.onebusaway.gtfs.model.ServiceCalendar; import org.onebusaway.gtfs.model.ServiceCalendarDate; import org.onebusaway.gtfs.services.GtfsMutableRelationalDao; @@ -35,6 +36,31 @@ public class TruncateNewCalendarStatements implements GtfsTransformStrategy { private final Logger _log = LoggerFactory.getLogger(TruncateNewCalendarStatements.class); + + /* + * add two arguments used in the truncated transformation strategy + * calendarField --> calendar_field (json config file) + * Calendar.YEAR = 1 + * Calendar.MONTH = 2 + * Calendar.DAY_OF_MONTH = 5 + * Calendar.DAY_OF_YEAR = 6 + * calendarAmount --> calendar_amount (json config file) + */ + @CsvField(optional = true) + private int calendarField = Calendar.MONTH; + + @CsvField(optional = true) + private int calendarAmount = 1; + + + public void setCalendarField(int calendarField) { + this.calendarField = calendarField; + } + + public void setCalendarAmount(int calendarAmount) { + this.calendarAmount = calendarAmount; + } + @Override public String getName() { return this.getClass().getSimpleName(); @@ -43,9 +69,8 @@ public String getName() { @Override public void run(TransformContext transformContext, GtfsMutableRelationalDao gtfsMutableRelationalDao) { RemoveEntityLibrary removeEntityLibrary = new RemoveEntityLibrary(); - // TODO make this an argument -- default to one month from now Calendar c = Calendar.getInstance(); - c.roll(Calendar.MONTH, 1); + c.add(calendarField, calendarAmount); java.util.Date oneMonthFromNow = c.getTime(); Set serviceCalendarsToRemove = new HashSet(); for (ServiceCalendar calendar: gtfsMutableRelationalDao.getAllCalendars()) { diff --git a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/UpdateCalendarDatesForDuplicateTrips.java b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/UpdateCalendarDatesForDuplicateTrips.java index dea177d0b..c15b564d1 100644 --- a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/UpdateCalendarDatesForDuplicateTrips.java +++ b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/UpdateCalendarDatesForDuplicateTrips.java @@ -17,7 +17,6 @@ package org.onebusaway.gtfs_transformer.impl; import org.onebusaway.gtfs.model.*; -import org.onebusaway.gtfs.model.calendar.ServiceDate; import org.onebusaway.gtfs.services.GtfsMutableRelationalDao; import org.onebusaway.gtfs_transformer.services.GtfsTransformStrategy; import org.onebusaway.gtfs_transformer.services.TransformContext; @@ -25,7 +24,15 @@ import org.slf4j.LoggerFactory; import java.util.*; +import java.util.stream.Collectors; +/** + * Map ATIS trip_ids to mta_trips_ids while de-duplicating. + * Tag each "duplicate" trip with an ATIS id to force it unique if the stopping pattern differs. + * Otherwise create new service_ids representing the service of the duplicates, + * adding to an exemplar trip and deleting the duplicates. + * + */ public class UpdateCalendarDatesForDuplicateTrips implements GtfsTransformStrategy { private final Logger _log = LoggerFactory.getLogger(UpdateCalendarDatesForDuplicateTrips.class); @@ -35,307 +42,293 @@ public String getName() { return this.getClass().getSimpleName(); } - @Override public void run(TransformContext context, GtfsMutableRelationalDao dao) { - RemoveEntityLibrary removeEntityLibrary = new RemoveEntityLibrary(); - String agency = dao.getAllTrips().iterator().next().getId().getAgencyId(); + if (dao == null || dao.getAllTrips().isEmpty()) { + throw new IllegalStateException("nothing to do!"); + } + String calendarAgencyId = dao.getAllTrips().iterator().next().getId().getAgencyId(); + DuplicateState state = new DuplicateState(dao, calendarAgencyId); //map of each mta_trip_id and list of trips - HashMap> tripsMap = new HashMap<>(); - //List of DuplicateTrips - ArrayList duplicateTripData = new ArrayList<>(); + HashMap> tripsByMtaTripId = buildTripMap(state, dao); - //they are only duplicates if the stop times match as well. - //if the stop times match, then we can move forward with merging trips - //if not, then we can't merge and we leave the trips alone + // we only use this for informational logging, we don't actually compare to reference + GtfsMutableRelationalDao reference = (GtfsMutableRelationalDao) context.getReferenceReader().getEntityStore(); - //set all the trips that are duplicates based on mta_trip_id - int mtaIdNull = 0; - for (Trip trip : dao.getAllTrips()) { - if (trip.getMtaTripId() != null) { - if (tripsMap.containsKey(trip.getMtaTripId())) { - ArrayList trips = tripsMap.get(trip.getMtaTripId()); - trips.add(trip); - tripsMap.put(trip.getMtaTripId(), trips); - } else { - ArrayList trips = new ArrayList<>(); - trips.add(trip); - tripsMap.put(trip.getMtaTripId(), trips); - } - } else { - _log.info("trip {} mta_trip_id is null", trip.getId()); - mtaIdNull++; - } + HashMap referenceTripsByTripIdByTripId = new HashMap<>(); + for (Trip trip : reference.getAllTrips()) { + referenceTripsByTripIdByTripId.put(trip.getId().getId(), trip); } - GtfsMutableRelationalDao reference = (GtfsMutableRelationalDao) context.getReferenceReader().getEntityStore(); + logDuplicates(tripsByMtaTripId, referenceTripsByTripIdByTripId); - HashMap referenceTrips = new HashMap<>(); - for (Trip trip : reference.getAllTrips()) { - referenceTrips.put(trip.getId().getId(), trip); + _log.info("Incoming Routes: {} Trips: {} Stops: {} Stop times: {} CalDatess: {} ", dao.getAllRoutes().size(), dao.getAllTrips().size(), dao.getAllStops().size(), dao.getAllStopTimes().size(), dao.getAllCalendarDates().size()); + + // perform the computations, but application of them is delayed till later + update(dao, state, tripsByMtaTripId); + // apply the changes + state.apply(); + + _log.info("Outgoing Routes: {} Trips: {} Stops: {} Stop times: {} CalDates: {} ", dao.getAllRoutes().size(), dao.getAllTrips().size(), dao.getAllStops().size(), dao.getAllStopTimes().size(), dao.getAllCalendarDates().size()); + _log.info("deleted trips: {} duplicate trip Ids: {} null ids {}", state.deletedTripCounter, state.duplicateTripIdCounter, state.mtaIdNullCounter); + } + + private void update(GtfsMutableRelationalDao dao, DuplicateState state, HashMap> tripsByMtaTripId) { + for (Map.Entry> entry : tripsByMtaTripId.entrySet()) { + String mtaTripId = entry.getKey(); + ArrayList atisTrips = entry.getValue(); + update(state, mtaTripId, atisTrips); + deDuplicate(dao, state, mtaTripId, atisTrips); } + } - //this is just for logging if dups are in reference, delete when ready - /* Iterator entries2 = tripsMap.entrySet().iterator(); - while (entries2.hasNext()) { - HashMap.Entry entry = (HashMap.Entry) entries2.next(); - ArrayList trips = (ArrayList) entry.getValue(); - if (trips.size() > 1) { - //these are duplicates - if (referenceTrips.containsKey(entry.getKey())) { - //_log.info("Duplicate trip id {} is in reference", entry.getKey()); - } - } + private void update(DuplicateState state, String mtaTripId, ArrayList duplicateTrips) { + for (Trip duplicateTrip : duplicateTrips) { + String agencyId = duplicateTrip.getId().getAgencyId(); + String modifier = duplicateTrip.getId().getId(); + // if we change the id here we can't decide to delete it later + // instead map the change and do it later + state.addTripToTrack(duplicateTrip.getId(), new AgencyAndId(agencyId, mtaTripId + "-dup-" + modifier)); } -*/ - int orStopTimes = dao.getAllStopTimes().size(); - _log.info("Routes: {} Trips: {} Stops: {} Stop times: {} CalDatess: {} ", dao.getAllRoutes().size(), dao.getAllTrips().size(), dao.getAllStops().size(), dao.getAllStopTimes().size(), dao.getAllCalendarDates().size()); + } + + private void deDuplicate(GtfsMutableRelationalDao dao, DuplicateState state, String mtaTripId, ArrayList duplicateTrips) { + Map> patternHashToTripId = new HashMap<>(); + for (Trip duplicateTrip : duplicateTrips) { + String patternHash = hashPattern(dao, duplicateTrip); + if (!patternHashToTripId.containsKey(patternHash)) { + patternHashToTripId.put(patternHash, new ArrayList()); + } + patternHashToTripId.get(patternHash).add(duplicateTrip); + } - int countUnique = 0; - int countCombine = 0; - int countDoNothing = 0; - int countToday = 0; + deDuplicate(dao, state, mtaTripId, patternHashToTripId); + } - Iterator entries = tripsMap.entrySet().iterator(); - int service_id = getNextServiceId(dao); - while (entries.hasNext()) { - HashMap.Entry entry = (HashMap.Entry) entries.next(); - ArrayList trips = (ArrayList) entry.getValue(); + private void deDuplicate(GtfsMutableRelationalDao dao, DuplicateState state, String mtaTripId, Map> patternHashToTripId) { + // each pattern only needs one representative trip -- we don't care which -- and then multiple calendar entries + for (List trips : patternHashToTripId.values()) { if (trips.size() > 1) { - Boolean equals = true; - //do all the trips have identical stops? If yes, proceed and update calendar dates and stop times and the trip_id - //If not, leave the trip alone. Do nothing. - trip_loop: - for (int i = 0; i < trips.size(); i++) { - for (int j = i+1; j < trips.size(); j++) { - //if (!dao.getStopTimesForTrip(trips.get(i)).equals(dao.getStopTimesForTrip(trips.get(j))) ) { - //they won't be equal because a stop time has a trip id and the trip ids are different - if (!stopTimesEqual(dao.getStopTimesForTrip(trips.get(i)), dao.getStopTimesForTrip(trips.get(j)))) { - //_log.info("The stop times for {} and {} are not equal", trips.get(i).getId().getId(), trips.get(j).getId().getId()); - equals = false; - //so at this point the stop times don't equal. Do I check if its just one or just throw the whole thing out? - //For now if one doesn't match then none do and I'm going to ignore. - countDoNothing = countDoNothing + trips.size(); - - //check if any of the trips are today. If one of them is, then copy over the mta_id and ignore the duplicates - //so at least one trip will get the right id - if (checkForServiceToday(trips, dao)) { - countToday++; - } - break trip_loop; - } - } - } - if (equals) { - //_log.info("EQUALS!"); - //for each mta_id that is a duplicate, we need to ultimately delete those duplicates - //First, get all the corresponding serviceDates for all the trips with that mta_id, then create new service ids - //and add entries with that new id that correspond to all the service dates for all the trips - DuplicateTrips dup = new DuplicateTrips((String) entry.getKey(), Integer.toString(service_id), trips); - duplicateTripData.add(dup); - service_id++; - countCombine = countCombine + trips.size(); - } - } - else { - //trips.size is not > 1 so these trips are unique. Copy over mta_trip_id - Trip trip = trips.get(0); - trip.setId(new AgencyAndId(trip.getId().getAgencyId(), trip.getMtaTripId())); - countUnique++; + Trip exemplar = trips.remove(0); + deDuplicateTrip(dao, state, exemplar, trips); } } - _log.info("Mta_trip_ids: null {}, unique {}, do nothing {}, today {}, combine {}, total {}", mtaIdNull, countUnique, countDoNothing, countToday, countCombine, mtaIdNull+countUnique+countDoNothing+countCombine); - - //now we have a list of DuplicateTrips and we need to fill in the calendar dates - for (DuplicateTrips dts : duplicateTripData) { - for (Trip trip : dts.getTrips()) { - //for each trip, get the calendar dates - for (ServiceCalendarDate calDate : dao.getCalendarDatesForServiceId(trip.getServiceId())) { - dts.addServiceDate(calDate); + } + + private void deDuplicateTrip(GtfsMutableRelationalDao dao, DuplicateState state, Trip exemplar, List tripsToRemove) { + Set serviceIds = tripsToRemove.stream().map(l->l.getServiceId()).collect(Collectors.toSet()); + serviceIds.add(exemplar.getServiceId()); + addServiceForTrip(dao, state, exemplar, serviceIds); + deleteTrips(dao, state, tripsToRemove); + } + + private void deleteTrips(GtfsMutableRelationalDao dao, DuplicateState state, List tripsToRemove) { + state.removeTrip(tripsToRemove); + } + + private void addServiceForTrip(GtfsMutableRelationalDao dao, DuplicateState state, Trip exemplar, Set serviceIds) { + state.addTrip(exemplar, serviceIds); + } + + private String hashPattern(GtfsMutableRelationalDao dao, Trip duplicateTrip) { + StringBuffer sb = new StringBuffer(); + for (StopTime stopTime : dao.getStopTimesForTrip(duplicateTrip)) { + sb.append(stopTime.getStop().getId().getId()); + sb.append(":"); + sb.append(stopTime.getArrivalTime()); + sb.append(":"); + sb.append(stopTime.getDepartureTime()); + sb.append(":"); + } + if (sb.length() == 0) + return "empty"; // this is technically an error but support it just in case + return sb.substring(0, sb.length() - 1); + } + + // index ATIS trips by mta_trip_id + private HashMap> buildTripMap(DuplicateState state, GtfsMutableRelationalDao dao) { + HashMap> tripsByMtaTripId = new HashMap<>(); + + for (Trip trip : dao.getAllTrips()) { + if (trip.getMtaTripId() != null) { + if (!tripsByMtaTripId.containsKey(trip.getMtaTripId())) { + tripsByMtaTripId.put(trip.getMtaTripId(), new ArrayList<>()); } + tripsByMtaTripId.get(trip.getMtaTripId()).add(trip); + } else { + _log.info("trip {} mta_trip_id is null", trip.getId()); + state.mtaIdNullCounter++; } } - //now we have a list of DuplicateTrips and their calendar dates - //a lot of the DuplicateTrips will have the same list of calendar dates. Don't create duplicate calendar entries unnecessarily - - //Create a unique list of calendar dates to add - HashMap> dateMap = new HashMap<>(); - - //for each duplicateTrips in the list, get the list of caldate entries - //if the caldate entries is in the dateMap, change the Service Id for the duplicate trip - //if its not in there, then add it - int newDates = 0; - for (DuplicateTrips dts : duplicateTripData) { - //first time through, populate dateMap - if (dateMap.isEmpty()) { - dateMap.put(dts.getServiceId(), dts.getDates()); - } else { - boolean addNewDateMap = true; - for (HashMap.Entry> calDate : dateMap.entrySet()) { - ArrayList scds = (ArrayList) calDate.getValue(); - //scds is a unique list of service calendar dates in the map - if (new HashSet(dts.getDates()).equals(new HashSet(scds))) { - //we already have a list of the same dates. Re-use the service id - addNewDateMap = false; - //set the service date id in DuplicateTrips to be this one - dts.setServiceId(calDate.getKey()); - break; + + return tripsByMtaTripId; + } + + private void logDuplicates(HashMap> tripsByMtaTripId, HashMap referenceTripsByTripId) { + if (_log.isDebugEnabled()) { + //this is just for logging if dups are in reference + Iterator entries2 = tripsByMtaTripId.entrySet().iterator(); + while (entries2.hasNext()) { + HashMap.Entry entry = (HashMap.Entry) entries2.next(); + ArrayList trips = (ArrayList) entry.getValue(); + if (trips.size() > 1) { + //these are duplicates + if (referenceTripsByTripId.containsKey(entry.getKey())) { + _log.info("Duplicate trip id {} is in reference", entry.getKey()); } } - //there was no match, update the date map and add new serviceId - if (addNewDateMap) { - //dates don't exist, add new entry to date map and add service id - dateMap.put(dts.getServiceId(), dts.getDates()); - newDates = newDates + dts.getDates().size(); - } } } + } + - int serviceIds = 0; - //Now the list is compete, add the new service id and dates - for (HashMap.Entry> calDateId : dateMap.entrySet()) { - AgencyAndId newServiceId = new AgencyAndId(agency, calDateId.getKey()); - ArrayList scds = calDateId.getValue(); - //need a list of the service cal dates, iterate, add - for (ServiceCalendarDate calDate : scds) { - serviceIds++; - //for each date, create a new calendar_dates entry with the new service_id - ServiceCalendarDate newScd = new ServiceCalendarDate(); - newScd.setServiceId(newServiceId); - newScd.setDate(calDate.getDate()); - newScd.setExceptionType(calDate.getExceptionType()); - dao.saveOrUpdateEntity(newScd); + /** + * Internal state of the algorithm. + */ + private static class DuplicateState { + private int mtaIdNullCounter = 0; + private int serviceIdCounter = 0; + private int duplicateTripIdCounter = 0; + private int deletedTripCounter = 0; + private Map, List> tripsByServiceIds = new HashMap<>(); + private List tripsToRemove = new ArrayList<>(); + private GtfsMutableRelationalDao dao; + private String calendarAgencyId; + private Map atisToMtaTripId = new HashMap<>(); + + public DuplicateState(GtfsMutableRelationalDao dao, String calendarAgencyId) { + this.dao = dao; + this.calendarAgencyId = calendarAgencyId; + String largestServiceId = Collections.max(dao.getAllCalendarDates().stream().map(l->l.getServiceId().getId()).collect(Collectors.toSet())); + // remove any de-duplication prefix from the service id + largestServiceId = largestServiceId.replaceAll("^[a-z]-", ""); + try { + // here we make an assumption that service ids are numeric + serviceIdCounter = Integer.parseInt(largestServiceId) + 1; + } catch (NumberFormatException e) { + // we guessed wrong, we have some string service ids + // create a higher order service_id that should not conflict + serviceIdCounter = 10000; } } - //trips updated, array of mta_ids that we've updated - HashMap tripsUpdated = new HashMap<>(); - ArrayList tripsToRemove = new ArrayList<>(); - - //update the trips with the new service_id - for (DuplicateTrips dts : duplicateTripData) { - AgencyAndId newServiceId = new AgencyAndId(agency, dts.getServiceId()); - for (Trip trip : dts.getTrips()) { - //for each trip, set the new service id - trip.setServiceId(newServiceId); - //now the trip_id has to be set with the mta_trip_id - //we have to have one as the one to keep and mark the others for deletion - //and then there needs to be a seperate method for all the deletions. - if (trip.getMtaTripId() != null) { - if (tripsUpdated.containsKey(trip.getMtaTripId())) { - tripsToRemove.add(trip); - } else { - tripsUpdated.put(trip.getMtaTripId(), trip); - trip.setId(new AgencyAndId(trip.getId().getAgencyId(), trip.getMtaTripId())); - } - } + public void addTrip(Trip exemplar, Set serviceIds) { + if (!tripsByServiceIds.containsKey(serviceIds)) { + tripsByServiceIds.put(serviceIds, new ArrayList<>()); } + tripsByServiceIds.get(serviceIds).add(exemplar); } - int stopsTimesToRemove = 0; - int remove = 0; - for (Trip tripToRemove : tripsToRemove) { - stopsTimesToRemove = stopsTimesToRemove + dao.getStopTimesForTrip(tripToRemove).size(); - removeEntityLibrary.removeTrip(dao, tripToRemove); - remove++; + public void removeTrip(List incoming) { + tripsToRemove.addAll(incoming); } - _log.info("Added Service Cal dates: {}, Removed trips: {}, Removed stoptimes: {}", serviceIds, remove, stopsTimesToRemove); - _log.info("Routes: {} Trips: {} Stops: {} Stop times: {} CalDates: {} ", dao.getAllRoutes().size(), dao.getAllTrips().size(), dao.getAllStops().size(), dao.getAllStopTimes().size(), dao.getAllCalendarDates().size()); - } - - private boolean checkForServiceToday(ArrayList trips, GtfsMutableRelationalDao dao) { - //if the stop times are not equal, check and see if any of the trips are running today. - //if the trip is running today, then copy over the id for this one trip, - //we'll ignore the rest of the trips and break the trip loop. - Date today = removeTime(new Date()); - if (trips.size() > 2) { - _log.info("There are more than two matches for this trip id {}", trips.get(0).getMtaTripId()); + public void apply() { + generateServiceIds(); + deleteTrips(); + applyNewTripIds(); } - for (Trip trip : trips) { - for (ServiceCalendarDate calDate : dao.getCalendarDatesForServiceId(trip.getServiceId())) { - Date date = constructDate(calDate.getDate()); - if (calDate.getExceptionType() == 1 && date.equals(today)) { - //_log.info("Copying over id for {} {}", trip.getId(), trip.getMtaTripId()); - //trip is today, copy of the mta_id for this one and quit - trip.setId(new AgencyAndId(trip.getId().getAgencyId(), trip.getMtaTripId())); - return true; + + private void applyNewTripIds() { + Map mtaTripIdCounts = new HashMap<>(); + for (Map.Entry entry : atisToMtaTripId.entrySet()) { + AgencyAndId atisTripId = entry.getKey(); + AgencyAndId mtaTripId = entry.getValue(); + AgencyAndId rawMtaTripId = removeTag(mtaTripId); + if (!mtaTripIdCounts.containsKey(rawMtaTripId)) { + mtaTripIdCounts.put(rawMtaTripId, 1); + } else { + mtaTripIdCounts.put(rawMtaTripId, mtaTripIdCounts.get(rawMtaTripId)+1); } } - } - return false; - } - private int getNextServiceId(GtfsMutableRelationalDao dao) { - ArrayList idList = new ArrayList<>(); - for (ServiceCalendarDate svcDate : dao.getAllCalendarDates()) { - if (isInt(svcDate.getServiceId().getId())) { - idList.add(Integer.parseInt(svcDate.getServiceId().getId())); + + for (Map.Entry agencyAndIdAgencyAndIdEntry : atisToMtaTripId.entrySet()) { + AgencyAndId atisTripId = agencyAndIdAgencyAndIdEntry.getKey(); + AgencyAndId mtaTripId = agencyAndIdAgencyAndIdEntry.getValue(); + AgencyAndId rawMtaTripId = removeTag(mtaTripId); + int occurrenceCount = mtaTripIdCounts.get(rawMtaTripId); + Trip toModify = dao.getTripForId(atisTripId); + if (toModify != null) { + if (occurrenceCount > 1) { + // it is a duplicate + incDuplicateCount(); + toModify.setId(mtaTripId); + } else { + toModify.setId(removeTag(mtaTripId)); } + } else { + // the trip has already been deleted, nothing to do + System.out.println("non-existent trip " + atisTripId + "/" + mtaTripId); + } + } - } - return Collections.max(idList) + 1; - } - private boolean isInt(String str) { - if (str == null) { - return false; } - int length = str.length(); - if (length == 0) { - return false; + + private void incDuplicateCount() { + duplicateTripIdCounter++; } - for (int i = 0; i < length; i++) { - char c = str.charAt(i); - if (c < '0' || c > '9') { - return false; + + private AgencyAndId removeTag(AgencyAndId mtaTripId) { + int pos = mtaTripId.getId().lastIndexOf("-dup-"); + if (pos > 1) { + return new AgencyAndId(mtaTripId.getAgencyId(), mtaTripId.getId().substring(0, pos)); } + return mtaTripId; } - return true; - } - private boolean stopTimesEqual(List s1, List s2) { - if (s1.size() != s2.size()) { - //_log.info("Not equal on size {} {}", s1.size(), s2.size()); - return false; + private void deleteTrips() { + RemoveEntityLibrary removeEntityLibrary = new RemoveEntityLibrary(); + + for (Trip tripToRemove : tripsToRemove) { + deletedTripCounter++; + removeEntityLibrary.removeTrip(dao, tripToRemove); + atisToMtaTripId.remove(tripToRemove.getId()); + } } - int index = 0; - for (int i = 0; i < s1.size(); i++) { - if(!s1.get(i).getStop().equals(s2.get(i).getStop())) { - //_log.info("Stops {} {}", s1.get(i).getStop(), s2.get(i).getStop()); - return false; + + private void generateServiceIds() { + for (Map.Entry, List> tripsBySet : tripsByServiceIds.entrySet()) { + Set calendarIds = tripsBySet.getKey(); + List trips = tripsBySet.getValue(); + AgencyAndId newServiceId = generateServiceId(calendarIds); + for (Trip trip : trips) { + trip.setServiceId(newServiceId); + } } - if(s1.get(i).getDepartureTime() != s2.get(i).getDepartureTime()) { - //_log.info("Dep time {} {}", s1.get(i).getDepartureTime(), s2.get(i).getDepartureTime()); - return false; + + } + + private AgencyAndId generateServiceId(Set calendarIds) { + List dates = new ArrayList<>(); + for (AgencyAndId calendarId : calendarIds) { + List calendarDatesForServiceId = dao.getCalendarDatesForServiceId(calendarId); + dates.addAll(calendarDatesForServiceId); } - if(s1.get(i).getArrivalTime() != s2.get(i).getArrivalTime()) { - //_log.info("Arr time {} {}", s1.get(i).getArrivalTime(), s2.get(i).getArrivalTime()); - return false; + + AgencyAndId newServiceId = generateServiceId(); + + for (ServiceCalendarDate calDate : dates) { + ServiceCalendarDate newDate = new ServiceCalendarDate(); + newDate.setServiceId(newServiceId); + newDate.setDate(calDate.getDate()); + newDate.setExceptionType(calDate.getExceptionType()); + dao.saveOrUpdateEntity(newDate); } + + return newServiceId; } - return true; - } - private Date constructDate(ServiceDate date) { - Calendar calendar = Calendar.getInstance(); - calendar.set(Calendar.YEAR, date.getYear()); - calendar.set(Calendar.MONTH, date.getMonth()-1); - calendar.set(Calendar.DATE, date.getDay()); - Date date1 = calendar.getTime(); - date1 = removeTime(date1); - return date1; - } + private AgencyAndId generateServiceId() { + serviceIdCounter++; + return new AgencyAndId(calendarAgencyId, String.valueOf(serviceIdCounter)); + } - private Date removeTime(Date date) { - Calendar calendar = Calendar.getInstance(); - calendar.setTime(date); - calendar.set(Calendar.HOUR_OF_DAY, 0); - calendar.set(Calendar.MINUTE, 0); - calendar.set(Calendar.SECOND, 0); - calendar.set(Calendar.MILLISECOND, 0); - date = calendar.getTime(); - return date; + public void addTripToTrack(AgencyAndId atisId, AgencyAndId mtaTripId) { + atisToMtaTripId.put(atisId, mtaTripId); + } } + } diff --git a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/UpdateTripHeadsignByDestinationStrategy.java b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/UpdateTripHeadsignByDestinationStrategy.java index dae2df5b5..78ad76aa6 100644 --- a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/UpdateTripHeadsignByDestinationStrategy.java +++ b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/UpdateTripHeadsignByDestinationStrategy.java @@ -50,23 +50,8 @@ public void run(TransformContext context, GtfsMutableRelationalDao dao) { trip.setTripHeadsign(tripHeadSign); update++; } - else { - fallbackSetHeadsign(trip); - fallback++; - } - } - else { - fallbackSetHeadsign(trip); - fallback++; } } _log.info("trip headsign update:{} fallback: {}", update, fallback); } - - private void fallbackSetHeadsign (Trip trip) { - if (trip.getTripHeadsign() == null) { - trip.setTripHeadsign(trip.getRouteShortName()); - - } - } } diff --git a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/UpdateTripHeadsignByReference.java b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/UpdateTripHeadsignByReference.java index d963989ca..9a14aaaa6 100644 --- a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/UpdateTripHeadsignByReference.java +++ b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/UpdateTripHeadsignByReference.java @@ -44,7 +44,6 @@ public String getName() { public void run(TransformContext context, GtfsMutableRelationalDao dao) { GtfsMutableRelationalDao reference = (GtfsMutableRelationalDao) context.getReferenceReader().getEntityStore(); - String agency = reference.getAllTrips().iterator().next().getId().getAgencyId(); ArrayList missingStops = new ArrayList<>(); for (Trip trip : dao.getAllTrips()) { @@ -74,14 +73,11 @@ public void run(TransformContext context, GtfsMutableRelationalDao dao) { } else { _log.error("No stoptimes for trip {} mta id", trip.toString(), trip.getMtaTripId()); - if (trip.getTripHeadsign() == null && trip.getRouteShortName() == null) { + if (trip.getTripHeadsign() == null) { //if trip has no headsign, no stoptimes and no shortname, remove it _log.error("Removing trip {}", trip.getId()); dao.removeEntity(trip); } - else { - genericSetHeadsign(trip); - } } } } @@ -91,16 +87,6 @@ private void fallbackSetHeadsign (Trip trip, Stop stop) { trip.setTripHeadsign(stop.getName()); //_log.info("Setting headsign {} on {}", stop.getName(), trip.toString()); } - else { - genericSetHeadsign(trip); - } - } - - private void genericSetHeadsign (Trip trip) { - if (trip.getRouteShortName() != null) { - trip.setTripHeadsign(trip.getRouteShortName()); - //_log.info("Setting headsign {} on {}", trip.getRouteShortName(), trip.toString()); - } } @CsvField(ignore = true) diff --git a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/UpdateTripHeadsignExcludeNonreference.java b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/UpdateTripHeadsignExcludeNonreference.java index 190f40961..2746a4300 100644 --- a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/UpdateTripHeadsignExcludeNonreference.java +++ b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/UpdateTripHeadsignExcludeNonreference.java @@ -62,10 +62,6 @@ public void run(TransformContext context, GtfsMutableRelationalDao dao) { trip.setTripHeadsign(tripHeadSign); update++; } - else { - fallbackSetHeadsign(trip); - fallback++; - } } else { noChange++; @@ -74,20 +70,10 @@ public void run(TransformContext context, GtfsMutableRelationalDao dao) { } } } - else { - fallbackSetHeadsign(trip); - fallback++; - } } _log.info("trip headsign update:{} fallback: {} no change: {} shuttle: {}", update, fallback, noChange, shuttle); } - private void fallbackSetHeadsign (Trip trip) { - if (trip.getTripHeadsign() == null) { - trip.setTripHeadsign(trip.getRouteShortName()); - } - } - @CsvField(ignore = true) private String _referenceAgencyId = null; private String getReferenceAgencyId(GtfsMutableRelationalDao dao) { diff --git a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/UpdateTripHeadsignIfNull.java b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/UpdateTripHeadsignIfNull.java index b8248a1d6..d242ebc37 100644 --- a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/UpdateTripHeadsignIfNull.java +++ b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/UpdateTripHeadsignIfNull.java @@ -44,21 +44,9 @@ public void run(TransformContext context, GtfsMutableRelationalDao dao) { if (tripHeadSign != null) { trip.setTripHeadsign(tripHeadSign); } - else { - fallbackSetHeadsign(trip); - } - } - else { - fallbackSetHeadsign(trip); } } } } - private void fallbackSetHeadsign (Trip trip) { - if (trip.getTripHeadsign() == null) { - trip.setTripHeadsign(trip.getRouteShortName()); - _log.info("Setting headsign to route short name: ", trip.getRouteShortName()); - } - } } diff --git a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/UpdateTripHeadsignRailRoadConvention.java b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/UpdateTripHeadsignRailRoadConvention.java index e6cc10b58..663ac0eef 100644 --- a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/UpdateTripHeadsignRailRoadConvention.java +++ b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/UpdateTripHeadsignRailRoadConvention.java @@ -48,7 +48,7 @@ public void run(TransformContext context, GtfsMutableRelationalDao dao) { List stopTimes = dao.getStopTimesForTrip(trip); if (stopTimes != null && stopTimes.size() > 0) { - String existingTripHeadsign = (trip.getTripHeadsign() != null) ? trip.getTripHeadsign() : trip.getRouteShortName(); + String existingTripHeadsign = (trip.getTripHeadsign() != null) ? trip.getTripHeadsign() : "trip route short name"; Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(stopTimes.get(0).getDepartureTime() * 1000); diff --git a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/WSFBlockResolutionStrategy.java b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/WSFBlockResolutionStrategy.java deleted file mode 100644 index fce5667fb..000000000 --- a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/WSFBlockResolutionStrategy.java +++ /dev/null @@ -1,396 +0,0 @@ -/** - * Copyright (C) 2016 Cambridge Systematics, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.onebusaway.gtfs_transformer.impl; - -import java.io.InputStream; -import java.net.URL; -import java.net.URLConnection; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TimeZone; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBElement; -import javax.xml.bind.JAXBException; -import javax.xml.datatype.XMLGregorianCalendar; -import javax.xml.transform.stream.StreamSource; - -import org.onebusaway.csv_entities.schema.annotations.CsvField; -import org.onebusaway.gtfs.impl.calendar.CalendarServiceDataFactoryImpl; -import org.onebusaway.gtfs.model.Agency; -import org.onebusaway.gtfs.model.AgencyAndId; -import org.onebusaway.gtfs.model.Route; -import org.onebusaway.gtfs.model.ServiceCalendar; -import org.onebusaway.gtfs.model.StopTime; -import org.onebusaway.gtfs.model.StopLocation; -import org.onebusaway.gtfs.model.Trip; -import org.onebusaway.gtfs.model.calendar.CalendarServiceData; -import org.onebusaway.gtfs.model.calendar.ServiceDate; -import org.onebusaway.gtfs.services.GtfsMutableRelationalDao; -import org.onebusaway.gtfs.services.GtfsRelationalDao; -import org.onebusaway.gtfs.services.calendar.CalendarServiceDataFactory; -import org.onebusaway.gtfs_transformer.services.GtfsTransformStrategy; -import org.onebusaway.gtfs_transformer.services.TransformContext; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import gov.wa.wsdot.ferries.schedule.SchedResponse; -import gov.wa.wsdot.ferries.schedule.SchedTerminalCombo; -import gov.wa.wsdot.ferries.schedule.SchedTime; - -public class WSFBlockResolutionStrategy implements GtfsTransformStrategy { - - private static final Logger _log = LoggerFactory.getLogger( - WSFBlockResolutionStrategy.class); - - @CsvField(ignore=true) - private GtfsMutableRelationalDao _dao; - - @CsvField(ignore=true) - private String _agencyId; - - @CsvField(ignore=true) - private TimeZone _agencyTimeZone; - - @CsvField(ignore=true) - private WSFTripResolutionService _tripResolutionService; - - @CsvField(ignore=true) - private WSFScheduleService _scheduleService; - - private String apiAccessCode = ""; - - @Override - public String getName() { - return this.getClass().getSimpleName(); - } - - @Override - public void run(TransformContext context, GtfsMutableRelationalDao dao) { - _dao = dao; - - Agency agency = dao.getAllAgencies().iterator().next(); - _agencyId = agency.getId(); - _agencyTimeZone = TimeZone.getTimeZone(agency.getTimezone()); - - _tripResolutionService = new WSFTripResolutionService(_dao, _agencyId, - _agencyTimeZone); - - try { - _scheduleService = new WSFScheduleService(apiAccessCode); - setAllBlockIds(); - } catch (Exception e) { - _log.error("Error initializing WSFBlockResolutionStrategy: " + e); - return; - } - - } - - public void setApiAccessCode(String apiAccessCode) { - this.apiAccessCode = apiAccessCode; - } - - private void setAllBlockIds() throws InterruptedException { - Collection trips = _dao.getAllTrips(); - - Map tripsMap = new HashMap(); - for (Trip t : trips) { - - BlockTask task = new BlockTask(t); - - // No data for trips in past - if (task.isInPast()) { - continue; - } - - if (!tripsMap.containsKey(task)) { - tripsMap.put(task, task); - } - task = tripsMap.get(task); - - task.addTrip(t); - } - - Set jobs = tripsMap.keySet(); - - // Oversubscribe cores since we are network-bound - int nCores = Runtime.getRuntime().availableProcessors(); - ExecutorService executor = Executors.newFixedThreadPool(nCores * 2); - - executor.invokeAll(jobs); - - } - - private void setBlockIdsFromSchedResponse(SchedResponse resp) { - List combos = resp.getTerminalCombos().getValue().getSchedTerminalCombo(); - for (SchedTerminalCombo stc : combos) { - - String depart = stc.getDepartingTerminalID().toString(); - String arrive = stc.getArrivingTerminalID().toString(); - - for (SchedTime sched : schedTime(stc)) { - long time = ts(sched.getDepartingTime()); - Trip trip = _tripResolutionService.resolve(depart, time, arrive); - if (trip != null) { - trip.setBlockId(sched.getVesselID().toString()); - } else { - _log.warn("Skipping schedTime due to no matching trip {}", - sched.toString()); - } - } - } - } - - private String id(StopLocation st) { - return st.getId().getId(); - } - - private Date date(ServiceCalendar cal) { - return cal.getStartDate().getAsDate(_agencyTimeZone); - } - - private List schedTime(SchedTerminalCombo st) { - return st.getTimes().getValue().getSchedTime(); - } - - // From WSFRealtimeProvider - private long ts(XMLGregorianCalendar xgc) { - GregorianCalendar gc = xgc.toGregorianCalendar(_agencyTimeZone, null, null); - return (gc.getTimeInMillis() / 1000L); - } - - /** - * We need to query the API for each service date, for each stop pair (route). - * This class allows us to construct an index of trips that have the same - * service date and route. In addition, it is a Callable so that tasks can be - * run asynchronously. - * - */ - class BlockTask implements Callable { - Route route; - ServiceCalendar cal; - List trips; - - BlockTask(Trip t) { - this.route = t.getRoute(); - this.cal = _dao.getCalendarForServiceId(t.getServiceId()); - } - - @Override - public Boolean call() { - List stops = _dao.getStopTimesForTrip(trips.get(0)); - StopLocation orig = stops.get(0).getStop(), dest = stops.get(1).getStop(); - - _log.info("Submitting WSF block task for {} ({}, {})", cal, orig, dest); - SchedResponse resp = _scheduleService.getSchedule(date(cal), id(orig), - id(dest)); - - setBlockIdsFromSchedResponse(resp); - - return true; - } - - void addTrip(Trip t) { - if (trips == null) { - trips = new ArrayList(); - } - trips.add(t); - } - - boolean isInPast() { - Calendar today = Calendar.getInstance(); - today.set(Calendar.HOUR_OF_DAY, 0); - today.set(Calendar.MINUTE, 0); - today.set(Calendar.SECOND, 0); - today.set(Calendar.MILLISECOND, 0); - - return date(cal).before(today.getTime()); - } - - @Override - public int hashCode() { - return route.hashCode() * 17 + cal.getServiceId().hashCode() * 31; - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof BlockTask)) - return false; - BlockTask k = (BlockTask) o; - if (!this.route.equals(k.route)) - return false; - if (!this.cal.equals(k.cal)) - return false; - - return true; - } - } - -} - -/** - * Adapted from TripResolutionService in wsf-gtfsrealtime - */ -class WSFTripResolutionService { - - private static final Logger _log = LoggerFactory.getLogger( - WSFTripResolutionService.class); - - GtfsRelationalDao _dao; - String _agencyId; - CalendarServiceData _csd; - TimeZone _agencyTimeZone; - - int _maxStopTime; - - public WSFTripResolutionService(GtfsRelationalDao dao, String agencyId, - TimeZone agencyTimeZone) { - _dao = dao; - _agencyId = agencyId; - _agencyTimeZone = agencyTimeZone; - - CalendarServiceDataFactory factory = new CalendarServiceDataFactoryImpl( - _dao); - _csd = factory.createData(); - - _maxStopTime = calculateMaxStopTime(); - } - - public Trip resolve(String departingTerminalId, long departureTime, - String arrivingTerminalId) { - ServiceDate initialServiceDate = new ServiceDate( - new Date(departureTime * 1000)); - int lookBackDays = (_maxStopTime / 86400) + 1; - - AgencyAndId stopId = new AgencyAndId(_agencyId, departingTerminalId); - AgencyAndId routeId = new AgencyAndId(_agencyId, - departingTerminalId + arrivingTerminalId); - - for (StopTime st : _dao.getAllStopTimes()) { - if (st.getStop().getId().equals(stopId) - && st.getTrip().getRoute().getId().equals(routeId)) { - - ServiceDate sd = initialServiceDate; - for (int i = 0; i < lookBackDays; i++) { - - if (_csd.getServiceIdsForDate(sd).contains( - st.getTrip().getServiceId()) - && st.getDepartureTime() == (departureTime - - (sd.getAsCalendar(_agencyTimeZone).getTimeInMillis() - / 1000))) { - - return st.getTrip(); - } - - sd = sd.previous(); - } - - } - } - - _log.warn("no trip found for resolve(departId=" + departingTerminalId - + ", departureTime=" + departureTime + ", arrivalId=" - + arrivingTerminalId + ")"); - return null; - - } - - private int calculateMaxStopTime() { - Set times = new HashSet(); - - for (StopTime st : _dao.getAllStopTimes()) { - if (st.isArrivalTimeSet()) { - times.add(st.getArrivalTime()); - } - if (st.isDepartureTimeSet()) { - times.add(st.getDepartureTime()); - } - } - - return Collections.max(times); - } -} - -/** - * Query the WSF API for a schedule response. - */ -class WSFScheduleService { - - private static Logger _log = LoggerFactory.getLogger( - WSFScheduleService.class); - - private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat( - "yyyy-MM-dd"); - - private String _apiAccessCode; - - // Context is thread-safe. Unmarshaller is not. - JAXBContext _jc; - - public WSFScheduleService(String apiAccessCode) throws JAXBException { - _jc = JAXBContext.newInstance(SchedResponse.class); - _apiAccessCode = apiAccessCode; - } - - public SchedResponse getSchedule(Date serviceDate, String departTerminal, - String arriveTerminal) { - long start = System.currentTimeMillis(); - StringBuffer url = new StringBuffer( - "https://www.wsdot.wa.gov/ferries/api/schedule/rest/schedule"); - url.append("/" + formatDate(serviceDate)); - url.append("/" + departTerminal); - url.append("/" + arriveTerminal); - url.append("?apiaccesscode=" + _apiAccessCode); - - try { - URLConnection conn = new URL(url.toString()).openConnection(); - conn.setRequestProperty("Accept", "text/xml"); - - InputStream is = conn.getInputStream(); - try { - JAXBElement resp = _jc.createUnmarshaller().unmarshal( - new StreamSource(is), SchedResponse.class); - - return resp.getValue(); - } finally { - is.close(); - long finish = System.currentTimeMillis(); - _log.info("wsf call complete in " + (finish-start)/1000 + "s for call=" + url); - } - } catch (Exception e) { - _log.error("Exception processing WSF API for api call:'" + url.toString() + "', " + e); - return null; - } - - } - - private static String formatDate(Date date) { - return DATE_FORMATTER.format(date); - } -} \ No newline at end of file diff --git a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/updates/DeduplicateTripsStrategy.java b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/updates/DeduplicateTripsStrategy.java index 1dc83b6fe..6df0c436d 100644 --- a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/updates/DeduplicateTripsStrategy.java +++ b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/updates/DeduplicateTripsStrategy.java @@ -115,8 +115,6 @@ private String areTripsEquivalent(Trip tripA, Trip tripB) { return "directionId"; if (!equals(tripA.getRoute(), tripB.getRoute())) return "route"; - if (!equals(tripA.getRouteShortName(), tripB.getRouteShortName())) - return "routeShortName"; if (!equals(tripA.getShapeId(), tripB.getShapeId())) return "shapeId"; if (!equals(tripA.getTripHeadsign(), tripB.getTripHeadsign())) diff --git a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/updates/LocalVsExpressUpdateStrategy.java b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/updates/LocalVsExpressUpdateStrategy.java index 087fdcb8f..d23c127a4 100644 --- a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/updates/LocalVsExpressUpdateStrategy.java +++ b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/updates/LocalVsExpressUpdateStrategy.java @@ -71,7 +71,6 @@ public void run(TransformContext context, GtfsMutableRelationalDao dao) { boolean isExpress = trip.getTripShortName().equals("EXPRESS"); if (isExpress) { _log.info("route(" + route.getShortName() + ") gets an E for trip " + trip.getId()); - trip.setRouteShortName(trip.getRoute().getShortName() + "E"); if (addLocalVsExpressToTripName) { String tripHeadsign = trip.getTripHeadsign(); if (tripHeadsign != null) diff --git a/onebusaway-gtfs-transformer/src/main/resources/log4j.properties b/onebusaway-gtfs-transformer/src/main/resources/log4j.properties deleted file mode 100644 index ddc38d6df..000000000 --- a/onebusaway-gtfs-transformer/src/main/resources/log4j.properties +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright 2008 Brian Ferris -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy of -# the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations under -# the License. - - -log4j.rootLogger = INFO, stdout - -log4j.appender.stdout = org.apache.log4j.ConsoleAppender -log4j.appender.stdout.Threshold = DEBUG -log4j.appender.stdout.Target = System.out -log4j.appender.stdout.layout = org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern = %d{ISO8601} %-5p [%F:%L] : %m%n - -#log4j.category.org.onebusaway=DEBUG - - - diff --git a/onebusaway-gtfs-transformer/src/site/site.xml b/onebusaway-gtfs-transformer/src/site/site.xml deleted file mode 100644 index 323e55b50..000000000 --- a/onebusaway-gtfs-transformer/src/site/site.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/AbstractTestSupport.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/AbstractTestSupport.java index d42fa54b7..d67fe9020 100644 --- a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/AbstractTestSupport.java +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/AbstractTestSupport.java @@ -18,8 +18,8 @@ import java.io.File; import java.io.IOException; -import org.junit.After; -import org.junit.Before; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.onebusaway.gtfs.impl.GtfsRelationalDaoImpl; import org.onebusaway.gtfs.serialization.GtfsReader; import org.onebusaway.gtfs.services.GtfsRelationalDao; @@ -34,7 +34,7 @@ public class AbstractTestSupport { private File _outputPath; - @Before + @BeforeEach public void before() throws IOException { _gtfs = MockGtfs.create(); _outputPath = File.createTempFile("MockGtfs-", ".zip"); @@ -45,7 +45,7 @@ public void before() throws IOException { _transformer.setOutputDirectory(_outputPath); } - @After + @AfterEach public void after() { _gtfs.getPath().delete(); _outputPath.delete(); diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/AddEntityTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/AddEntityTest.java index 2f0d603d0..fd67b3c7b 100644 --- a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/AddEntityTest.java +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/AddEntityTest.java @@ -15,13 +15,13 @@ */ package org.onebusaway.gtfs_transformer; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; import java.io.IOException; import java.util.Collection; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.model.AgencyAndId; import org.onebusaway.gtfs.model.Frequency; import org.onebusaway.gtfs.serialization.mappings.StopTimeFieldMappingFactory; diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/GtfsTransformerTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/GtfsTransformerTest.java index ea79c68dc..3971675b0 100644 --- a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/GtfsTransformerTest.java +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/GtfsTransformerTest.java @@ -15,14 +15,14 @@ */ package org.onebusaway.gtfs_transformer; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import java.io.IOException; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.model.AgencyAndId; import org.onebusaway.gtfs.model.StopTime; import org.onebusaway.gtfs.services.GtfsRelationalDao; @@ -35,7 +35,7 @@ public class GtfsTransformerTest { private GtfsTransformer _transformer = new GtfsTransformer(); - @Before + @BeforeEach public void setup() throws IOException { _gtfs = MockGtfs.create(); _gtfs.putAgencies(1); diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/deferred/DeferredValueConverterTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/deferred/DeferredValueConverterTest.java index b84ade38d..f7a150a50 100644 --- a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/deferred/DeferredValueConverterTest.java +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/deferred/DeferredValueConverterTest.java @@ -15,10 +15,10 @@ */ package org.onebusaway.gtfs_transformer.deferred; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.csv_entities.schema.BeanWrapperFactory; import org.onebusaway.gtfs.impl.GtfsRelationalDaoImpl; import org.onebusaway.gtfs.model.Agency; @@ -43,7 +43,7 @@ public class DeferredValueConverterTest { private DeferredValueConverter _converter; - @Before + @BeforeEach public void setup() { _schemaCache.addEntitySchemasFromGtfsReader(_reader); _reader.setDefaultAgencyId("1"); diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/deferred/DeferredValueMatcherTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/deferred/DeferredValueMatcherTest.java index 499fdc498..006f40bb0 100644 --- a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/deferred/DeferredValueMatcherTest.java +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/deferred/DeferredValueMatcherTest.java @@ -15,11 +15,11 @@ */ package org.onebusaway.gtfs_transformer.deferred; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.model.AgencyAndId; import org.onebusaway.gtfs.model.Route; import org.onebusaway.gtfs.model.ServiceCalendar; @@ -35,7 +35,7 @@ public class DeferredValueMatcherTest { private EntitySchemaCache _schemaCache = new EntitySchemaCache(); - @Before + @BeforeEach public void setup() { _reader.setDefaultAgencyId("1"); _schemaCache.addEntitySchemasFromGtfsReader(_reader); diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/deferred/DeferredValueSetterTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/deferred/DeferredValueSetterTest.java index 5f83adbce..b0715b78c 100644 --- a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/deferred/DeferredValueSetterTest.java +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/deferred/DeferredValueSetterTest.java @@ -15,10 +15,10 @@ */ package org.onebusaway.gtfs_transformer.deferred; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.csv_entities.schema.BeanWrapperFactory; import org.onebusaway.gtfs.impl.GtfsRelationalDaoImpl; import org.onebusaway.gtfs.model.Stop; @@ -33,7 +33,7 @@ public class DeferredValueSetterTest { private EntitySchemaCache _schemaCache = new EntitySchemaCache(); - @Before + @BeforeEach public void setup() { _schemaCache.addEntitySchemasFromGtfsReader(_reader); } diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/deferred/DeferredValueSupportTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/deferred/DeferredValueSupportTest.java index 4c81da8bc..b1fcd860d 100644 --- a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/deferred/DeferredValueSupportTest.java +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/deferred/DeferredValueSupportTest.java @@ -15,15 +15,15 @@ */ package org.onebusaway.gtfs_transformer.deferred; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import org.apache.commons.beanutils.ConvertUtils; import org.apache.commons.beanutils.Converter; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.csv_entities.schema.BeanWrapper; import org.onebusaway.csv_entities.schema.BeanWrapperFactory; import org.onebusaway.csv_entities.schema.EntitySchema; @@ -41,7 +41,7 @@ public class DeferredValueSupportTest { private EntitySchemaCache _schemaCache; private DeferredValueSupport _support; - @Before + @BeforeEach public void before() { _reader = new GtfsReader(); _reader.setDefaultAgencyId("a0"); diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/deferred/EntitySchemaCacheTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/deferred/EntitySchemaCacheTest.java index 23d159fc8..36ba44d8e 100644 --- a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/deferred/EntitySchemaCacheTest.java +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/deferred/EntitySchemaCacheTest.java @@ -15,9 +15,9 @@ */ package org.onebusaway.gtfs_transformer.deferred; -import static org.junit.Assert.assertSame; +import static org.junit.jupiter.api.Assertions.assertSame; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.onebusaway.csv_entities.schema.DefaultFieldMapping; import org.onebusaway.csv_entities.schema.EntitySchema; import org.onebusaway.csv_entities.schema.FieldMapping; diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/deferred/PropertyPathExpressionValueSetterTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/deferred/PropertyPathExpressionValueSetterTest.java index dd59f0006..9e6a497b6 100644 --- a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/deferred/PropertyPathExpressionValueSetterTest.java +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/deferred/PropertyPathExpressionValueSetterTest.java @@ -15,10 +15,10 @@ */ package org.onebusaway.gtfs_transformer.deferred; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.collections.beans.PropertyPathExpression; import org.onebusaway.csv_entities.schema.BeanWrapperFactory; import org.onebusaway.gtfs.impl.GtfsRelationalDaoImpl; @@ -36,7 +36,7 @@ public class PropertyPathExpressionValueSetterTest { private EntitySchemaCache _schemaCache = new EntitySchemaCache(); private GtfsMutableRelationalDao _dao = new GtfsRelationalDaoImpl(); - @Before + @BeforeEach public void setup() { _schemaCache.addEntitySchemasFromGtfsReader(_reader); } diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/deferred/ReplaceValueSetterTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/deferred/ReplaceValueSetterTest.java index cb6c34ef5..abb963321 100644 --- a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/deferred/ReplaceValueSetterTest.java +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/deferred/ReplaceValueSetterTest.java @@ -15,9 +15,9 @@ */ package org.onebusaway.gtfs_transformer.deferred; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.onebusaway.csv_entities.schema.BeanWrapperFactory; import org.onebusaway.gtfs.model.Stop; diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/factory/PropertyMethodResolverImplTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/factory/PropertyMethodResolverImplTest.java index 139543138..1d3035f06 100644 --- a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/factory/PropertyMethodResolverImplTest.java +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/factory/PropertyMethodResolverImplTest.java @@ -15,14 +15,14 @@ */ package org.onebusaway.gtfs_transformer.factory; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; import java.lang.reflect.InvocationTargetException; import java.util.Arrays; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.collections.beans.PropertyMethod; import org.onebusaway.csv_entities.schema.DefaultEntitySchemaFactory; import org.onebusaway.csv_entities.schema.EntitySchema; @@ -46,7 +46,7 @@ public class PropertyMethodResolverImplTest { private PropertyMethodResolverImpl _resolver; - @Before + @BeforeEach public void before() { _dao = new GtfsRelationalDaoImpl(); _schemaCache = new EntitySchemaCache(); diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/factory/TransformFactoryTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/factory/TransformFactoryTest.java index 0c8147f61..977000045 100644 --- a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/factory/TransformFactoryTest.java +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/factory/TransformFactoryTest.java @@ -15,14 +15,14 @@ */ package org.onebusaway.gtfs_transformer.factory; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.util.List; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.impl.GtfsRelationalDaoImpl; import org.onebusaway.gtfs.model.AgencyAndId; import org.onebusaway.gtfs.model.Route; diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/impl/AddExtensionFileTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/impl/AddExtensionFileTest.java index 47e65985b..d89bc2709 100644 --- a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/impl/AddExtensionFileTest.java +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/impl/AddExtensionFileTest.java @@ -15,13 +15,12 @@ */ package org.onebusaway.gtfs_transformer.impl; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.impl.FileSupport; import org.onebusaway.gtfs.impl.GtfsRelationalDaoImpl; import org.onebusaway.gtfs.serialization.GtfsWriter; -import org.onebusaway.gtfs.services.GtfsRelationalDao; import org.onebusaway.gtfs_transformer.AbstractTestSupport; import org.onebusaway.gtfs_transformer.services.TransformContext; import java.nio.charset.StandardCharsets; @@ -34,7 +33,7 @@ import java.nio.file.Files; import java.nio.file.Path; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * test we can insert an extension into a GTFS file via a transformation. @@ -48,7 +47,7 @@ public class AddExtensionFileTest extends AbstractTestSupport { private TransformContext context = new TransformContext(); private GtfsWriter writer = new GtfsWriter(); - @Before + @BeforeEach public void setup() { _gtfs.putAgencies(1); _gtfs.putStops(1); @@ -57,7 +56,7 @@ public void setup() { _gtfs.putStopTimes("t0", "s0"); } - @After + @AfterEach public void teardown() { _support.cleanup(); } @@ -91,13 +90,10 @@ public void run() throws IOException { String modLocation = tmpFileDirectory.getAbsolutePath() + File.separator + extensionName; File expectedFile = new File(modLocation); // verify file is there - assertTrue("expected extension to be present!", expectedFile.exists()); - assertTrue("expected extension to be a file!", expectedFile.isFile()); - Path path = Paths.get(modLocation); - byte[] bytes = Files.readAllBytes(path); - String actualText = new String(bytes, StandardCharsets.UTF_8); - + assertTrue(expectedFile.exists(), "expected extension to be present!"); + assertTrue(expectedFile.isFile(), "expected extension to be a file!"); + String actualText = Files.readString(Path.of(modLocation)); assertEquals(TXT_STRING, actualText); } -} \ No newline at end of file +} diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/impl/RemoveOldCalendarStatementsTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/impl/RemoveOldCalendarStatementsTest.java new file mode 100644 index 000000000..87a2d3937 --- /dev/null +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/impl/RemoveOldCalendarStatementsTest.java @@ -0,0 +1,81 @@ +package org.onebusaway.gtfs_transformer.impl; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import java.io.IOException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.onebusaway.gtfs.services.GtfsMutableRelationalDao; +import org.onebusaway.gtfs.services.MockGtfs; +import org.onebusaway.gtfs_transformer.services.TransformContext; + +public class RemoveOldCalendarStatementsTest { + + private RemoveOldCalendarStatements removeOldCalendarStatements = new RemoveOldCalendarStatements(); + private TransformContext _context = new TransformContext(); + private MockGtfs _gtfs; + + @BeforeEach + public void setup() throws IOException{ + + _gtfs = MockGtfs.create(); + _gtfs.putAgencies(1); + _gtfs.putStops(1); + _gtfs.putRoutes(1); + _gtfs.putTrips(1, "r0", "sid0"); + _gtfs.putStopTimes("t0", "s0"); + + // Insert a calendar entry wtih start and end dates set to today's date + String startDate = getCurrentDateFormatted(-3); + String endDate = getCurrentDateFormatted(null); + + // Define additional date for testing purposes, relative to startDate + String threeDaysFromStartDate = getCurrentDateFormatted(3); + + _gtfs.putCalendars( + 1, + "start_date="+startDate, + "end_date="+endDate + ); + + // Insert calendar dates entries + _gtfs.putCalendarDates("sid0="+startDate+","+endDate+","+ + threeDaysFromStartDate); + } + + @Test + public void testRemoveCalendarForToday() throws IOException { + GtfsMutableRelationalDao dao = _gtfs.read(); + // Set removeToday to true to allow the removal of the calendar for today's date + removeOldCalendarStatements.setRemoveToday(true); + removeOldCalendarStatements.run(_context, dao); + // Verify that GtfsMutableRelationalDao object no longer contains any calendar entries after removing the calendar for today's date + assertEquals(0,dao.getAllCalendars().size()); + // Verify that GtfsMutableRelationalDao object no longer contains any calendar dates entries after removing invalid dates, including today's date + assertEquals(0,dao.getAllCalendarDates().size()); + } + + @Test + public void testRemoveCalendar() throws IOException { + GtfsMutableRelationalDao dao = _gtfs.read(); + // Keep the default value as false and do not change it + removeOldCalendarStatements.run(_context, dao); + // Verify that GtfsMutableRelationalDao object still contain the initially added calendar entry + assertEquals(1,dao.getAllCalendars().size()); + // Verify that GtfsMutableRelationalDao object contains two calendar dates entries after removing invalid dates + assertEquals(2,dao.getAllCalendarDates().size()); + } + + // Helper function to get today's date in the required format + public static String getCurrentDateFormatted(Integer daysOffset) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd"); + LocalDate date = LocalDate.now(); + if (daysOffset != null){ + date = date.plusDays(daysOffset); + } + // Format date as "yyyyMMdd" + return date.format(formatter); + } +} \ No newline at end of file diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/impl/RetainUpFromPolygonTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/impl/RetainUpFromPolygonTest.java new file mode 100644 index 000000000..a00febd01 --- /dev/null +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/impl/RetainUpFromPolygonTest.java @@ -0,0 +1,76 @@ +package org.onebusaway.gtfs_transformer.impl; +import static org.junit.jupiter.api.Assertions.assertEquals; +import java.io.IOException; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.onebusaway.gtfs.services.GtfsMutableRelationalDao; +import org.onebusaway.gtfs.services.MockGtfs; +import org.onebusaway.gtfs_transformer.services.TransformContext; + +public class RetainUpFromPolygonTest { + + private RetainUpFromPolygon retainUpFromPolygon = new RetainUpFromPolygon(); + private TransformContext _context = new TransformContext(); + private MockGtfs _gtfs; + + @BeforeEach + public void setup() throws IOException{ + + _gtfs = MockGtfs.create(); + // Insert mock data into the GTFS for testing: + // 1 agency + _gtfs.putAgencies(1); + // 4 routes + _gtfs.putRoutes(4); + // 4 trips + _gtfs.putTrips(4, "r$0","sid$0"); + // 8 stops + _gtfs.putStops(8); + // 13 stop times + _gtfs.putLines("stop_times.txt", + "trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled", + // Trip t0: sequence of stops s0,s1,s2,s3 + "t0,08:00:00,08:25:00,s0,0,,,,", + "t0,08:30:00,08:55:00,s1,1,,,,", + "t0,09:00:00,09:55:00,s2,2,,,,", + "t0,10:00:00,10:30:00,s3,3,,,,", + // Trip t1: reverse sequence of stops s3,s2,s1,s0 + "t1,08:00:00,08:25:00,s3,0,,,,", + "t1,08:30:00,08:55:00,s2,1,,,,", + "t1,09:00:00,09:55:00,s1,2,,,,", + "t1,10:00:00,10:00:00,s0,3,,,,", + // Trip t2: sequence of stops s3,s4,s5 + "t2,10:00:00,10:55:00,s3,0,,,,", + "t2,11:00:00,11:25:00,s4,1,,,,", + "t2,11:30:00,11:55:00,s5,2,,,,", + // Trip t3: Additional stops + "t3,12:00:00,12:25:00,s6,0,,,,", + "t3,12:30:00,12:55:00,s7,1,,,,"); + } + + @Test + public void testRetainUpFromPolygonTest() throws IOException { + GtfsMutableRelationalDao dao = _gtfs.read(); + + // Define a polygon in WKT (Well-Known Text) format + // This polygon is designed to include only the first 4 stops (S0 to S4) + String polygonWKT = "POLYGON ((-122.308 47.653, -122.308 47.666, -122.307 47.666, -122.307 47.665, -122.307 47.661, -122.307 47.657, -122.307 47.653, -122.308 47.653))"; + retainUpFromPolygon.setPolygon(polygonWKT); + + // Execute the retainUpFromPolygon strategy based on the polygon + retainUpFromPolygon.run(_context, dao); + + // Verify that the number of routes is reduced to 3 + assertEquals(3,dao.getAllRoutes().size()); + + // Verify that the number of trips is reduced to 3 + assertEquals(3,dao.getAllTrips().size()); + + // Verify that the number of stops is reduced to 6 + assertEquals(6,dao.getAllStops().size()); + + // Verify that the number of stop times is reduced to 11 + assertEquals(11,dao.getAllStopTimes().size()); + } +} \ No newline at end of file diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/impl/TrimTripFromPolygonTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/impl/TrimTripFromPolygonTest.java new file mode 100644 index 000000000..586ad7f39 --- /dev/null +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/impl/TrimTripFromPolygonTest.java @@ -0,0 +1,81 @@ +package org.onebusaway.gtfs_transformer.impl; +import static org.junit.jupiter.api.Assertions.assertEquals; +import java.io.IOException; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.onebusaway.gtfs.services.GtfsMutableRelationalDao; +import org.onebusaway.gtfs.services.MockGtfs; +import org.onebusaway.gtfs_transformer.services.TransformContext; + +public class TrimTripFromPolygonTest { + + private TrimTripFromPolygon trimTripFromPolygon = new TrimTripFromPolygon(); + private RetainUpFromPolygon retainUpFromPolygon = new RetainUpFromPolygon(); + private TransformContext _context = new TransformContext(); + private MockGtfs _gtfs; + + @BeforeEach + public void setup() throws IOException{ + + _gtfs = MockGtfs.create(); + // Insert mock data into the GTFS for testing: + // 1 agency + _gtfs.putAgencies(1); + // 4 routes + _gtfs.putRoutes(4); + // 4 trips + _gtfs.putTrips(4, "r$0","sid$0"); + // 8 stops + _gtfs.putStops(10); + // 13 stop times + _gtfs.putLines("stop_times.txt", + "trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled", + // Trip t0: sequence of stops s0,s1,s2,s3 + "t0,08:00:00,08:25:00,s0,0,,,,", + "t0,08:30:00,08:55:00,s1,1,,,,", + "t0,09:00:00,09:55:00,s2,2,,,,", + "t0,10:00:00,10:30:00,s3,3,,,,", + // Trip t1: reverse sequence of stops s3,s2,s1,s0 + "t1,08:00:00,08:25:00,s3,0,,,,", + "t1,08:30:00,08:55:00,s2,1,,,,", + "t1,09:00:00,09:55:00,s1,2,,,,", + "t1,10:00:00,10:00:00,s0,3,,,,", + // Trip t2: sequence of stops s2,s3,s4,s5 + "t2,09:00:00,09:55:00,s2,0,,,,", + "t2,10:00:00,10:55:00,s3,1,,,,", + "t2,11:00:00,11:25:00,s4,2,,,,", + "t2,11:30:00,11:55:00,s5,3,,,,", + // Trip t3: Additional stops + "t3,12:00:00,12:25:00,s6,0,,,,", + "t3,12:30:00,12:55:00,s7,1,,,,"); + } + + @Test + public void testTrimTripFromPolygon() throws IOException { + GtfsMutableRelationalDao dao = _gtfs.read(); + + // Define a polygon in WKT (Well-Known Text) format + // This polygon is designed to include only the first 4 stops (S0 to S4) + String polygonWKT = "POLYGON ((-122.308 47.653, -122.308 47.666, -122.307 47.666, -122.307 47.665, -122.307 47.661, -122.307 47.657, -122.307 47.653, -122.308 47.653))"; + trimTripFromPolygon.setPolygon(polygonWKT); + retainUpFromPolygon.setPolygon(polygonWKT); + + // Execute the retainUpFromPolygon strategy based on the polygon + retainUpFromPolygon.run(_context, dao); + // Execute the trimTripFromPolygon strategy based on the polygon + trimTripFromPolygon.run(_context, dao); + + // Verify that the number of routes is reduced to 3 + assertEquals(3,dao.getAllRoutes().size()); + + // Verify that the number of trips is reduced to 3 + assertEquals(3,dao.getAllTrips().size()); + + // Verify that the number of stops is reduced to 6 + assertEquals(6,dao.getAllStops().size()); + + // Verify that the number of stop times is reduced to 10 + assertEquals(10,dao.getAllStopTimes().size()); + } +} \ No newline at end of file diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/impl/TruncateNewCalendarStatementsTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/impl/TruncateNewCalendarStatementsTest.java new file mode 100644 index 000000000..7576a48ab --- /dev/null +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/impl/TruncateNewCalendarStatementsTest.java @@ -0,0 +1,90 @@ +package org.onebusaway.gtfs_transformer.impl; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import java.io.IOException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.onebusaway.gtfs.services.GtfsMutableRelationalDao; +import org.onebusaway.gtfs.services.MockGtfs; +import org.onebusaway.gtfs_transformer.services.TransformContext; + +public class TruncateNewCalendarStatementsTest { + + private TruncateNewCalendarStatements truncateNewCalendarStatements = new TruncateNewCalendarStatements(); + private TransformContext _context = new TransformContext(); + private MockGtfs _gtfs; + + @BeforeEach + public void setup() throws IOException{ + + _gtfs = MockGtfs.create(); + _gtfs.putAgencies(1); + _gtfs.putStops(1); + _gtfs.putRoutes(1); + _gtfs.putTrips(1, "r0", "sid0"); + _gtfs.putStopTimes("t0", "s0"); + + // Set startDate to today's date and endDate to three weeks from today + String startDate = getCurrentDateFormatted(null); + String endDate = getCurrentDateFormatted(21); + + // Define additional dates for testing purposes, relative to startDate + String threeDaysFromStartDate = getCurrentDateFormatted(5); + String sevenDaysFromStartDate = getCurrentDateFormatted(7); + String tenDaysFromStartDate = getCurrentDateFormatted(10); + String fifteenDaysFromStartDate = getCurrentDateFormatted(15); + + // Insert a calendar entry with startDate set to today and endDate set to 3 weeks from today + _gtfs.putCalendars( + 1, + "start_date="+startDate, + "end_date="+endDate + ); + + // Insert calendar dates entries + _gtfs.putCalendarDates("sid0="+startDate+","+threeDaysFromStartDate+","+ + sevenDaysFromStartDate+","+tenDaysFromStartDate+","+fifteenDaysFromStartDate); + } + + @Test + public void testTruncateCalendarStatementsWithinThreeDays() throws IOException { + GtfsMutableRelationalDao dao = _gtfs.read(); + // Set calendarField 6 -> Calendar.DAY_OF_YEAR + truncateNewCalendarStatements.setCalendarField(6); + // Set calendarAmount : 3 -> 3 Days + truncateNewCalendarStatements.setCalendarAmount(3); + truncateNewCalendarStatements.run(_context, dao); + + // Verify that GtfsMutableRelationalDao object contains exactly one calendar and one calendar date entry + assertEquals(1,dao.getAllCalendars().size()); + assertEquals(1,dao.getAllCalendarDates().size()); + } + + @Test + public void testTruncateCalendarStatementsWithinSevenDays() throws IOException { + GtfsMutableRelationalDao dao = _gtfs.read(); + // Set calendarField 6 -> Calendar.DAY_OF_YEAR + truncateNewCalendarStatements.setCalendarField(6); + // Set calendarAmount : 7 -> 7 Days + truncateNewCalendarStatements.setCalendarAmount(7); + truncateNewCalendarStatements.run(_context, dao); + // Verify that GtfsMutableRelationalDao object contains exactly one calendar and three calendar dates entries + assertEquals(1,dao.getAllCalendars().size()); + assertEquals(3,dao.getAllCalendarDates().size()); + } + + // Helper function to get today's date in the required format + public static String getCurrentDateFormatted(Integer daysOffset) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd"); + LocalDate date = LocalDate.now(); + if (daysOffset != null){ + date = date.plusDays(daysOffset); + } + // Format date as "yyyyMMdd" + return date.format(formatter); + } + +} \ No newline at end of file diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/CalendarExtensionStrategyTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/CalendarExtensionStrategyTest.java index b771193dc..ab9de5526 100644 --- a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/CalendarExtensionStrategyTest.java +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/CalendarExtensionStrategyTest.java @@ -15,15 +15,15 @@ */ package org.onebusaway.gtfs_transformer.updates; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.util.Set; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.impl.calendar.CalendarServiceDataFactoryImpl; import org.onebusaway.gtfs.model.AgencyAndId; import org.onebusaway.gtfs.model.ServiceCalendar; @@ -41,7 +41,7 @@ public class CalendarExtensionStrategyTest { private TransformContext _context = new TransformContext(); - @Before + @BeforeEach public void setup() throws IOException { _gtfs = MockGtfs.create(); _gtfs.putAgencies(1); diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/CalendarSimplicationLibraryTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/CalendarSimplicationLibraryTest.java index 237562a3b..4a012007c 100644 --- a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/CalendarSimplicationLibraryTest.java +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/CalendarSimplicationLibraryTest.java @@ -15,13 +15,13 @@ */ package org.onebusaway.gtfs_transformer.updates; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; import java.util.ArrayList; import java.util.List; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.impl.calendar.CalendarServiceDataFactoryImpl; import org.onebusaway.gtfs.model.AgencyAndId; import org.onebusaway.gtfs.model.ServiceCalendar; diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/CalendarSimplicationStrategyTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/CalendarSimplicationStrategyTest.java index 652b75526..8c79cf5cc 100644 --- a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/CalendarSimplicationStrategyTest.java +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/CalendarSimplicationStrategyTest.java @@ -15,13 +15,13 @@ */ package org.onebusaway.gtfs_transformer.updates; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import java.util.List; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.model.AgencyAndId; import org.onebusaway.gtfs.model.ServiceCalendar; import org.onebusaway.gtfs.model.ServiceCalendarDate; @@ -31,7 +31,7 @@ public class CalendarSimplicationStrategyTest extends AbstractTestSupport { - @Before + @BeforeEach public void setup() { _transformer.addTransform(new CalendarSimplicationStrategy()); } diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/CarryForwardExpectedFilesTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/CarryForwardExpectedFilesTest.java index 8fcef1bcd..94be17e4b 100644 --- a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/CarryForwardExpectedFilesTest.java +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/CarryForwardExpectedFilesTest.java @@ -15,9 +15,9 @@ */ package org.onebusaway.gtfs_transformer.updates; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.impl.FileSupport; import org.onebusaway.gtfs.impl.GtfsRelationalDaoImpl; import org.onebusaway.gtfs.impl.ZipHandler; @@ -28,7 +28,7 @@ import java.io.IOException; import java.net.URISyntaxException; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Ensure metadata files are still present in typical read/write cycle. @@ -42,12 +42,12 @@ public class CarryForwardExpectedFilesTest { private FileSupport _support = new FileSupport(); - @Before + @BeforeEach public void setup() throws IOException, URISyntaxException { _dao = new GtfsRelationalDaoImpl(); } - @After + @AfterEach public void teardown() { _support.cleanup(); } @@ -75,8 +75,8 @@ public void testFile() throws Exception { String modLocation = _tmpFileDirectory.getAbsolutePath() + File.separator + "modifications.txt"; File expectedFile = new File(modLocation); // verify modifications.txt is there!!!! - assertTrue("expected modifications.txt to be present!", expectedFile.exists()); - assertTrue("expected modifications.txt to be a file!", expectedFile.isFile()); + assertTrue(expectedFile.exists(), "expected modifications.txt to be present!"); + assertTrue(expectedFile.isFile(), "expected modifications.txt to be a file!"); } @Test diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/DeduplicateServiceIdsStrategyTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/DeduplicateServiceIdsStrategyTest.java index 2e70aecda..822cbf469 100644 --- a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/DeduplicateServiceIdsStrategyTest.java +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/DeduplicateServiceIdsStrategyTest.java @@ -15,14 +15,14 @@ */ package org.onebusaway.gtfs_transformer.updates; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import java.io.IOException; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.model.AgencyAndId; import org.onebusaway.gtfs.model.Trip; import org.onebusaway.gtfs.services.GtfsMutableRelationalDao; @@ -37,7 +37,7 @@ public class DeduplicateServiceIdsStrategyTest { private TransformContext _context = new TransformContext(); - @Before + @BeforeEach public void setup() throws IOException { _gtfs = MockGtfs.create(); _gtfs.putAgencies(1); diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/EntityRetentionGraphTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/EntityRetentionGraphTest.java index c877c8a89..9475b32b8 100644 --- a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/EntityRetentionGraphTest.java +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/EntityRetentionGraphTest.java @@ -15,24 +15,22 @@ */ package org.onebusaway.gtfs_transformer.updates; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.util.List; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.impl.GtfsRelationalDaoImpl; import org.onebusaway.gtfs.model.AgencyAndId; -import org.onebusaway.gtfs.model.FareRule; import org.onebusaway.gtfs.model.ShapePoint; import org.onebusaway.gtfs.model.Stop; import org.onebusaway.gtfs.serialization.GtfsReader; -import org.onebusaway.gtfs.services.GtfsMutableRelationalDao; import org.onebusaway.gtfs.services.GtfsRelationalDao; import org.onebusaway.gtfs.services.MockGtfs; import org.onebusaway.gtfs_transformer.factory.EntityRetentionGraph; @@ -43,7 +41,7 @@ public class EntityRetentionGraphTest { private EntityRetentionGraph _graph; - @Before + @BeforeEach public void setup() throws IOException, URISyntaxException { GtfsRelationalDaoImpl dao = new GtfsRelationalDaoImpl(); _dao = dao; diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/InterpolateStopTimesFromTimePointsStrategyTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/InterpolateStopTimesFromTimePointsStrategyTest.java index 7c923eef2..d041c82da 100644 --- a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/InterpolateStopTimesFromTimePointsStrategyTest.java +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/InterpolateStopTimesFromTimePointsStrategyTest.java @@ -15,8 +15,8 @@ */ package org.onebusaway.gtfs_transformer.updates; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.model.StopTime; import org.onebusaway.gtfs.model.Trip; import org.onebusaway.gtfs.services.GtfsMutableRelationalDao; @@ -30,7 +30,7 @@ import java.util.Date; import java.util.List; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Sanity check the interpolation strategy. @@ -44,7 +44,7 @@ public class InterpolateStopTimesFromTimePointsStrategyTest { private TransformContext _context = new TransformContext(); - @Before + @BeforeEach public void setup() throws IOException { _gtfs = MockGtfs.create(); } diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/LastStopToHeadsignStrategyTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/LastStopToHeadsignStrategyTest.java index cc255b71f..45eb4c038 100644 --- a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/LastStopToHeadsignStrategyTest.java +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/LastStopToHeadsignStrategyTest.java @@ -16,9 +16,12 @@ package org.onebusaway.gtfs_transformer.updates; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.impl.GtfsRelationalDaoImpl; import org.onebusaway.gtfs.model.AgencyAndId; import org.onebusaway.gtfs.model.Trip; @@ -32,7 +35,7 @@ public class LastStopToHeadsignStrategyTest { private GtfsRelationalDaoImpl _dao; - @Before + @BeforeEach public void setup() throws IOException, URISyntaxException { _dao = new GtfsRelationalDaoImpl(); @@ -53,10 +56,10 @@ public void test() { tripId.setAgencyId("agency"); Trip trip = _dao.getTripForId(tripId); - Assert.assertNotSame("C",trip.getTripHeadsign()); + assertNotSame("C",trip.getTripHeadsign()); _strategy.run(new TransformContext(), _dao); trip = _dao.getTripForId(tripId); - Assert.assertEquals("C",trip.getTripHeadsign()); + assertEquals("C",trip.getTripHeadsign()); } } \ No newline at end of file diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/MTAStationAccessibilityStrategyTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/MTAStationAccessibilityStrategyTest.java index 176e8eceb..46a436484 100644 --- a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/MTAStationAccessibilityStrategyTest.java +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/MTAStationAccessibilityStrategyTest.java @@ -15,8 +15,8 @@ */ package org.onebusaway.gtfs_transformer.updates; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.model.AgencyAndId; import org.onebusaway.gtfs.model.Stop; import org.onebusaway.gtfs.services.GtfsRelationalDao; @@ -24,8 +24,8 @@ import java.io.File; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.onebusaway.gtfs_transformer.csv.MTAStation.*; /** @@ -33,7 +33,7 @@ */ public class MTAStationAccessibilityStrategyTest extends AbstractTestSupport { - @Before + @BeforeEach public void setup() throws Exception { File stations_csv = new File(getClass().getResource( "/org/onebusaway/gtfs_transformer/stations/stations.csv").toURI()); @@ -66,17 +66,16 @@ private void assertStation(GtfsRelationalDao dao, String stopId, int ada, Intege Stop sStop = dao.getStopForId(new AgencyAndId("a0", stopId+"S")); assertNotNull(sStop); - assertEquals("expecting ada flag to match wheelchairBoarding flag for stop " + parentStop.getId(), - ada, converGTFSccessibilityToMTA(parentStop.getWheelchairBoarding())); + assertEquals(converGTFSccessibilityToMTA(parentStop.getWheelchairBoarding()), ada, "expecting ada flag to match wheelchairBoarding flag for stop " + parentStop.getId()); if (northBoundFlag == null) { - assertEquals("expecting N/A wheelchairBoarding for northbound stop " + nStop,0, converGTFSccessibilityToMTA(nStop.getWheelchairBoarding())); // default is 0 + assertEquals(0, converGTFSccessibilityToMTA(nStop.getWheelchairBoarding()), "expecting N/A wheelchairBoarding for northbound stop " + nStop); // default is 0 } else { - assertEquals("expecting northBoundFlag to match wheelchairBoarding flag for stop" + nStop, northBoundFlag.intValue(), converGTFSccessibilityToMTA(nStop.getWheelchairBoarding())); + assertEquals(northBoundFlag.intValue(), converGTFSccessibilityToMTA(nStop.getWheelchairBoarding()), "expecting northBoundFlag to match wheelchairBoarding flag for stop" + nStop); } if (southBoundFlag == null) { - assertEquals("expecting N/A wheelchairBoarding for southbound stop " + sStop,0, converGTFSccessibilityToMTA(sStop.getWheelchairBoarding())); + assertEquals(0, converGTFSccessibilityToMTA(sStop.getWheelchairBoarding()), "expecting N/A wheelchairBoarding for southbound stop " + sStop); } else { - assertEquals("expecting southBoundFlag to match wheelchairBoarding flag for stop" + sStop, southBoundFlag.intValue(), converGTFSccessibilityToMTA(sStop.getWheelchairBoarding())); + assertEquals(southBoundFlag.intValue(), converGTFSccessibilityToMTA(sStop.getWheelchairBoarding()), "expecting southBoundFlag to match wheelchairBoarding flag for stop" + sStop); } } diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/RemoveNonRevenueStopsStrategyTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/RemoveNonRevenueStopsStrategyTest.java index 7c1f8252b..1ea211742 100644 --- a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/RemoveNonRevenueStopsStrategyTest.java +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/RemoveNonRevenueStopsStrategyTest.java @@ -15,13 +15,13 @@ */ package org.onebusaway.gtfs_transformer.updates; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; import java.io.IOException; import java.util.List; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.model.StopTime; import org.onebusaway.gtfs.model.Trip; import org.onebusaway.gtfs.services.GtfsMutableRelationalDao; @@ -33,7 +33,7 @@ public class RemoveNonRevenueStopsStrategyTest { private MockGtfs _gtfs; - @Before + @BeforeEach public void before() throws IOException { _gtfs = MockGtfs.create(); } diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/RemoveRepeatedStopTimesStrategyTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/RemoveRepeatedStopTimesStrategyTest.java index c7cf84b9f..de6e363c3 100644 --- a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/RemoveRepeatedStopTimesStrategyTest.java +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/RemoveRepeatedStopTimesStrategyTest.java @@ -15,11 +15,10 @@ */ package org.onebusaway.gtfs_transformer.updates; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.collections.tuple.T2; import org.onebusaway.gtfs.impl.GtfsRelationalDaoImpl; -import org.onebusaway.gtfs.model.AgencyAndId; import org.onebusaway.gtfs.model.StopTime; import org.onebusaway.gtfs.model.Trip; import org.onebusaway.gtfs.serialization.GtfsReader; @@ -32,8 +31,8 @@ import java.util.List; import java.util.Map; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.onebusaway.gtfs_transformer.updates.TripsByBlockInSortedOrder.getTripsByBlockAndServiceIdInSortedOrder; /** @@ -43,7 +42,7 @@ public class RemoveRepeatedStopTimesStrategyTest { private GtfsRelationalDaoImpl _dao; private TransformContext _context = new TransformContext(); - @Before + @BeforeEach public void before() throws IOException { _dao = new GtfsRelationalDaoImpl(); } diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/ShapeDirectionTransformStrategyTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/ShapeDirectionTransformStrategyTest.java index 4bda9284f..86b31085b 100644 --- a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/ShapeDirectionTransformStrategyTest.java +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/ShapeDirectionTransformStrategyTest.java @@ -15,8 +15,8 @@ */ package org.onebusaway.gtfs_transformer.updates; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import org.onebusaway.gtfs.model.AgencyAndId; import org.onebusaway.gtfs.model.ShapePoint; @@ -25,8 +25,8 @@ import org.onebusaway.gtfs.services.MockGtfs; import org.onebusaway.gtfs_transformer.services.TransformContext; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.util.Collection; @@ -41,7 +41,7 @@ public class ShapeDirectionTransformStrategyTest { private MockGtfs _gtfs; - @Before + @BeforeEach public void before() throws IOException { _gtfs = MockGtfs.create(); } diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/ShiftNegativeStopTimesUpdateStrategyTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/ShiftNegativeStopTimesUpdateStrategyTest.java index cd659acba..33e81b945 100644 --- a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/ShiftNegativeStopTimesUpdateStrategyTest.java +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/ShiftNegativeStopTimesUpdateStrategyTest.java @@ -15,13 +15,13 @@ */ package org.onebusaway.gtfs_transformer.updates; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; import java.util.List; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.model.ServiceCalendar; import org.onebusaway.gtfs.model.StopTime; import org.onebusaway.gtfs.model.Trip; @@ -37,7 +37,7 @@ public class ShiftNegativeStopTimesUpdateStrategyTest { private MockGtfs _gtfs; - @Before + @BeforeEach public void before() throws IOException { _gtfs = MockGtfs.create(); } diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/TrimTripTransformStrategyTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/TrimTripTransformStrategyTest.java index 161eef65f..7c090237c 100644 --- a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/TrimTripTransformStrategyTest.java +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/TrimTripTransformStrategyTest.java @@ -15,14 +15,14 @@ */ package org.onebusaway.gtfs_transformer.updates; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; import java.util.Collection; import java.util.List; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.collections.beans.PropertyPathExpression; import org.onebusaway.gtfs.model.AgencyAndId; import org.onebusaway.gtfs.model.ShapePoint; @@ -45,7 +45,7 @@ public class TrimTripTransformStrategyTest { private TransformContext _context = new TransformContext(); - @Before + @BeforeEach public void setup() throws IOException { _gtfs = MockGtfs.create(); } diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/UpdateStopNameFromParentStationIfInvalidStrategyTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/UpdateStopNameFromParentStationIfInvalidStrategyTest.java index 6641e0f57..ae485faeb 100644 --- a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/UpdateStopNameFromParentStationIfInvalidStrategyTest.java +++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/UpdateStopNameFromParentStationIfInvalidStrategyTest.java @@ -15,8 +15,8 @@ */ package org.onebusaway.gtfs_transformer.updates; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.model.AgencyAndId; import org.onebusaway.gtfs.services.GtfsMutableRelationalDao; import org.onebusaway.gtfs.services.MockGtfs; @@ -25,18 +25,19 @@ import java.io.IOException; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; public class UpdateStopNameFromParentStationIfInvalidStrategyTest { private MockGtfs _gtfs; - @Before + @BeforeEach public void before() throws IOException { _gtfs = MockGtfs.create(); } - @Test public void test() throws IOException { + @Test + public void test() throws IOException { _gtfs.putAgencies(1); _gtfs.putLines("stops.txt", "stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station,platform_code", diff --git a/onebusaway-gtfs/pom.xml b/onebusaway-gtfs/pom.xml index ebeb4b2b9..9fc727129 100644 --- a/onebusaway-gtfs/pom.xml +++ b/onebusaway-gtfs/pom.xml @@ -9,29 +9,24 @@ org.onebusaway onebusaway-gtfs-modules - 1.4.15-openmove-5 + 5.0.1-openmove-1 + ../pom.xml org.onebusaway onebusaway-csv-entities - - - slf4j-api - org.slf4j - - + ${project.version} org.slf4j slf4j-api - ${slf4j_version} com.fasterxml.jackson.core jackson-databind - 2.14.0 + 2.18.2 de.grundid.opendatalab @@ -41,12 +36,11 @@ org.slf4j slf4j-simple - ${slf4j_version} + test - junit - junit - test + org.junit.jupiter + junit-jupiter-api org.mockito @@ -58,14 +52,29 @@ - com.mycila - license-maven-plugin + com.google.cloud.tools + jib-maven-plugin - - src/test/resources/org/onebusaway/gtfs/** - + + true + + + io.github.zlika + reproducible-build-maven-plugin + 0.17 + + + run-when-packaged + + strip-jar + + package + + + + diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/annotations/Experimental.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/annotations/Experimental.java new file mode 100644 index 000000000..905612c9e --- /dev/null +++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/annotations/Experimental.java @@ -0,0 +1,36 @@ +/** + * Copyright (C) 2024 OneBusAway contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.gtfs.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.annotation.Documented; + +/** + * This annotation indicates that a field is experimental. + */ +@Retention(value = RetentionPolicy.SOURCE) +@Target(value = ElementType.FIELD) +@Documented +public @interface Experimental { + /** + * This indicates what issue this field was proposed in. + * Ideally this should be a URL. + */ + String proposedBy(); +} diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/GtfsDaoImpl.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/GtfsDaoImpl.java index 89d411605..5ae18606e 100644 --- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/GtfsDaoImpl.java +++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/GtfsDaoImpl.java @@ -146,7 +146,9 @@ public Collection getAllRiderships() { return getAllEntitiesForType(Ridership.class); } - public Collection getAllVehicles() { return getAllEntitiesForType(Vehicle.class); } + public Collection getAllVehicles() { + return getAllEntitiesForType(Vehicle.class); + } public Collection getAllLevels() { return getAllEntitiesForType(Level.class); @@ -253,6 +255,13 @@ public Level getLevelForId(AgencyAndId id) { return getEntityForId(Level.class, id); } + public Vehicle getVehicleForId(AgencyAndId id) { + return getEntityForId(Vehicle.class, id); + } + + public Collection getAllNetworks() { + return getAllEntitiesForType(Network.class); + } public Facility getFacilityForId(AgencyAndId id) { return getEntityForId(Facility.class, id);} @@ -304,11 +313,6 @@ public Collection getAllStopAreaElements() { return getAllEntitiesForType(StopAreaElement.class); } - @Override - public Collection getAllStopAreas() { - return getAllEntitiesForType(StopArea.class); - } - public Collection getAllLocations() { return getAllEntitiesForType(Location.class); } diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/GtfsDataServiceImpl.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/GtfsDataServiceImpl.java index 033eb3976..680b07b9e 100644 --- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/GtfsDataServiceImpl.java +++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/GtfsDataServiceImpl.java @@ -353,6 +353,16 @@ public Collection getAllRiderships() { return _dao.getAllRiderships(); } + @Override + public Collection getAllVehicles() { + return _dao.getAllVehicles(); + } + + @Override + public Vehicle getVehicleForId(AgencyAndId id) { + return _dao.getVehicleForId(id); + } + @Override public Collection getAllAreas() { return _dao.getAllAreas(); @@ -368,11 +378,6 @@ public Collection getAllStopAreaElements() { return _dao.getAllStopAreaElements(); } - @Override - public Collection getAllStopAreas() { - return _dao.getAllStopAreas(); - } - @Override public Collection getAllLocationGroups() { return _dao.getAllLocationGroups(); @@ -393,6 +398,11 @@ public Collection getAllTranslations() { return _dao.getAllTranslations(); } + @Override + public Collection getAllNetworks() { + return _dao.getAllNetworks(); + } + @Override public List getOptionalMetadataFilenames() { return _dao.getOptionalMetadataFilenames(); diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/StopTimeArray.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/StopTimeArray.java index a2a73f1be..1561e0ff7 100644 --- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/StopTimeArray.java +++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/StopTimeArray.java @@ -21,12 +21,7 @@ import java.util.Iterator; import java.util.NoSuchElementException; -import org.onebusaway.gtfs.model.Area; -import org.onebusaway.gtfs.model.BookingRule; -import org.onebusaway.gtfs.model.StopLocation; -import org.onebusaway.gtfs.model.StopTime; -import org.onebusaway.gtfs.model.StopTimeProxy; -import org.onebusaway.gtfs.model.Trip; +import org.onebusaway.gtfs.model.*; public class StopTimeArray extends AbstractList { @@ -36,6 +31,10 @@ public class StopTimeArray extends AbstractList { private StopLocation[] stops = new StopLocation[0]; + private StopLocation[] locations = new StopLocation[0]; + + private StopLocation[] locationGroups = new StopLocation[0]; + private Area[] startServiceAreas = new Area[0]; private Area[] endServiceAreas = new Area[0]; @@ -89,6 +88,8 @@ public boolean add(StopTime stopTime) { startServiceAreas[index] = stopTime.getStartServiceArea(); endServiceAreas[index] = stopTime.getEndServiceArea(); stops[index] = stopTime.getStop(); + locations[index] = stopTime.getLocation(); + locationGroups[index] = stopTime.getLocationGroup(); arrivalTimes[index] = stopTime.getArrivalTime(); departureTimes[index] = stopTime.getDepartureTime(); timepoints[index] = stopTime.getTimepoint(); @@ -151,6 +152,8 @@ private void setLength(int newLength) { this.startServiceAreas = Arrays.copyOf(this.startServiceAreas, newLength); this.endServiceAreas = Arrays.copyOf(this.endServiceAreas, newLength); this.stops = Arrays.copyOf(this.stops, newLength); + this.locationGroups = Arrays.copyOf(this.locationGroups,newLength); + this.locations = Arrays.copyOf(this.locations,newLength); this.arrivalTimes = Arrays.copyOf(this.arrivalTimes, newLength); this.departureTimes = Arrays.copyOf(this.departureTimes, newLength); this.timepoints = Arrays.copyOf(this.timepoints, newLength); @@ -260,12 +263,12 @@ public StopLocation getStop() { @Override public StopLocation getLocation() { - return stops[index]; + return locations[index]; } @Override public StopLocation getLocationGroup() { - return stops[index]; + return locationGroups[index]; } @Override @@ -275,12 +278,12 @@ public void setStop(StopLocation stop) { @Override public void setLocation(StopLocation location) { - stops[index] = location; + locations[index] = location; } @Override public void setLocationGroup(StopLocation group) { - stops[index] = group; + locationGroups[index] = group; } @Override diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Agency.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Agency.java index f80f9f1fa..81a84ff21 100644 --- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Agency.java +++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Agency.java @@ -64,7 +64,6 @@ public Agency(Agency a) { this.timezone = a.timezone; this.lang = a.lang; this.phone = a.phone; - this.brandingUrl = a.brandingUrl; this.email = a.email; this.fareUrl = a.fareUrl; this.vatCode = a.vatCode; @@ -154,4 +153,4 @@ public void setVatCode(String vatCode) { public String toString() { return ""; } -} \ No newline at end of file +} diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/AlternateStopNameException.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/AlternateStopNameException.java index 4dd04882f..6a57c4ed6 100644 --- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/AlternateStopNameException.java +++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/AlternateStopNameException.java @@ -26,13 +26,13 @@ public class AlternateStopNameException extends IdentityBean { private int id; @CsvField(optional = true) - int routeId; + String routeId; @CsvField(optional = true) int directionId; @CsvField(optional = true) - int stopId; + String stopId; @CsvField(optional = true) String alternateStopName; @@ -58,11 +58,11 @@ public void setId(Integer id) { this.id = id; } - public int getRouteId() { + public String getRouteId() { return routeId; } - public void setRouteId(int routeId) { + public void setRouteId(String routeId) { this.routeId = routeId; } @@ -74,11 +74,11 @@ public void setDirectionId(int directionId) { this.directionId = directionId; } - public int getStopId() { + public String getStopId() { return stopId; } - public void setStopId(int stopId) { + public void setStopId(String stopId) { this.stopId = stopId; } diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Area.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Area.java index b09828c10..f0c304394 100644 --- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Area.java +++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Area.java @@ -15,6 +15,10 @@ */ package org.onebusaway.gtfs.model; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; import org.onebusaway.csv_entities.schema.annotations.CsvField; import org.onebusaway.csv_entities.schema.annotations.CsvFields; import org.onebusaway.gtfs.serialization.mappings.DefaultAgencyIdFieldMappingFactory; @@ -22,16 +26,17 @@ @CsvFields(filename = "areas.txt", required = false) public final class Area extends IdentityBean { - private static final long serialVersionUID = 1L; - @CsvField(name="area_id", mapping = DefaultAgencyIdFieldMappingFactory.class) private AgencyAndId id; @CsvField(name="area_name", optional = true) private String name; - @CsvField(name="wkt", optional = true) - private String wkt; + // we use a List, not Set to keep the insertion order. by definition these stops don't have an + // order but it's nice for clients to not randomly change it. + @CsvField(ignore = true) + private List stops = new ArrayList<>(); + public Area() { @@ -40,10 +45,8 @@ public Area() { public Area(Area a) { this.id = a.id; this.name = a.name; - this.wkt = a.wkt; } - public String getAreaId() { return id.getId(); } @@ -52,6 +55,15 @@ public AgencyAndId getId() { return id; } + private void setStops(Collection stops) { + this.stops = List.copyOf(stops); + } + + public void addStop(Stop stop) { + this.stops.add(stop); + } + + public void setId(AgencyAndId areaId) { this.id = areaId; } @@ -60,11 +72,7 @@ public void setId(AgencyAndId areaId) { public void setName(String name) { this.name = name; } - public String getWkt() { - return wkt; - } - - public void setWkt(String wkt) { - this.wkt = wkt; + public Collection getStops() { + return List.copyOf(stops); } } diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/DuplicateTrips.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/DuplicateTrips.java deleted file mode 100644 index ae5f82950..000000000 --- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/DuplicateTrips.java +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Copyright (C) 2018 Cambridge Systematics, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.onebusaway.gtfs.model; - -import java.util.ArrayList; - -public class DuplicateTrips { - - private String id; - - private String serviceId; - - private ArrayList trips = new ArrayList(); - - private ArrayList dates = new ArrayList(); - - public DuplicateTrips() { - - } - - public DuplicateTrips(String id, String svcId, ArrayList trips) { - this.setId(id); - this.setServiceId(svcId); - this.setTrips(trips); - } - - public DuplicateTrips(DuplicateTrips dts) { - this.setId(dts.getId()); - this.setServiceId(dts.getServiceId()); - this.setTrips(dts.getTrips()); - this.setDates(dts.getDates()); - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getServiceId() { - return serviceId; - } - - public void setServiceId(String serviceId) { - this.serviceId = serviceId; - } - - public ArrayList getTrips() { - return trips; - } - - public void setTrips(ArrayList trips) { - this.trips = trips; - } - - public void addTrip(Trip trip) { - this.trips.add(trip); - } - - public ArrayList getDates() { - return dates; - } - - public void setDates(ArrayList dates) { - this.dates = dates; - } - - public void addServiceDate(ServiceCalendarDate date) { - this.dates.add(date); - } - - -} diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Icon.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Icon.java new file mode 100644 index 000000000..337031f7b --- /dev/null +++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Icon.java @@ -0,0 +1,61 @@ +/** + * Copyright (C) 2024 Cambridge Systematics, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.gtfs.model; + +import org.onebusaway.csv_entities.schema.annotations.CsvField; +import org.onebusaway.csv_entities.schema.annotations.CsvFields; +import org.onebusaway.gtfs.serialization.mappings.DefaultAgencyIdFieldMappingFactory; + +/** + * GTFS Extension representing icon configuration data. + */ +@CsvFields(filename = "icons.txt", required = false, prefix = "icon_") +public final class Icon extends IdentityBean{ + private static final long serialVersionUID = 1L; + + @CsvField(mapping = DefaultAgencyIdFieldMappingFactory.class) + private AgencyAndId id; + @CsvField(optional = true) + private String description; + @CsvField + private String url; + + @Override + public AgencyAndId getId() { + return id; + } + + @Override + public void setId(AgencyAndId id) { + this.id = id; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } +} diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/LocationGroup.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/LocationGroup.java index 46ef67e98..2fe154b51 100644 --- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/LocationGroup.java +++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/LocationGroup.java @@ -15,18 +15,29 @@ */ package org.onebusaway.gtfs.model; -import java.util.HashSet; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; import java.util.Set; +import org.onebusaway.csv_entities.schema.annotations.CsvField; +import org.onebusaway.csv_entities.schema.annotations.CsvFields; +import org.onebusaway.gtfs.serialization.mappings.DefaultAgencyIdFieldMappingFactory; +@CsvFields(filename = "location_groups.txt", required = false) public class LocationGroup extends IdentityBean implements StopLocation { private static final long serialVersionUID = 1L; + @CsvField(name = "location_group_id", mapping = DefaultAgencyIdFieldMappingFactory.class) private AgencyAndId id; - private Set locations = new HashSet<>(); - + @CsvField(name = "location_group_name", optional = true) private String name; + // we use a List, not Set to keep the insertion order. by definition these stops don't have an + // order but it's nice for clients to not randomly change it. + @CsvField(ignore = true) + private List stops = new ArrayList<>(); + @Override public AgencyAndId getId() { return id; @@ -36,23 +47,22 @@ public void setId(AgencyAndId id) { this.id = id; } - public Set getLocations() { - return locations; + public String getName() { + return name; } - private void setLocations(Set locations) { - this.locations = locations; + public void setName(String name) { + this.name = name; } - public void addLocation(StopLocation location) { - this.locations.add(location); + public void addLocation(StopLocation stop) { + stops.add(stop); } - - public String getName() { - return name; + public void setLocations(Collection stop) { + stops.addAll(stop); } - public void setName(String name) { - this.name = name; + public Set getLocations() { + return Set.copyOf(stops); } } diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/LocationGroupElement.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/LocationGroupElement.java index f12f17c08..2e508c8cd 100644 --- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/LocationGroupElement.java +++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/LocationGroupElement.java @@ -20,19 +20,18 @@ import org.onebusaway.gtfs.serialization.mappings.DefaultAgencyIdFieldMappingFactory; import org.onebusaway.gtfs.serialization.mappings.StopLocationFieldMappingFactory; -@CsvFields(filename = "location_groups.txt", required = false, prefix = "location_group_") +@CsvFields(filename = "location_group_stops.txt", required = false, prefix = "location_group_") public class LocationGroupElement extends IdentityBean { private static final long serialVersionUID = 1L; - @CsvField(ignore = true) private int id; @CsvField(name = "location_group_id", mapping = DefaultAgencyIdFieldMappingFactory.class) private AgencyAndId locationGroupId; @CsvField(name = "stop_id", mapping = StopLocationFieldMappingFactory.class) - private StopLocation location; + private StopLocation stop; @CsvField(optional = true) private String name; @@ -54,12 +53,12 @@ public void setLocationGroupId(AgencyAndId locationGroupId) { this.locationGroupId = locationGroupId; } - public StopLocation getLocation() { - return location; + public StopLocation getStop() { + return stop; } - public void setLocation(StopLocation location) { - this.location = location; + public void setStop(StopLocation stop) { + this.stop = stop; } public String getName() { diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Network.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Network.java new file mode 100644 index 000000000..f07be5940 --- /dev/null +++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Network.java @@ -0,0 +1,55 @@ +/** + * Copyright (C) 2024 Sound Transit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.gtfs.model; + +import org.onebusaway.csv_entities.schema.annotations.CsvField; +import org.onebusaway.csv_entities.schema.annotations.CsvFields; +import org.onebusaway.gtfs.serialization.mappings.DefaultAgencyIdFieldMappingFactory; + +@CsvFields(filename = "networks.txt", required = false) +public final class Network extends IdentityBean { + + private static final long serialVersionUID = 1L; + + @CsvField(name = "network_id", mapping = DefaultAgencyIdFieldMappingFactory.class) + private AgencyAndId id; + + @CsvField(name = "network_name", optional = true) + private String name; + + @Override + public AgencyAndId getId() { + return id; + } + + @Override + public void setId(AgencyAndId id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return ""; + } +} diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Route.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Route.java index de57e352b..e74b53726 100644 --- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Route.java +++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Route.java @@ -59,10 +59,6 @@ public final class Route extends IdentityBean { @CsvField(name = "eligibility_restricted", optional = true, defaultValue = "-999") private int eligibilityRestricted = MISSING_VALUE; - - @Deprecated - @CsvField(name="route_bikes_allowed", optional = true, defaultValue = "0") - private int routeBikesAllowed = 0; /** * 0 = unknown / unspecified, 1 = bikes allowed, 2 = bikes NOT allowed @@ -73,9 +69,6 @@ public final class Route extends IdentityBean { @CsvField(optional = true) private int sortOrder = MISSING_VALUE; - @CsvField(optional = true) - private String brandingUrl; - // Custom extension representing (bus) route accepts regional fare card. // That is it has a vending machine on board. @CsvField(optional = true, name = "regional_fare_card", defaultValue = "0") @@ -101,7 +94,6 @@ public Route(Route r) { this.textColor = r.textColor; this.bikesAllowed = r.bikesAllowed; this.sortOrder = r.sortOrder; - this.brandingUrl = r.brandingUrl; this.eligibilityRestricted = r.eligibilityRestricted; this.regionalFareCardAccepted = r.regionalFareCardAccepted; this.bookingRule = r.bookingRule; @@ -178,16 +170,6 @@ public String getTextColor() { public void setTextColor(String textColor) { this.textColor = textColor; } - - @Deprecated - public int getRouteBikesAllowed() { - return routeBikesAllowed; - } - - @Deprecated - public void setRouteBikesAllowed(int routeBikesAllowed) { - this.routeBikesAllowed = routeBikesAllowed; - } /** * @return 0 = unknown / unspecified, 1 = bikes allowed, 2 = bikes NOT allowed @@ -216,14 +198,6 @@ public void setSortOrder(int sortOrder) { this.sortOrder = sortOrder; } - public String getBrandingUrl() { - return brandingUrl; - } - - public void setBrandingUrl(String brandingUrl) { - this.brandingUrl = brandingUrl; - } - public boolean hasEligibilityRestricted() { return eligibilityRestricted != MISSING_VALUE; } diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/StopArea.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/StopArea.java deleted file mode 100644 index 2d2d74a64..000000000 --- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/StopArea.java +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright (C) 2023 Leonard Ehrenfried - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.onebusaway.gtfs.model; - -import java.util.HashSet; -import java.util.Set; - -public class StopArea extends IdentityBean implements StopLocation { - - private static final long serialVersionUID = 1L; - - private Area area; - - private Set stops = new HashSet<>(); - - @Override - public AgencyAndId getId() { - return area.getId(); - } - - @Override - public void setId(AgencyAndId id) { - } - - public void setArea(Area area) { - this.area = area; - } - - public Set getLocations() { - return stops; - } - - private void setLocations(Set stops) { - this.stops = stops; - } - - public void addLocation(StopLocation location) { - this.stops.add(location); - } - - public String getName() { - return area.getName(); - } - - @Override - public void setName(String name) { - - } - -} diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/StopAreaElement.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/StopAreaElement.java index b0ee1cf61..2ea7402a2 100644 --- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/StopAreaElement.java +++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/StopAreaElement.java @@ -25,8 +25,8 @@ public final class StopAreaElement extends IdentityBean { @CsvField(name = "area_id", mapping = EntityFieldMappingFactory.class) private Area area; - @CsvField(name = "stop_id", mapping = StopLocationFieldMappingFactory.class) - private StopLocation stopLocation; + @CsvField(name = "stop_id", mapping = EntityFieldMappingFactory.class) + private Stop stop; public void setArea(Area area) { this.area = area; @@ -38,7 +38,7 @@ public Area getArea() { @Override public AgencyAndId getId() { - return new AgencyAndId(getArea().getId().getAgencyId(), String.format("%s_%s", area.getId().getId(), stopLocation.getId().getId())); + return new AgencyAndId(getArea().getId().getAgencyId(), String.format("%s_%s", area.getId().getId(), stop.getId().getId())); } @Override @@ -46,11 +46,11 @@ public void setId(AgencyAndId id) { } - public void setStopLocation(StopLocation stopLocation) { - this.stopLocation = stopLocation; + public void setStop(Stop stop) { + this.stop = stop; } - public StopLocation getStopLocation() { - return stopLocation; + public Stop getStop() { + return stop; } } diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/StopTime.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/StopTime.java index 7e9bb9a72..34ffccdf6 100644 --- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/StopTime.java +++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/StopTime.java @@ -61,44 +61,12 @@ public final class StopTime extends IdentityBean implements @CsvField(optional = true, mapping = StopTimeFieldMappingFactory.class) private int departureTime = MISSING_VALUE; - /** - * @deprecated - * GTFS-Flex v2.1 renamed this field. Use {@link #startPickupDropOffWindow} instead. - */ - @Deprecated - @CsvField(optional = true, mapping = StopTimeFieldMappingFactory.class, defaultValue = "-999") - private int minArrivalTime = MISSING_VALUE; - @CsvField(optional = true, name = "start_pickup_drop_off_window", mapping = StopTimeFieldMappingFactory.class, defaultValue = "-999") private int startPickupDropOffWindow = MISSING_VALUE; - /** - * @deprecated - * GTFS-Flex v2.1 renamed "dropoff" to "drop off": https://github.com/MobilityData/gtfs-flex/commit/547200dfb580771265ae14b07d9bfd7b91c16ed2 - */ - @Deprecated - @CsvField(optional = true, name = "start_pickup_dropoff_window", mapping = StopTimeFieldMappingFactory.class, defaultValue = "-999") - public int oldSpellingOfStartPickupDropOffWindow = MISSING_VALUE; - - /** - * @deprecated - * GTFS-Flex v2.1 renamed this field. Use {@link #endPickupDropOffWindow} instead. - */ - @Deprecated - @CsvField(optional = true, mapping = StopTimeFieldMappingFactory.class, defaultValue = "-999") - private int maxDepartureTime = MISSING_VALUE; - @CsvField(optional = true, name = "end_pickup_drop_off_window", mapping = StopTimeFieldMappingFactory.class, defaultValue = "-999") private int endPickupDropOffWindow = MISSING_VALUE; - /** - * @deprecated - * GTFS-Flex v2.1 renamed "dropoff" to "drop off": https://github.com/MobilityData/gtfs-flex/commit/547200dfb580771265ae14b07d9bfd7b91c16ed2 - */ - @Deprecated - @CsvField(optional = true, name = "end_pickup_dropoff_window", mapping = StopTimeFieldMappingFactory.class, defaultValue = "-999") - public int oldSpellingOfEndPickupDropOffWindow = MISSING_VALUE; - @CsvField(optional = true, defaultValue = "-999") private int timepoint = MISSING_VALUE; @@ -192,9 +160,7 @@ public StopTime(StopTime st) { this.dropOffType = st.dropOffType; this.id = st.id; this.pickupType = st.pickupType; - this.minArrivalTime = st.minArrivalTime; this.startPickupDropOffWindow = st.startPickupDropOffWindow; - this.maxDepartureTime = st.maxDepartureTime; this.endPickupDropOffWindow = st.endPickupDropOffWindow; this.continuousPickup = st.continuousPickup; this.continuousDropOff = st.continuousDropOff; @@ -309,14 +275,14 @@ public StopLocation getLocationGroup() { * - location group */ public StopLocation getStopLocation(){ - if(stop != null){ - return stop; + if(getStop() != null){ + return getStop(); } - else if(location != null) { - return location; + else if(getLocation() != null) { + return getLocation(); } - else if(locationGroup != null){ - return locationGroup; + else if(getLocationGroup() != null){ + return getLocationGroup(); } return null; } @@ -411,50 +377,17 @@ public void clearDepartureTime() { this.departureTime = MISSING_VALUE; } - @Deprecated - public int getMinArrivalTime() { - return minArrivalTime; - } - - @Deprecated - public void setMinArrivalTime(int minArrivalTime) { - this.minArrivalTime = minArrivalTime; - } public int getStartPickupDropOffWindow() { - if (startPickupDropOffWindow != MISSING_VALUE) { - return startPickupDropOffWindow; - } else if(oldSpellingOfStartPickupDropOffWindow != MISSING_VALUE){ - return oldSpellingOfStartPickupDropOffWindow; - } else { - return minArrivalTime; - } + return startPickupDropOffWindow; } public void setStartPickupDropOffWindow(int startPickupDropOffWindow) { this.startPickupDropOffWindow = startPickupDropOffWindow; } - @Deprecated - public int getMaxDepartureTime() { - return maxDepartureTime; - } - - @Deprecated - public void setMaxDepartureTime(int maxDepartureTime) { - this.maxDepartureTime = maxDepartureTime; - } - public int getEndPickupDropOffWindow() { - if (endPickupDropOffWindow != MISSING_VALUE) { - return endPickupDropOffWindow; - } - else if (oldSpellingOfEndPickupDropOffWindow != MISSING_VALUE) { - return oldSpellingOfEndPickupDropOffWindow; - } - else { - return maxDepartureTime; - } + return endPickupDropOffWindow; } public void setEndPickupDropOffWindow(int endPickupDropOffWindow) { @@ -738,7 +671,7 @@ public String displayArrival() { @Override public String toString() { - return "StopTime(seq=" + getStopSequence() + " stop=" + (getStopLocation()==null?"NuLl":getStop().getId()) + return "StopTime(seq=" + getStopSequence() + " stop=" + (getStopLocation()==null?"NuLl":getStopLocation().getId()) + " trip=" + (getTrip()==null?"NuLl":getTrip().getId()) + " times=" + StopTimeFieldMappingFactory.getSecondsAsString(getArrivalTime()) + "-" @@ -806,30 +739,5 @@ public void setFreeRunningFlag(String freeRunningFlag) { } this.freeRunningFlag = freeRunningFlag; } - @Deprecated - public void setOldSpellingOfStartPickupDropOffWindow(int time) { - oldDropOffSpellingWarning("start"); - this.oldSpellingOfStartPickupDropOffWindow = time; - } - - @Deprecated - public void setOldSpellingOfEndPickupDropOffWindow(int time) { - oldDropOffSpellingWarning("end"); - this.oldSpellingOfEndPickupDropOffWindow = time; - } - private static void oldDropOffSpellingWarning(String type) { - _log.warn("This feed uses the old spelling of '{}_pickup_drop_off_window' ('dropoff' instead of 'drop_off'). " - + "Compatibility will be removed in the future, so please update your feed to be in line with the latest Flex V2 spec:" - + " https://github.com/MobilityData/gtfs-flex/commit/547200dfb", type); - } - @Deprecated - public int getOldSpellingOfStartPickupDropOffWindow() { - return this.oldSpellingOfStartPickupDropOffWindow; - } - - @Deprecated - public int getOldSpellingOfEndPickupDropOffWindow() { - return oldSpellingOfEndPickupDropOffWindow; - } } diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Trip.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Trip.java index e29e9d313..ff40ff0fb 100644 --- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Trip.java +++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Trip.java @@ -17,6 +17,7 @@ import org.onebusaway.csv_entities.schema.annotations.CsvField; import org.onebusaway.csv_entities.schema.annotations.CsvFields; +import org.onebusaway.gtfs.annotations.Experimental; import org.onebusaway.gtfs.serialization.mappings.DefaultAgencyIdFieldMappingFactory; import org.onebusaway.gtfs.serialization.mappings.EntityFieldMappingFactory; import org.onebusaway.gtfs.serialization.mappings.TripAgencyIdFieldMappingFactory; @@ -41,9 +42,6 @@ public final class Trip extends IdentityBean { @CsvField(optional = true) private String tripHeadsign; - @CsvField(optional = true) - private String routeShortName; - @CsvField(optional = true) private String directionId; @@ -56,52 +54,34 @@ public final class Trip extends IdentityBean { @CsvField(optional = true, defaultValue = "0") private int wheelchairAccessible = 0; - @CsvField(optional = true) - private String drtMaxTravelTime; - - @CsvField(optional = true) - private String drtAvgTravelTime; - - @CsvField(optional = true, defaultValue = "-1") - private double drtAdvanceBookMin; - - @CsvField(optional = true) - private String drtPickupMessage; - - @CsvField(optional = true) - private String drtDropOffMessage; - - @CsvField(optional = true) - private String continuousPickupMessage; - - @CsvField(optional = true) - private String continuousDropOffMessage; + /** + * 0 = unknown / unspecified, 1 = bikes allowed, 2 = bikes NOT allowed + */ + @CsvField(optional = true, defaultValue = "0") + private int bikesAllowed = 0; + @Experimental(proposedBy = "https://github.com/MobilityData/gtfs-flex/pull/79") @CsvField(optional = true) private Double meanDurationFactor; + @Experimental(proposedBy = "https://github.com/MobilityData/gtfs-flex/pull/79") @CsvField(optional = true) private Double meanDurationOffset; + @Experimental(proposedBy = "https://github.com/MobilityData/gtfs-flex/pull/79") @CsvField(optional = true) private Double safeDurationFactor; + @Experimental(proposedBy = "https://github.com/MobilityData/gtfs-flex/pull/79") @CsvField(optional = true) private Double safeDurationOffset; - - @Deprecated - @CsvField(optional = true, defaultValue = "0") - private int tripBikesAllowed = 0; /** - * 0 = unknown / unspecified, 1 = bikes allowed, 2 = bikes NOT allowed + * 0 = unknown / unspecified, 1 = cars allowed, 2 = cars NOT allowed */ + @Experimental(proposedBy="https://github.com/google/transit/issues/466") @CsvField(optional = true, defaultValue = "0") - private int bikesAllowed = 0; - - // Custom extension for KCM to specify a fare per-trip - @CsvField(optional = true) - private String fareId; + private int carsAllowed = 0; // Custom extension for MNR @CsvField(optional = true, name = "note_id", mapping = EntityFieldMappingFactory.class, order = -1) @@ -139,25 +119,16 @@ public Trip(Trip obj) { this.serviceId = obj.serviceId; this.tripShortName = obj.tripShortName; this.tripHeadsign = obj.tripHeadsign; - this.routeShortName = obj.routeShortName; this.directionId = obj.directionId; this.blockId = obj.blockId; this.shapeId = obj.shapeId; this.wheelchairAccessible = obj.wheelchairAccessible; - this.drtMaxTravelTime = obj.drtMaxTravelTime; - this.drtAvgTravelTime = obj.drtAvgTravelTime; - this.drtAdvanceBookMin = obj.drtAdvanceBookMin; - this.drtPickupMessage = obj.drtPickupMessage; - this.drtDropOffMessage = obj.drtDropOffMessage; - this.continuousPickupMessage = obj.continuousPickupMessage; - this.continuousDropOffMessage = obj.continuousDropOffMessage; this.meanDurationFactor = obj.meanDurationFactor; this.meanDurationOffset = obj.meanDurationOffset; this.safeDurationFactor = obj.safeDurationFactor; this.safeDurationOffset = obj.safeDurationOffset; - this.tripBikesAllowed = obj.tripBikesAllowed; this.bikesAllowed = obj.bikesAllowed; - this.fareId = obj.fareId; + this.carsAllowed = obj.carsAllowed; this.note = obj.note; this.peakOffpeak = obj.peakOffpeak; this.mtaTripId = obj.mtaTripId; @@ -205,14 +176,6 @@ public void setTripHeadsign(String tripHeadsign) { this.tripHeadsign = tripHeadsign; } - public String getRouteShortName() { - return routeShortName; - } - - public void setRouteShortName(String routeShortName) { - this.routeShortName = routeShortName; - } - public String getDirectionId() { return directionId; } @@ -245,62 +208,6 @@ public int getWheelchairAccessible() { return wheelchairAccessible; } - public String getDrtMaxTravelTime() { - return drtMaxTravelTime; - } - - public void setDrtMaxTravelTime(String drtMaxTravelTime) { - this.drtMaxTravelTime = drtMaxTravelTime; - } - - public String getDrtAvgTravelTime() { - return drtAvgTravelTime; - } - - public void setDrtAvgTravelTime(String drtAvgTravelTime) { - this.drtAvgTravelTime = drtAvgTravelTime; - } - - public double getDrtAdvanceBookMin() { - return drtAdvanceBookMin; - } - - public void setDrtAdvanceBookMin(double drtAdvanceBookMin) { - this.drtAdvanceBookMin = drtAdvanceBookMin; - } - - public String getDrtPickupMessage() { - return drtPickupMessage; - } - - public void setDrtPickupMessage(String drtPickupMessage) { - this.drtPickupMessage = drtPickupMessage; - } - - public String getDrtDropOffMessage() { - return drtDropOffMessage; - } - - public void setDrtDropOffMessage(String drtDropOffMessage) { - this.drtDropOffMessage = drtDropOffMessage; - } - - public String getContinuousPickupMessage() { - return continuousPickupMessage; - } - - public void setContinuousPickupMessage(String continuousPickupMessage) { - this.continuousPickupMessage = continuousPickupMessage; - } - - public String getContinuousDropOffMessage() { - return continuousDropOffMessage; - } - - public void setContinuousDropOffMessage(String continuousDropOffMessage) { - this.continuousDropOffMessage = continuousDropOffMessage; - } - public Double getMeanDurationFactor() { return meanDurationFactor; } @@ -333,16 +240,6 @@ public void setSafeDurationOffset(Double safeDurationOffset) { this.safeDurationOffset = safeDurationOffset; } - @Deprecated - public void setTripBikesAllowed(int tripBikesAllowed) { - this.tripBikesAllowed = tripBikesAllowed; - } - - @Deprecated - public int getTripBikesAllowed() { - return tripBikesAllowed; - } - /** * @return 0 = unknown / unspecified, 1 = bikes allowed, 2 = bikes NOT allowed */ @@ -358,16 +255,23 @@ public void setBikesAllowed(int bikesAllowed) { this.bikesAllowed = bikesAllowed; } - public String toString() { - return ""; + /** + * @return 0 = unknown / unspecified, 1 = cars allowed, 2 = cars NOT allowed + */ + public int getCarsAllowed() { + return carsAllowed; } - - public String getFareId() { - return fareId; + + /** + * @param carsAllowed 0 = unknown / unspecified, 1 = cars allowed, 2 = cars + * NOT allowed + */ + public void setCarsAllowed(int carsAllowed) { + this.carsAllowed = carsAllowed; } - - public void setFareId(String fareId) { - this.fareId = fareId; + + public String toString() { + return ""; } public Note getNote() { diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Vehicle.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Vehicle.java index 659366dfe..3a81c0d40 100644 --- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Vehicle.java +++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Vehicle.java @@ -18,13 +18,15 @@ import org.onebusaway.csv_entities.schema.annotations.CsvField; import org.onebusaway.csv_entities.schema.annotations.CsvFields; import org.onebusaway.gtfs.serialization.mappings.DefaultAgencyIdFieldMappingFactory; +import org.onebusaway.gtfs.serialization.mappings.EntityFieldMappingFactory; +import org.onebusaway.gtfs.serialization.mappings.StopLocationFieldMappingFactory; /** * GTFS Extension representing vehicle configuration data. */ @CsvFields(filename = "vehicles.txt", required = false) public final class Vehicle extends IdentityBean { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 2L; @CsvField(name = "vehicle_id", mapping = DefaultAgencyIdFieldMappingFactory.class) private AgencyAndId id; @@ -53,6 +55,9 @@ public final class Vehicle extends IdentityBean { @CsvField(name = "wheelchair_access", optional = true) private String wheelchairAccess; + @CsvField(name = "icon_id", optional = true, mapping = EntityFieldMappingFactory.class, order = -1) + private Icon icon; + @Override public AgencyAndId getId() { return id; @@ -86,4 +91,12 @@ public void setId(AgencyAndId id) { public String getWheelchairAccess() { return wheelchairAccess; } public void setWheelchairAccess(String wheelchairAccess) { this.wheelchairAccess = wheelchairAccess; } + + public Icon getIcon() { + return icon; + } + + public void setIcon(Icon icon) { + this.icon = icon; + } } diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/serialization/GtfsEntitySchemaFactory.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/serialization/GtfsEntitySchemaFactory.java index fa954a31e..11102b7e5 100644 --- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/serialization/GtfsEntitySchemaFactory.java +++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/serialization/GtfsEntitySchemaFactory.java @@ -47,6 +47,7 @@ public static List> getEntityClasses() { entityClasses.add(Level.class); entityClasses.add(Stop.class); entityClasses.add(StopAreaElement.class); + entityClasses.add(LocationGroup.class); entityClasses.add(LocationGroupElement.class); entityClasses.add(Trip.class); entityClasses.add(Note.class); @@ -76,6 +77,8 @@ public static List> getEntityClasses() { entityClasses.add(WrongWayConcurrency.class); entityClasses.add(DirectionEntry.class); entityClasses.add(AlternateStopNameException.class); + entityClasses.add(Icon.class); + entityClasses.add(Network.class); return entityClasses; } @@ -97,6 +100,7 @@ public static Map, Comparator> getEntityComparators() { comparators.put(ServiceCalendarDate.class, new ServiceCalendarDateComparator()); comparators.put(Vehicle.class, getComparatorForIdentityBeanType(Vehicle.class)); + comparators.put(Icon.class, getComparatorForIdentityBeanType(Icon.class)); return comparators; } diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/serialization/GtfsReader.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/serialization/GtfsReader.java index 5f31f7602..d863fc38d 100644 --- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/serialization/GtfsReader.java +++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/serialization/GtfsReader.java @@ -65,6 +65,7 @@ public GtfsReader() { _entityClasses.add(Agency.class); _entityClasses.add(Block.class); _entityClasses.add(ShapePoint.class); + _entityClasses.add(Icon.class); _entityClasses.add(Note.class); _entityClasses.add(Area.class); _entityClasses.add(BookingRule.class); @@ -74,6 +75,7 @@ public GtfsReader() { _entityClasses.add(Level.class); _entityClasses.add(Stop.class); _entityClasses.add(Location.class); + _entityClasses.add(LocationGroup.class); _entityClasses.add(LocationGroupElement.class); _entityClasses.add(Trip.class); _entityClasses.add(StopAreaElement.class); @@ -104,6 +106,7 @@ public GtfsReader() { _entityClasses.add(WrongWayConcurrency.class); _entityClasses.add(DirectionEntry.class); _entityClasses.add(AlternateStopNameException.class); + _entityClasses.add(Network.class); CsvTokenizerStrategy tokenizerStrategy = new CsvTokenizerStrategy(); tokenizerStrategy.getCsvParser().setTrimInitialWhitespace(true); @@ -322,74 +325,48 @@ public void handleEntity(Object entity) { return; _agencies.add((Agency) entity); - } else if (entity instanceof BookingRule) { - BookingRule bookingRule = (BookingRule) entity; + } else if (entity instanceof final BookingRule bookingRule) { registerAgencyId(BookingRule.class, bookingRule.getId()); - } else if (entity instanceof Pathway) { - Pathway pathway = (Pathway) entity; + } else if (entity instanceof final Pathway pathway) { registerAgencyId(Pathway.class, pathway.getId()); - } else if (entity instanceof Level) { - Level level = (Level) entity; + } else if (entity instanceof final Level level) { registerAgencyId(Level.class, level.getId()); - } else if (entity instanceof Route) { - Route route = (Route) entity; + } else if (entity instanceof final Route route) { registerAgencyId(Route.class, route.getId()); - } else if (entity instanceof Trip) { - Trip trip = (Trip) entity; + } else if (entity instanceof final Trip trip) { registerAgencyId(Trip.class, trip.getId()); - } else if (entity instanceof Stop) { - Stop stop = (Stop) entity; + } else if (entity instanceof final Stop stop) { registerAgencyId(Stop.class, stop.getId()); - } else if (entity instanceof FareProduct) { - FareProduct product = (FareProduct) entity; + } else if (entity instanceof final FareProduct product) { registerAgencyId(FareProduct.class, product.getId()); - } else if (entity instanceof FareMedium) { - FareMedium medium = (FareMedium) entity; + } else if (entity instanceof final FareMedium medium) { registerAgencyId(FareMedium.class, medium.getId()); - } else if (entity instanceof RiderCategory) { - RiderCategory category = (RiderCategory) entity; + } else if (entity instanceof final RiderCategory category) { registerAgencyId(RiderCategory.class, category.getId()); - } else if (entity instanceof FareAttribute) { - FareAttribute fare = (FareAttribute) entity; + } else if (entity instanceof final FareAttribute fare) { registerAgencyId(FareAttribute.class, fare.getId()); - } else if (entity instanceof Note) { - Note note = (Note) entity; + } else if (entity instanceof final Note note) { registerAgencyId(Note.class, note.getId()); - } else if (entity instanceof Area) { - Area area = (Area) entity; + } else if (entity instanceof final Area area) { registerAgencyId(Area.class, area.getId()); - - } else if (entity instanceof Location) { - Location location = (Location) entity; + } else if (entity instanceof final Location location) { registerAgencyId(Location.class, location.getId()); - } else if (entity instanceof LocationGroupElement) { - LocationGroupElement locationGroupElement = (LocationGroupElement) entity; - LocationGroup locationGroup = _entityStore.getEntityForId(LocationGroup.class, locationGroupElement.getLocationGroupId()); - if (locationGroup == null) { - locationGroup = new LocationGroup(); - locationGroup.setId(locationGroupElement.getLocationGroupId()); - locationGroup.setName(locationGroupElement.getName()); - _entityStore.saveEntity(locationGroup); - } - locationGroup.addLocation(locationGroupElement.getLocation()); - } else if (entity instanceof StopAreaElement) { - StopAreaElement stopAreaElement = (StopAreaElement) entity; - StopArea stopArea = _entityStore.getEntityForId(StopArea.class, stopAreaElement.getArea().getId()); - if (stopArea == null) { - stopArea = new StopArea(); - stopArea.setArea(stopAreaElement.getArea()); - _entityStore.saveEntity(stopArea); - } - stopArea.addLocation(stopAreaElement.getStopLocation()); - } else if (entity instanceof Vehicle) { - Vehicle vehicle = (Vehicle) entity; + } else if (entity instanceof final LocationGroup group) { + registerAgencyId(LocationGroup.class, group.getId()); + } else if (entity instanceof final LocationGroupElement locationGroupElement) { + var locationGroup = _entityStore.getEntityForId(LocationGroup.class, locationGroupElement.getLocationGroupId()); + locationGroup.addLocation(locationGroupElement.getStop()); + } else if (entity instanceof final StopAreaElement stopAreaElement) { + var area = _entityStore.getEntityForId(Area.class, stopAreaElement.getArea().getId()); + area.addStop(stopAreaElement.getStop()); + } else if (entity instanceof final Vehicle vehicle) { registerAgencyId(Vehicle.class, vehicle.getId()); - } else if (entity instanceof Facility){ - Facility facility = (Facility) entity; + } else if (entity instanceof final Facility facility){ registerAgencyId(Facility.class, facility.getId()); - } else if (entity instanceof FacilityPropertyDefinition){ - FacilityPropertyDefinition facilityPropertyDefinition = (FacilityPropertyDefinition) entity; + } else if (entity instanceof final FacilityPropertyDefinition facilityPropertyDefinition){ registerAgencyId(FacilityPropertyDefinition.class, facilityPropertyDefinition.getId()); + } else if (entity instanceof final Icon icon){ + registerAgencyId(Icon.class, icon.getId()); } if (entity instanceof IdentityBean) { @@ -403,7 +380,7 @@ private void registerAgencyId(Class entityType, AgencyAndId id) { Map agencyIdsByEntityId = _agencyIdsByEntityClassAndId.get(entityType); if (agencyIdsByEntityId == null) { - agencyIdsByEntityId = new HashMap(); + agencyIdsByEntityId = new HashMap<>(); _agencyIdsByEntityClassAndId.put(entityType, agencyIdsByEntityId); } diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/serialization/mappings/StopLocationFieldMappingImpl.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/serialization/mappings/StopLocationFieldMappingImpl.java index d53199b13..ca6a7a599 100644 --- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/serialization/mappings/StopLocationFieldMappingImpl.java +++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/serialization/mappings/StopLocationFieldMappingImpl.java @@ -45,7 +45,7 @@ public ConverterImpl(GtfsReaderContext context) { public Object convert(@SuppressWarnings("rawtypes") Class type, Object value) { if (type == String.class) { if (value instanceof String) - return (String) value; + return value; return null; } else if (type == StopLocation.class) { String entityId = value.toString(); @@ -57,13 +57,12 @@ public Object convert(@SuppressWarnings("rawtypes") Class type, Object value) { if (location != null) return location; Object locationGroup = _context.getEntity(LocationGroup.class, id); if (locationGroup != null) return locationGroup; - Object stopArea = _context.getEntity(StopArea.class, id); - if (stopArea != null) return stopArea; return null; } // we fell through -- unexpected situation - throw new ConversionException("Could not convert " + value + " of type " - + value.getClass() + " to " + type); + throw new ConversionException( + "Could not convert %s of type %s to %s".formatted( + value, value.getClass(), type)); } } } diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/services/GtfsDao.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/services/GtfsDao.java index f701b4ab5..b5045a68f 100644 --- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/services/GtfsDao.java +++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/services/GtfsDao.java @@ -184,6 +184,17 @@ public interface GtfsDao extends GenericDao { public Collection getAllRiderships(); + /**** + * {@link Vehicle} Methods + ****/ + Collection getAllVehicles(); + + /**** + * {@link Vehicle} Methods + ****/ + Vehicle getVehicleForId(AgencyAndId id); + + /**** * {@link Area} Methods ****/ @@ -197,8 +208,6 @@ public interface GtfsDao extends GenericDao { public Collection getAllStopAreaElements(); - public Collection getAllStopAreas(); - public Collection getAllLocations(); public Collection getAllBookingRules(); @@ -209,6 +218,12 @@ public interface GtfsDao extends GenericDao { public Collection getAllTranslations(); + /**** + * {@link Network} Methods + ****/ + + public Collection getAllNetworks(); + public Collection getAllDirectionEntries(); public Collection getAllWrongWayConcurrencies(); diff --git a/onebusaway-gtfs/src/site/site.xml b/onebusaway-gtfs/src/site/site.xml deleted file mode 100644 index 99a22b504..000000000 --- a/onebusaway-gtfs/src/site/site.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/ExtensionsTest.java b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/ExtensionsTest.java index 48f876420..a0d99da82 100644 --- a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/ExtensionsTest.java +++ b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/ExtensionsTest.java @@ -15,14 +15,14 @@ */ package org.onebusaway.gtfs; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.File; import java.io.IOException; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.csv_entities.schema.DefaultEntitySchemaFactory; import org.onebusaway.csv_entities.schema.annotations.CsvField; import org.onebusaway.gtfs.impl.GtfsRelationalDaoImpl; @@ -39,7 +39,7 @@ public class ExtensionsTest { private File _tmpDirectory; - @Before + @BeforeEach public void setup() throws IOException { _tmpDirectory = File.createTempFile("GtfsWriterTest-", "-tmp"); if (_tmpDirectory.exists()) @@ -47,7 +47,7 @@ public void setup() throws IOException { _tmpDirectory.mkdirs(); } - @After + @AfterEach public void teardown() { GtfsWriterTest.deleteFileRecursively(_tmpDirectory); } diff --git a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/GtfsTestData.java b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/GtfsTestData.java index 4e21dea45..a91b36d07 100644 --- a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/GtfsTestData.java +++ b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/GtfsTestData.java @@ -53,6 +53,8 @@ private static String gtfsPath(String name) { public static final String LOCATIONS_GEOJSON = gtfsPath("locations.geojson"); + public static final String TEST_AGENCY_VEHICLES_EXT_GTFS = gtfsPath("testagency-vehicles-ext"); + public static File getCaltrainGtfs() { return getResourceAsTemporaryFile(CALTRAIN_GTFS); } @@ -94,6 +96,10 @@ public static File getAuburnTransitFlex() { return new File("src/test/resources", AUBURN_TRANSIT_FLEX); } + public static File getTestAgencyVehiclesExt(){ + return new File("src/test/resources", TEST_AGENCY_VEHICLES_EXT_GTFS); + } + public static void readGtfs(T entityStore, File resourcePath, String defaultAgencyId) throws IOException { diff --git a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/GenericDaoImplTest.java b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/GenericDaoImplTest.java index 9f3b88060..8a9b345cc 100644 --- a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/GenericDaoImplTest.java +++ b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/GenericDaoImplTest.java @@ -15,13 +15,13 @@ */ package org.onebusaway.gtfs.impl; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; import java.util.Collection; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.model.AgencyAndId; import org.onebusaway.gtfs.model.Stop; diff --git a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/GtfsDaoImplTest.java b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/GtfsDaoImplTest.java index 8ffe17973..3176c83c5 100644 --- a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/GtfsDaoImplTest.java +++ b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/GtfsDaoImplTest.java @@ -15,7 +15,7 @@ */ package org.onebusaway.gtfs.impl; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import org.onebusaway.gtfs.GtfsTestData; import org.onebusaway.gtfs.model.Agency; @@ -33,7 +33,7 @@ import org.onebusaway.gtfs.model.Trip; import org.onebusaway.gtfs.model.calendar.ServiceDate; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.util.Collection; diff --git a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/GtfsRelationalDaoImplTest.java b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/GtfsRelationalDaoImplTest.java index c74c5dd78..1e0c9a850 100644 --- a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/GtfsRelationalDaoImplTest.java +++ b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/GtfsRelationalDaoImplTest.java @@ -16,13 +16,13 @@ */ package org.onebusaway.gtfs.impl; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.util.List; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.GtfsTestData; import org.onebusaway.gtfs.model.Agency; import org.onebusaway.gtfs.model.AgencyAndId; diff --git a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/calendar/CalendarServiceDataFactoryImplSyntheticTest.java b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/calendar/CalendarServiceDataFactoryImplSyntheticTest.java index d1c76b62a..b9f2dc2dc 100644 --- a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/calendar/CalendarServiceDataFactoryImplSyntheticTest.java +++ b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/calendar/CalendarServiceDataFactoryImplSyntheticTest.java @@ -17,10 +17,10 @@ */ package org.onebusaway.gtfs.impl.calendar; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; import java.util.Arrays; @@ -29,7 +29,7 @@ import java.util.Set; import java.util.TimeZone; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.DateSupport; import org.onebusaway.gtfs.impl.GtfsRelationalDaoImpl; import org.onebusaway.gtfs.model.Agency; diff --git a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/calendar/CalendarServiceDataFactoryImplTest.java b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/calendar/CalendarServiceDataFactoryImplTest.java index d75a08a66..83a61ac6d 100644 --- a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/calendar/CalendarServiceDataFactoryImplTest.java +++ b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/calendar/CalendarServiceDataFactoryImplTest.java @@ -15,8 +15,8 @@ */ package org.onebusaway.gtfs.impl.calendar; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.util.Date; @@ -24,7 +24,7 @@ import java.util.Set; import java.util.TimeZone; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.DateSupport; import org.onebusaway.gtfs.GtfsTestData; import org.onebusaway.gtfs.impl.GtfsRelationalDaoImpl; diff --git a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/calendar/CalendarServiceImplSyntheticTest.java b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/calendar/CalendarServiceImplSyntheticTest.java index cb8294a16..2547ef0a5 100644 --- a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/calendar/CalendarServiceImplSyntheticTest.java +++ b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/calendar/CalendarServiceImplSyntheticTest.java @@ -15,17 +15,17 @@ */ package org.onebusaway.gtfs.impl.calendar; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.onebusaway.gtfs.DateSupport.date; import static org.onebusaway.gtfs.DateSupport.hourToSec; import java.util.*; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.model.AgencyAndId; import org.onebusaway.gtfs.model.calendar.*; @@ -53,7 +53,7 @@ public class CalendarServiceImplSyntheticTest { private CalendarServiceImpl service; - @Before + @BeforeEach public void setup() { CalendarServiceData data = new CalendarServiceData(); diff --git a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/calendar/CalendarServiceImplTest.java b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/calendar/CalendarServiceImplTest.java index 581310463..f21ade2a6 100644 --- a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/calendar/CalendarServiceImplTest.java +++ b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/calendar/CalendarServiceImplTest.java @@ -15,8 +15,8 @@ */ package org.onebusaway.gtfs.impl.calendar; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.util.Calendar; @@ -24,7 +24,7 @@ import java.util.HashSet; import java.util.Set; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.GtfsTestData; import org.onebusaway.gtfs.impl.GtfsRelationalDaoImpl; import org.onebusaway.gtfs.model.AgencyAndId; diff --git a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/fares/FareAtrributeAgencyTest.java b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/fares/FareAtrributeAgencyTest.java index 543ed8498..1512983e8 100644 --- a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/fares/FareAtrributeAgencyTest.java +++ b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/fares/FareAtrributeAgencyTest.java @@ -15,7 +15,7 @@ */ package org.onebusaway.gtfs.impl.fares; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.GtfsTestData; import org.onebusaway.gtfs.impl.GtfsRelationalDaoImpl; import org.onebusaway.gtfs.model.FareAttribute; diff --git a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/translation/TranslationServiceImplTest.java b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/translation/TranslationServiceImplTest.java index 98d571571..0529b4bcf 100644 --- a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/translation/TranslationServiceImplTest.java +++ b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/impl/translation/TranslationServiceImplTest.java @@ -15,7 +15,7 @@ */ package org.onebusaway.gtfs.impl.translation; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.GtfsTestData; import org.onebusaway.gtfs.impl.GtfsRelationalDaoImpl; import org.onebusaway.gtfs.model.Agency; @@ -29,7 +29,7 @@ import java.io.IOException; import java.util.List; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; public class TranslationServiceImplTest { diff --git a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/model/AgencyAndIdTest.java b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/model/AgencyAndIdTest.java index 19d4440e8..6e1ae60b5 100644 --- a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/model/AgencyAndIdTest.java +++ b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/model/AgencyAndIdTest.java @@ -15,10 +15,10 @@ */ package org.onebusaway.gtfs.model; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class AgencyAndIdTest { diff --git a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/model/MissingValueTest.java b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/model/MissingValueTest.java index bbe6f6613..c36111091 100644 --- a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/model/MissingValueTest.java +++ b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/model/MissingValueTest.java @@ -19,15 +19,15 @@ import java.io.IOException; import java.util.Scanner; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.serialization.GtfsWriter; import org.onebusaway.gtfs.serialization.GtfsWriterTest; import org.onebusaway.gtfs.services.MockGtfs; import org.onebusaway.gtfs.services.GtfsRelationalDao; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Optional flex fields in stop times should not be present @@ -40,7 +40,7 @@ public class MissingValueTest { private File _tmpDirectory; - @Before + @BeforeEach public void before() throws IOException { _gtfs = MockGtfs.create(); @@ -88,9 +88,9 @@ public void test() throws IOException { foundShapeDist = true; } } - assertFalse("Empty Column not properly removed in line " + headerLine, foundEmptyColumn); - assertTrue("Column unexpectedly removed in line " + headerLine, foundProperArrivalTime); - assertFalse("Not expecting shapeDistTraveled in line " + headerLine, foundShapeDist); + assertFalse(foundEmptyColumn, "Empty Column not properly removed in line " + headerLine); + assertTrue(foundProperArrivalTime, "Column unexpectedly removed in line " + headerLine); + assertFalse(foundShapeDist, "Not expecting shapeDistTraveled in line " + headerLine); } @Test @@ -117,7 +117,7 @@ public void testStartingWithMissingValue() throws IOException { foundTimepoint = true; } } - assertFalse("Empty Column not properly removed", foundTimepoint); + assertFalse(foundTimepoint, "Empty Column not properly removed" ); } /** @@ -182,14 +182,14 @@ public void testStartingWithMissingDoubleValue() throws IOException { } } - assertFalse("Empty Column not properly removed", foundTimepoint); - assertFalse("Not expecting shapeDistTraveled in line " + headerLine, foundShapeDist); - assertFalse("Not expecting start service area radius in line " + headerLine, foundStartServiceArea); - assertFalse("Not expecting end service area radius in line " + headerLine, foundEndServiceArea); - assertFalse("Not expecting mean duration factor in line " + headerLine, foundMeanDurationFactor); - assertFalse("Not expecting mean duration offset in line " + headerLine, foundMeanDurationOffset); - assertFalse("Not expecting safe duration factor in line " + headerLine, foundSafeDurationFactor); - assertFalse("Not expecting safe duration offset in line " + headerLine, foundSafeDurationOffset); + assertFalse(foundTimepoint, "Empty Column not properly removed"); + assertFalse(foundShapeDist, "Not expecting shapeDistTraveled in line " + headerLine); + assertFalse(foundStartServiceArea, "Not expecting start service area radius in line " + headerLine ); + assertFalse(foundEndServiceArea, "Not expecting end service area radius in line " + headerLine ); + assertFalse(foundMeanDurationFactor, "Not expecting mean duration factor in line " + headerLine ); + assertFalse(foundMeanDurationOffset, "Not expecting mean duration offset in line " + headerLine ); + assertFalse(foundSafeDurationFactor, "Not expecting safe duration factor in line " + headerLine ); + assertFalse(foundSafeDurationOffset, "Not expecting safe duration offset in line " + headerLine); } @Test @@ -199,7 +199,7 @@ public void testPutMinimal() throws IOException { _gtfs.read(); } - @After + @AfterEach public void teardown() { deleteFileRecursively(_tmpDirectory); } diff --git a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/model/NetworkTest.java b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/model/NetworkTest.java new file mode 100644 index 000000000..552906552 --- /dev/null +++ b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/model/NetworkTest.java @@ -0,0 +1,117 @@ +/** + * Copyright (C) 2024 Sound Transit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.gtfs.model; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.onebusaway.gtfs.serialization.GtfsWriter; +import org.onebusaway.gtfs.serialization.GtfsWriterTest; +import org.onebusaway.gtfs.services.MockGtfs; +import org.onebusaway.gtfs.services.GtfsRelationalDao; + +import static org.junit.jupiter.api.Assertions.*; + +public class NetworkTest { + + private MockGtfs _gtfs; + + private File _tmpDirectory; + + @BeforeEach + public void before() throws IOException { + _gtfs = MockGtfs.create(); + + //make temp directory for gtfs writing output + _tmpDirectory = File.createTempFile("NetworkTest-", "-tmp"); + if (_tmpDirectory.exists()) + GtfsWriterTest.deleteFileRecursively(_tmpDirectory); + _tmpDirectory.mkdirs(); + } + + @Test + public void testBasicNetworks() throws IOException { + _gtfs.putMinimal(); + _gtfs.putDefaultTrips(); + _gtfs.putDefaultStops(); + _gtfs.putLines("networks.txt", + "network_id,network_name", + "rail_network,Rail Network", "bus_network,Bus Network"); + + GtfsRelationalDao dao = _gtfs.read(); + assertEquals(2, dao.getAllNetworks().size()); + + GtfsWriter writer = new GtfsWriter(); + writer.setOutputLocation(_tmpDirectory); + writer.run(dao); + + Scanner scan = new Scanner(new File(_tmpDirectory + "/networks.txt")); + Set expectedNetworkNames = new HashSet(); + Set foundNetworkNames = new HashSet(); + expectedNetworkNames.add("Rail Network"); + expectedNetworkNames.add("Bus Network"); + boolean onHeader = true; + while(scan.hasNext()){ + String line = scan.nextLine(); + if (onHeader) { + onHeader = false; + } else { + String[] lineParts = line.split(","); + + if (lineParts.length > 1) { + foundNetworkNames.add(lineParts[1]); + } + } + } + scan.close(); + + assertEquals(expectedNetworkNames, foundNetworkNames, "Did not find network names in output"); + } + + @Test + public void testPutMinimal() throws IOException { + _gtfs.putMinimal(); + // Just make sure it parses without throwing an error. + _gtfs.read(); + } + + @AfterEach + public void teardown() { + deleteFileRecursively(_tmpDirectory); + } + + public static void deleteFileRecursively(File file) { + + if (!file.exists()) + return; + + if (file.isDirectory()) { + File[] files = file.listFiles(); + if (files != null) { + for (File child : files) + deleteFileRecursively(child); + } + } + + file.delete(); + } + +} + diff --git a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/model/StopTimeWithUnderscoreTest.java b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/model/StopTimeWithUnderscoreTest.java index 9a5240a7e..785aa350e 100644 --- a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/model/StopTimeWithUnderscoreTest.java +++ b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/model/StopTimeWithUnderscoreTest.java @@ -19,15 +19,15 @@ import java.io.IOException; import java.util.*; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.serialization.GtfsWriter; import org.onebusaway.gtfs.serialization.GtfsWriterTest; import org.onebusaway.gtfs.services.MockGtfs; import org.onebusaway.gtfs.services.GtfsRelationalDao; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; public class StopTimeWithUnderscoreTest { @@ -35,7 +35,7 @@ public class StopTimeWithUnderscoreTest { private File _tmpDirectory; - @Before + @BeforeEach public void before() throws IOException { _gtfs = MockGtfs.create(); @@ -71,7 +71,7 @@ public void testWithUnderScore() throws IOException { } } // if the underscore version was input use it as output - assertTrue("Column without underscore was not found", foundUnderscoreParam); + assertTrue(foundUnderscoreParam, "Column without underscore was not found"); } @Test @@ -80,7 +80,7 @@ public void testWithoutUnderscore() throws IOException { _gtfs.putDefaultTrips(); _gtfs.putDefaultStops(); _gtfs.putLines("stop_times.txt", - "trip_id,stop_id,stop_sequence,arrival_time,departure_time,end_pickup_dropoff_window", + "trip_id,stop_id,stop_sequence,arrival_time,departure_time,end_pickup_drop_off_window", "T10-0,100,0,05:55:55,08:00:00,08:23:23", "T10-0,200,1,05:55:55,09:00:00,08:44:44"); GtfsRelationalDao dao = _gtfs.read(); @@ -94,12 +94,12 @@ public void testWithoutUnderscore() throws IOException { boolean foundUnderscoreParam = false; while(scan.hasNext()){ String line = scan.nextLine(); - if(line.contains("end_pickup_dropoff_window")){ + if(line.contains("end_pickup_drop_off_window")){ foundUnderscoreParam = true; } } // if the new spec was used as input ensure it is output - assertTrue("Column without underscore was not found", foundUnderscoreParam); + assertTrue(foundUnderscoreParam, "Column without underscore was not found"); } @Test @@ -109,7 +109,7 @@ public void testPutMinimal() throws IOException { _gtfs.read(); } - @After + @AfterEach public void teardown() { deleteFileRecursively(_tmpDirectory); } diff --git a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/model/calendar/LocalizedServiceIdTest.java b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/model/calendar/LocalizedServiceIdTest.java index d77e7f5a6..ee21d9937 100644 --- a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/model/calendar/LocalizedServiceIdTest.java +++ b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/model/calendar/LocalizedServiceIdTest.java @@ -15,12 +15,12 @@ */ package org.onebusaway.gtfs.model.calendar; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.TimeZone; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.model.AgencyAndId; public class LocalizedServiceIdTest { diff --git a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/model/calendar/ServiceDateTest.java b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/model/calendar/ServiceDateTest.java index b7ddd6afd..403db6ad5 100644 --- a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/model/calendar/ServiceDateTest.java +++ b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/model/calendar/ServiceDateTest.java @@ -16,9 +16,9 @@ */ package org.onebusaway.gtfs.model.calendar; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import static org.onebusaway.gtfs.DateSupport.date; import java.text.ParseException; @@ -26,8 +26,7 @@ import java.util.Date; import java.util.TimeZone; -import org.junit.Test; -import org.onebusaway.gtfs.model.calendar.ServiceDate; +import org.junit.jupiter.api.Test; public class ServiceDateTest { diff --git a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/model/calendar/ServiceIdIntervalsTest.java b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/model/calendar/ServiceIdIntervalsTest.java index 9c85644b8..87cffbcdf 100644 --- a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/model/calendar/ServiceIdIntervalsTest.java +++ b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/model/calendar/ServiceIdIntervalsTest.java @@ -15,11 +15,11 @@ */ package org.onebusaway.gtfs.model.calendar; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.TimeZone; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.model.AgencyAndId; public class ServiceIdIntervalsTest { diff --git a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/FaresV2ReaderTest.java b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/FaresV2ReaderTest.java index dfaf49d7b..04923bcc8 100644 --- a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/FaresV2ReaderTest.java +++ b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/FaresV2ReaderTest.java @@ -15,20 +15,16 @@ */ package org.onebusaway.gtfs.serialization; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.util.ArrayList; import java.util.Comparator; import java.util.List; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.onebusaway.csv_entities.exceptions.CsvEntityIOException; import org.onebusaway.gtfs.GtfsTestData; import org.onebusaway.gtfs.model.Agency; @@ -39,12 +35,15 @@ import org.onebusaway.gtfs.model.FareTransferRule; import org.onebusaway.gtfs.model.RiderCategory; import org.onebusaway.gtfs.model.Route; +import org.onebusaway.gtfs.model.Stop; import org.onebusaway.gtfs.model.StopAreaElement; import org.onebusaway.gtfs.services.GtfsRelationalDao; import org.onebusaway.gtfs.services.MockGtfs; public class FaresV2ReaderTest extends BaseGtfsTest { + private static final String AGENCY_ID = "1"; + @Test public void turlockFaresV2() throws CsvEntityIOException, IOException { String agencyId = "1642"; @@ -146,6 +145,30 @@ public void mdotMetroFaresV2() throws CsvEntityIOException, IOException { assertTrue(dao.hasFaresV2()); } + @Test + public void pierceTransitStopAreas() throws CsvEntityIOException, IOException { + var dao = processFeed(GtfsTestData.getPierceTransitFlex(), AGENCY_ID, false); + + var areaElements = List.copyOf(dao.getAllStopAreaElements()); + assertEquals(12, areaElements.size()); + + var first = areaElements.get(0); + assertEquals("1_4210813", first.getArea().getId().toString()); + var stop = first.getStop(); + assertEquals("4210806", stop.getId().getId()); + assertEquals("Bridgeport Way & San Francisco Ave SW (Northbound)", stop.getName()); + assertSame(Stop.class, stop.getClass()); + + var area = areaElements.get(0); + + assertSame(Stop.class, area.getStop().getClass()); + + var areas = List.copyOf(dao.getAllAreas()); + assertEquals(1, areas.size()); + + areas.forEach(stopArea -> assertFalse(stopArea.getStops().isEmpty())); + } + @Test public void testFaresV2Distance() throws IOException{ diff --git a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/FlexDropOffSpellingTest.java b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/FlexDropOffSpellingTest.java index d5bb63ad7..77cc8c455 100644 --- a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/FlexDropOffSpellingTest.java +++ b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/FlexDropOffSpellingTest.java @@ -15,13 +15,13 @@ */ package org.onebusaway.gtfs.serialization; -import static junit.framework.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.onebusaway.gtfs.serialization.GtfsReaderTest.processFeed; import java.io.IOException; import java.time.LocalTime; import java.util.ArrayList; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.model.StopTime; import org.onebusaway.gtfs.services.GtfsRelationalDao; import org.onebusaway.gtfs.services.MockGtfs; @@ -35,21 +35,12 @@ * * Since it's hard to spot: the change is in the word "dropoff" vs "drop_off". * - * This test makes sure that both spellings are understood. + * This test makes sure that the new spelling is understood. */ public class FlexDropOffSpellingTest { - @Test - public void oldSpelling() throws IOException { - testFlexStopTimeWithSpelling("dropoff"); - } - @Test public void newSpelling() throws IOException { - testFlexStopTimeWithSpelling("drop_off"); - } - - private static void testFlexStopTimeWithSpelling(String dropOffSpelling) throws IOException { MockGtfs gtfs = MockGtfs.create(); gtfs.putMinimal(); gtfs.putDefaultTrips(); @@ -57,7 +48,7 @@ private static void testFlexStopTimeWithSpelling(String dropOffSpelling) throws String rows = String.format( "trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_booking_rule_id,drop_off_booking_rule_id,start_pickup_%s_window,end_pickup_%s_window", - dropOffSpelling, dropOffSpelling + "drop_off", "drop_off" ); gtfs.putLines( @@ -75,4 +66,5 @@ private static void testFlexStopTimeWithSpelling(String dropOffSpelling) throws assertEquals(LocalTime.parse("10:00").toSecondOfDay(), stopTime.getStartPickupDropOffWindow()); assertEquals(LocalTime.parse("18:00").toSecondOfDay(), stopTime.getEndPickupDropOffWindow()); } + } diff --git a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/FlexReaderTest.java b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/FlexReaderTest.java index 896aaa1be..f794d0afe 100644 --- a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/FlexReaderTest.java +++ b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/FlexReaderTest.java @@ -15,72 +15,30 @@ */ package org.onebusaway.gtfs.serialization; -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertNotNull; -import static org.junit.Assert.assertSame; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; import java.io.IOException; import java.util.Comparator; import java.util.List; import java.util.Set; import java.util.stream.Collectors; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.onebusaway.csv_entities.exceptions.CsvEntityIOException; import org.onebusaway.gtfs.GtfsTestData; import org.onebusaway.gtfs.model.*; import org.onebusaway.gtfs.services.*; +//import org.onebusaway.gtfs.model.Location; +//import org.onebusaway.gtfs.model.LocationGroup; +//import org.onebusaway.gtfs.model.Stop; +//import org.onebusaway.gtfs.model.StopLocation; +//import org.onebusaway.gtfs.model.StopTime; public class FlexReaderTest extends BaseGtfsTest { private static final String AGENCY_ID = "1"; - @Test - public void pierceTransitStopAreas() throws CsvEntityIOException, IOException { - GtfsRelationalDao dao = processFeed(GtfsTestData.getPierceTransitFlex(), AGENCY_ID, false); - - List areaElements = dao.getAllStopAreaElements().stream().collect(Collectors.toList()); - assertEquals(15, areaElements.size()); - - StopAreaElement first = areaElements.get(0); - assertEquals("1_4210813", first.getArea().getId().toString()); - StopLocation stop = first.getStopLocation(); - assertEquals("4210806", stop.getId().getId()); - assertEquals("Bridgeport Way & San Francisco Ave SW (Northbound)", stop.getName()); - assertSame(Stop.class, stop.getClass()); - - StopAreaElement areaWithLocation = areaElements.stream().filter(a -> a.getId().toString().equals("1_4210800_area_1076")).findFirst().get(); - - StopLocation location = areaWithLocation.getStopLocation(); - assertSame(Location.class, location.getClass()); - - List stopAreas = dao.getAllStopAreas().stream().collect(Collectors.toList()); - assertEquals(2, stopAreas.size()); - - StopArea area = getArea(stopAreas, "1_4210813"); - assertEquals(12, area.getLocations().size()); - StopLocation stop2 = area.getLocations().stream().min(Comparator.comparing(StopLocation::getName)).get(); - assertEquals("Barnes Blvd & D St SW", stop2.getName()); - - StopArea area2 = getArea(stopAreas, "1_4210800"); - assertEquals(3, area2.getLocations().size()); - -// var names = area2.getLocations().stream().map(s -> s.getId().toString()).collect(Collectors.toSet()); -// -// assertEquals(Set.of("1_area_1075", "1_area_1074", "1_area_1076"), names); - - List trips = dao.getAllTrips().stream().collect(Collectors.toList());; - assertEquals(7, trips.size()); - - Trip trip = trips.stream().filter(t -> t.getId().getId().equals("t_5586096_b_80376_tn_0")).findFirst().get(); - List stopTimes = dao.getStopTimesForTrip(trip); - -// var classes = stopTimes.stream().map(st -> st.getStop().getClass()).collect(Collectors.toList()); -// assertEquals(List.of(StopArea.class, StopArea.class), classes); -// -// assertEquals("JBLM Stops", area.getName()); - - } - @Test public void locationIdAsASeparateColumn() throws CsvEntityIOException, IOException { GtfsRelationalDao dao = processFeed(GtfsTestData.getBrownCountyFlex(), AGENCY_ID, false); @@ -100,9 +58,15 @@ public void locationIdAsASeparateColumn() throws CsvEntityIOException, IOExcepti @Test public void locationGroupIdAsSeparateColumn() throws CsvEntityIOException, IOException { - GtfsRelationalDao dao = processFeed(GtfsTestData.getAuburnTransitFlex(), AGENCY_ID, false); - Trip trip = dao.getAllTrips().stream().filter(t -> t.getId().getId().equals("t_5756013_b_33000_tn_0")).findAny().get(); - List stopTimes = dao.getStopTimesForTrip(trip); + var dao = processFeed(GtfsTestData.getAuburnTransitFlex(), AGENCY_ID, false); + var locationGroup = List.copyOf(dao.getAllLocationGroups()).get(0); + assertEquals("Aurburn Loop Stops", locationGroup.getName()); + assertEquals("1_4230479", locationGroup.getId().toString()); + var actualStops = locationGroup.getLocations().stream().map(s -> s.getId().toString()).collect(Collectors.toList()); + assertEquals(30, actualStops.size()); + + var trip = dao.getAllTrips().stream().filter(t -> t.getId().getId().equals("t_5756013_b_33000_tn_0")).findAny().get(); + var stopTimes = dao.getStopTimesForTrip(trip); stopTimes.forEach(st -> assertNotNull(st.getStopLocation())); List stopLocations = stopTimes.stream().map(StopTime::getStopLocation).collect(Collectors.toList()); @@ -115,7 +79,4 @@ public void locationGroupIdAsSeparateColumn() throws CsvEntityIOException, IOExc assertEquals(LocationGroup.class, second.getClass()); } - private static StopArea getArea(List stopAreas, String id) { - return stopAreas.stream().filter(a -> a.getId().toString().equals(id)).findAny().get(); - } } diff --git a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/GtfsReaderDefaultAgencyIdTest.java b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/GtfsReaderDefaultAgencyIdTest.java index c9176bb32..4aca79d38 100644 --- a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/GtfsReaderDefaultAgencyIdTest.java +++ b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/GtfsReaderDefaultAgencyIdTest.java @@ -18,7 +18,7 @@ import java.io.IOException; import java.io.StringReader; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.onebusaway.csv_entities.exceptions.CsvEntityIOException; import org.onebusaway.gtfs.model.Agency; import org.onebusaway.gtfs.model.Route; diff --git a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/GtfsReaderStopsTest.java b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/GtfsReaderStopsTest.java index 348adfd57..88b95ed99 100644 --- a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/GtfsReaderStopsTest.java +++ b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/GtfsReaderStopsTest.java @@ -16,15 +16,15 @@ */ package org.onebusaway.gtfs.serialization; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; import java.util.Arrays; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import org.onebusaway.csv_entities.exceptions.CsvEntityIOException; import org.onebusaway.csv_entities.exceptions.MissingRequiredFieldException; import org.onebusaway.gtfs.model.Agency; @@ -35,7 +35,7 @@ public class GtfsReaderStopsTest { private MockGtfs _gtfs; - @Before + @BeforeEach public void setup() throws IOException { _gtfs = MockGtfs.create(); _gtfs.putDefaultAgencies(); @@ -50,7 +50,7 @@ public void setup() throws IOException { */ @Test - @Ignore + @Disabled public void testMissingStopLat() throws IOException { _gtfs.putLines("stops.txt", "stop_id,stop_name,stop_lat,stop_lon", "1,The Stop, ,-122.0"); @@ -64,7 +64,7 @@ public void testMissingStopLat() throws IOException { } @Test - @Ignore + @Disabled public void testMissingStopLon() throws IOException { _gtfs.putLines("stops.txt", "stop_id,stop_name,stop_lat,stop_lon", "1,The Stop,47.0,"); diff --git a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/GtfsReaderTest.java b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/GtfsReaderTest.java index ea3bd1ffe..39770b827 100644 --- a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/GtfsReaderTest.java +++ b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/GtfsReaderTest.java @@ -16,32 +16,29 @@ */ package org.onebusaway.gtfs.serialization; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import java.io.File; import java.io.IOException; import java.io.StringReader; -import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Comparator; import java.util.Iterator; import java.util.List; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.onebusaway.csv_entities.exceptions.CsvEntityIOException; import org.onebusaway.csv_entities.exceptions.InvalidValueEntityException; import org.onebusaway.csv_entities.exceptions.MissingRequiredFieldException; import org.onebusaway.gtfs.GtfsTestData; -import org.onebusaway.gtfs.impl.GtfsRelationalDaoImpl; import org.onebusaway.gtfs.model.*; import org.onebusaway.gtfs.model.calendar.ServiceDate; import org.onebusaway.gtfs.serialization.mappings.AgencyNotFoundForRouteException; @@ -78,8 +75,8 @@ public void testAllFields() throws IOException { gtfs.putLines( "trips.txt", "route_id,service_id,trip_id,trip_headsign,trip_short_name,direction_id,block_id,shape_id,route_short_name," - + "trip_bikes_allowed,bikes_allowed,wheelchair_accessible,peak_offpeak", - "R1,WEEK,T1,head-sign,short-name,1,B1,SHP1,10X,1,2,1,3"); + + "trip_bikes_allowed,bikes_allowed,wheelchair_accessible,peak_offpeak,cars_allowed", + "R1,WEEK,T1,head-sign,short-name,1,B1,SHP1,10X,1,2,1,3,1"); gtfs.putLines( "stop_times.txt", "trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type," @@ -198,7 +195,6 @@ public void testAllFields() throws IOException { assertEquals("route desc", route.getDesc()); assertEquals("FF0000", route.getColor()); assertEquals("0000FF", route.getTextColor()); - assertEquals(1, route.getRouteBikesAllowed()); assertEquals(2, route.getBikesAllowed()); assertEquals("http://agency.gov/route", route.getUrl()); assertEquals(100, route.getSortOrder()); @@ -212,9 +208,8 @@ public void testAllFields() throws IOException { assertEquals("1", trip.getDirectionId()); assertEquals("B1", trip.getBlockId()); assertEquals(new AgencyAndId("1", "SHP1"), trip.getShapeId()); - assertEquals("10X", trip.getRouteShortName()); - assertEquals(1, trip.getTripBikesAllowed()); assertEquals(2, trip.getBikesAllowed()); + assertEquals(1, trip.getCarsAllowed()); assertEquals(1, trip.getWheelchairAccessible()); assertEquals(3, trip.getPeakOffpeak()); diff --git a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/GtfsWriterTest.java b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/GtfsWriterTest.java index b8c47d6b5..24fe90c38 100644 --- a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/GtfsWriterTest.java +++ b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/GtfsWriterTest.java @@ -15,14 +15,14 @@ */ package org.onebusaway.gtfs.serialization; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.File; import java.io.IOException; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.impl.FileSupport; import org.onebusaway.gtfs.impl.GtfsRelationalDaoImpl; import org.onebusaway.gtfs.model.Agency; @@ -32,7 +32,7 @@ public class GtfsWriterTest { private FileSupport _support = new FileSupport(); private File _tmpDirectory; - @Before + @BeforeEach public void setup() throws IOException { _tmpDirectory = File.createTempFile("GtfsWriterTest-", "-tmp"); if (_tmpDirectory.exists()) @@ -41,7 +41,7 @@ public void setup() throws IOException { _support.markForDeletion(_tmpDirectory); } - @After + @AfterEach public void teardown() { _support.cleanup(); } diff --git a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/LocationsGeoJSONReaderTest.java b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/LocationsGeoJSONReaderTest.java index cab0e3f93..f2fbc858d 100644 --- a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/LocationsGeoJSONReaderTest.java +++ b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/LocationsGeoJSONReaderTest.java @@ -17,7 +17,7 @@ package org.onebusaway.gtfs.serialization; import org.geojson.LngLatAlt; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.GtfsTestData; import org.onebusaway.gtfs.model.Location; import org.geojson.Polygon; @@ -27,8 +27,8 @@ import java.io.InputStreamReader; import java.util.Collection; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; public class LocationsGeoJSONReaderTest { diff --git a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/VehiclesExtReaderTest.java b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/VehiclesExtReaderTest.java new file mode 100644 index 000000000..b5ce4fe90 --- /dev/null +++ b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/VehiclesExtReaderTest.java @@ -0,0 +1,56 @@ +/** + * Copyright (C) 2024 Cambridge Systematics, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onebusaway.gtfs.serialization; + +import org.junit.jupiter.api.Test; +import org.onebusaway.gtfs.GtfsTestData; +import org.onebusaway.gtfs.model.Agency; +import org.onebusaway.gtfs.model.AgencyAndId; +import org.onebusaway.gtfs.model.Vehicle; +import org.onebusaway.gtfs.services.GtfsRelationalDao; + +import java.io.IOException; +import java.util.Collection; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + + +public class VehiclesExtReaderTest extends BaseGtfsTest{ + + @Test + public void vehiclesTest() throws IOException { + String agencyId = "agency"; + GtfsRelationalDao dao = processFeed(GtfsTestData.getTestAgencyVehiclesExt(), agencyId, false); + Agency agency = dao.getAgencyForId(agencyId); + assertEquals(agencyId, agency.getId()); + assertEquals("Fake Agency", agency.getName()); + + // All Vehicles + Collection vehicles = dao.getAllVehicles(); + assertEquals(1, vehicles.size()); + + // Vehicle Lookup by ID + Vehicle vehicle = dao.getVehicleForId(new AgencyAndId("agency","123")); + assertNotNull(vehicle); + + // Icon Testing + assertEquals(new AgencyAndId("agency","ICO"),vehicle.getIcon().getId()); + assertEquals("test icon",vehicle.getIcon().getDescription()); + assertEquals("https://iconurl",vehicle.getIcon().getUrl()); + + } +} diff --git a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/mappings/LatLonFieldMappingFactoryTest.java b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/mappings/LatLonFieldMappingFactoryTest.java index 4ead77180..a15c72815 100644 --- a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/mappings/LatLonFieldMappingFactoryTest.java +++ b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/mappings/LatLonFieldMappingFactoryTest.java @@ -15,14 +15,14 @@ */ package org.onebusaway.gtfs.serialization.mappings; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.HashMap; import java.util.Locale; import java.util.Map; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.csv_entities.CsvEntityContextImpl; import org.onebusaway.csv_entities.schema.BeanWrapperFactory; import org.onebusaway.csv_entities.schema.DefaultEntitySchemaFactory; @@ -34,7 +34,7 @@ public class LatLonFieldMappingFactoryTest { private FieldMapping _fieldMapping; - @Before + @BeforeEach public void before() { _fieldMapping = buildFieldMapping(); } diff --git a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/mappings/ServiceDateFieldMappingFactoryTest.java b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/mappings/ServiceDateFieldMappingFactoryTest.java index 479408c30..b66a06a65 100644 --- a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/mappings/ServiceDateFieldMappingFactoryTest.java +++ b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/mappings/ServiceDateFieldMappingFactoryTest.java @@ -15,12 +15,12 @@ */ package org.onebusaway.gtfs.serialization.mappings; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.HashMap; import java.util.Map; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.onebusaway.csv_entities.CsvEntityContext; import org.onebusaway.csv_entities.CsvEntityContextImpl; import org.onebusaway.csv_entities.schema.BeanWrapper; diff --git a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/mappings/StopTimeFieldMappingFactoryTest.java b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/mappings/StopTimeFieldMappingFactoryTest.java index a839d1264..e68d4d881 100644 --- a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/mappings/StopTimeFieldMappingFactoryTest.java +++ b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/serialization/mappings/StopTimeFieldMappingFactoryTest.java @@ -16,15 +16,15 @@ */ package org.onebusaway.gtfs.serialization.mappings; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; import static org.onebusaway.gtfs.serialization.mappings.StopTimeFieldMappingFactory.getStringAsSeconds; import static org.onebusaway.gtfs.serialization.mappings.StopTimeFieldMappingFactory.getSecondsAsString; import java.util.HashMap; import java.util.Map; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.onebusaway.csv_entities.CsvEntityContext; import org.onebusaway.csv_entities.CsvEntityContextImpl; import org.onebusaway.csv_entities.schema.BeanWrapper; diff --git a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/services/MockGtfsTest.java b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/services/MockGtfsTest.java index 2099ac324..4477b883e 100644 --- a/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/services/MockGtfsTest.java +++ b/onebusaway-gtfs/src/test/java/org/onebusaway/gtfs/services/MockGtfsTest.java @@ -15,13 +15,13 @@ */ package org.onebusaway.gtfs.services; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; import java.util.List; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.onebusaway.gtfs.model.AgencyAndId; import org.onebusaway.gtfs.model.ServiceCalendarDate; import org.onebusaway.gtfs.model.calendar.ServiceDate; @@ -30,7 +30,7 @@ public class MockGtfsTest { private MockGtfs _gtfs; - @Before + @BeforeEach public void before() throws IOException { _gtfs = MockGtfs.create(); } diff --git a/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/auburn-transit-flex/location_group_stops.txt b/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/auburn-transit-flex/location_group_stops.txt new file mode 100644 index 000000000..a2c6ecce9 --- /dev/null +++ b/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/auburn-transit-flex/location_group_stops.txt @@ -0,0 +1,31 @@ +location_group_id,stop_id +4230479,2583236 +4230479,2583237 +4230479,2583238 +4230479,2583242 +4230479,2583244 +4230479,2583246 +4230479,2583249 +4230479,2583250 +4230479,2583251 +4230479,2583252 +4230479,2583253 +4230479,2583254 +4230479,2583255 +4230479,2583256 +4230479,2583259 +4230479,2583260 +4230479,2583262 +4230479,2583263 +4230479,2583266 +4230479,2583268 +4230479,2583271 +4230479,2583276 +4230479,2583280 +4230479,2583281 +4230479,2583282 +4230479,2583284 +4230479,2583285 +4230479,2751414 +4230479,3446932 +4230479,3446933 diff --git a/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/auburn-transit-flex/location_groups.txt b/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/auburn-transit-flex/location_groups.txt index 7fcf9b6d2..b9eba1b72 100644 --- a/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/auburn-transit-flex/location_groups.txt +++ b/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/auburn-transit-flex/location_groups.txt @@ -1,31 +1,2 @@ -location_group_id,stop_id,location_group_name -4230479,2583236,Aurburn Loop Stops -4230479,2583237,Aurburn Loop Stops -4230479,2583238,Aurburn Loop Stops -4230479,2583242,Aurburn Loop Stops -4230479,2583244,Aurburn Loop Stops -4230479,2583246,Aurburn Loop Stops -4230479,2583249,Aurburn Loop Stops -4230479,2583250,Aurburn Loop Stops -4230479,2583251,Aurburn Loop Stops -4230479,2583252,Aurburn Loop Stops -4230479,2583253,Aurburn Loop Stops -4230479,2583254,Aurburn Loop Stops -4230479,2583255,Aurburn Loop Stops -4230479,2583256,Aurburn Loop Stops -4230479,2583259,Aurburn Loop Stops -4230479,2583260,Aurburn Loop Stops -4230479,2583262,Aurburn Loop Stops -4230479,2583263,Aurburn Loop Stops -4230479,2583266,Aurburn Loop Stops -4230479,2583268,Aurburn Loop Stops -4230479,2583271,Aurburn Loop Stops -4230479,2583276,Aurburn Loop Stops -4230479,2583280,Aurburn Loop Stops -4230479,2583281,Aurburn Loop Stops -4230479,2583282,Aurburn Loop Stops -4230479,2583284,Aurburn Loop Stops -4230479,2583285,Aurburn Loop Stops -4230479,2751414,Aurburn Loop Stops -4230479,3446932,Aurburn Loop Stops -4230479,3446933,Aurburn Loop Stops +location_group_id,location_group_name +4230479,Aurburn Loop Stops \ No newline at end of file diff --git a/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/piercetransit-stop-areas-flex/areas.txt b/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/piercetransit-stop-areas-flex/areas.txt index 6ee648fda..bd8614846 100644 --- a/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/piercetransit-stop-areas-flex/areas.txt +++ b/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/piercetransit-stop-areas-flex/areas.txt @@ -1,3 +1,2 @@ area_id,area_name -4210800,Spanaway Core 4210813,JBLM Stops \ No newline at end of file diff --git a/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/piercetransit-stop-areas-flex/stop_areas.txt b/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/piercetransit-stop-areas-flex/stop_areas.txt index 6ed570846..06d91e6a4 100644 --- a/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/piercetransit-stop-areas-flex/stop_areas.txt +++ b/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/piercetransit-stop-areas-flex/stop_areas.txt @@ -1,7 +1,4 @@ area_id,stop_id -4210800,area_1074 -4210800,area_1075 -4210800,area_1076 4210813,4210804 4210813,4210805 4210813,4210806 diff --git a/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/agency.txt b/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/agency.txt new file mode 100644 index 000000000..31e2a82e4 --- /dev/null +++ b/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/agency.txt @@ -0,0 +1,2 @@ +agency_id,agency_name,agency_url,agency_timezone +agency,Fake Agency,http://fake.example.com,America/New_York diff --git a/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/calendar.txt b/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/calendar.txt new file mode 100644 index 000000000..6b351dffa --- /dev/null +++ b/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/calendar.txt @@ -0,0 +1,3 @@ +service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date +alldays,1,1,1,1,1,1,1,20090101,20500101 +weekdays,1,1,1,1,1,0,0,20090101,20500101 diff --git a/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/feed_info.txt b/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/feed_info.txt new file mode 100644 index 000000000..124bf78b1 --- /dev/null +++ b/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/feed_info.txt @@ -0,0 +1,2 @@ +feed_publisher_name,feed_publisher_url,feed_lang,default_lang +Fake Feed Publisher,http://fake.example.com,mul,en \ No newline at end of file diff --git a/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/icons.txt b/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/icons.txt new file mode 100644 index 000000000..979eb831b --- /dev/null +++ b/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/icons.txt @@ -0,0 +1,2 @@ +icon_id,icon_description,icon_url +ICO,test icon,https://iconurl \ No newline at end of file diff --git a/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/routes.txt b/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/routes.txt new file mode 100644 index 000000000..0a4dd22de --- /dev/null +++ b/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/routes.txt @@ -0,0 +1,8 @@ +route_id,route_short_name,route_long_name,route_type +1,1,1,3 +2,2,2,3 +3,3,3,3 +4,4,4,4 +5,5,5,5 +6,6,6,6 +7,7,7,7 diff --git a/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/shapes.txt b/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/shapes.txt new file mode 100644 index 000000000..5e0159c3f --- /dev/null +++ b/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/shapes.txt @@ -0,0 +1,10 @@ +shape_id,shape_pt_lat,shape_pt_lon,shape_pt_sequence,shape_dist_traveled +4,41.0,-75.0,1, +4,42.0,-75.0,2, +4,42.5,-75.3,3, +4,43.0,-75.0,4, +5,41.0,-72.0,1,0 +5,41.5,-72.5,2,1.234 +5,41.0,-73.0,3,17.62 +5,41.5,-73.5,4,35.234 +5,41.0,-74.0,5,52.01 diff --git a/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/stop_times.txt b/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/stop_times.txt new file mode 100644 index 000000000..733401298 --- /dev/null +++ b/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/stop_times.txt @@ -0,0 +1,44 @@ +trip_id,arrival_time,departure_time,stop_id,stop_sequence,shape_dist_traveled,pickup_type,drop_off_type,stop_headsign +1.1,00:00:00,00:00:00,A,1,,,, +1.1,00:10:00,00:10:00,B,2,,,, +1.1,00:20:00,00:20:00,C,3,,,, +1.2,00:20:00,00:20:00,A,1,0,,, +1.2,,,B,2,,3,2, +1.2,00:40:00,00:40:00,C,3,52.1,,, +1.3,08:00:00,08:00:00,A,1,,,, +1.3,08:10:00,08:20:00,B,2,,,, +1.3,08:30:00,08:30:00,C,3,,,, +2.1,00:20:00,00:20:00,B,1,,,, +2.1,00:30:00,00:30:00,C,2,,,, +2.1,00:40:00,00:40:00,D,3,,,, +2.2,00:50:00,00:50:00,B,1,,,, +2.2,01:00:00,01:00:00,C,2,,,, +2.2,01:10:00,01:10:00,D,3,,,, +3.1,00:40:00,00:40:00,B,1,,,, +3.1,00:50:00,00:50:00,C,2,,,, +3.1,01:00:00,01:00:00,D,3,,,, +3.1,01:10:00,01:10:00,E,4,,,, +3.2,01:00:00,01:00:00,B,1,,,, +3.2,01:10:00,01:10:00,C,2,,,, +3.2,01:20:00,01:20:00,D,3,,,, +3.2,01:30:00,01:30:00,E,4,,,, +4.1,05:00:00,05:00:00,F,1,,,, +4.1,05:30:00,05:30:00,G,2,,,, +4.1,06:00:00,06:00:00,H,3,,,, +4.2,23:00:00,23:00:00,F,1,,,, +4.2,23:30:00,23:30:00,G,2,,,, +4.2,24:00:00,24:00:00,H,3,,,, +4.3,23:40:00,23:40:00,F,1,,,,to G +4.3,24:10:00,24:10:00,G,2,,,,to H +4.3,24:40:00,24:40:00,H,3,,,, +5.1,08:00:00,08:00:00,I,1,0,,, +5.1,08:10:00,08:10:00,J,2,22.5,,, +5.1,08:20:00,08:20:00,K,3,52.01,,, +6.1,12:00:00,12:00:00,I,1,,,, +6.1,12:10:00,12:10:00,J,2,,,, +6.2,13:00:00,13:00:00,I,1,,,, +6.2,13:10:00,13:10:00,J,2,,,, +7.1,12:20:00,12:20:00,J,1,,,, +7.1,12:30:00,12:30:00,K,2,,,, +7.2,13:20:00,13:20:00,J,1,,,, +7.2,13:30:00,13:30:00,K,2,,,, diff --git a/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/stops.txt b/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/stops.txt new file mode 100644 index 000000000..37f31d1a3 --- /dev/null +++ b/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/stops.txt @@ -0,0 +1,14 @@ +stop_id,stop_name,stop_lat,stop_lon,wheelchair_boarding +A,A,40,-73,1 +B,B,40,-74,1 +C,C,40,-75,0 +D,D,40,-76,1 +E,E,40,-77,1 +F,F,41,-75, +G,G,42,-75, +H,H,43,-75, +I,I,41,-72, +J,J,41,-73, +K,K,41,-74, +L,L,41.000001,-73.000001, +M,M,41.000002,-73.000002, diff --git a/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/transfers.txt b/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/transfers.txt new file mode 100644 index 000000000..f085afa3a --- /dev/null +++ b/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/transfers.txt @@ -0,0 +1,5 @@ +to_stop_id,from_stop_id,transfer_type,min_transfer_time +K,L,0, +L,K,0, +M,K,3, +K,M,3, diff --git a/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/translations.txt b/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/translations.txt new file mode 100644 index 000000000..b9ab78a70 --- /dev/null +++ b/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/translations.txt @@ -0,0 +1,21 @@ +table_name,field_name,language,translation,record_id,record_sub_id,field_value +agency,agency_name,es,Fake Agency Spanish,agency,, +agency,agency_name,fr,Fake Agency French,,,Fake Agency +stops,stop_name,es,A Spanish,A,, +stops,stop_name,fr,A French,,,A +routes,route_long_name,es,3 Spanish,3,, +routes,route_long_name,fr,3 French,,,3 +trips,trip_headsign,es,headsign Spanish,3.1,, +trips,trip_headsign,fr,headsign French,,,headsign +stop_times,stop_headsign,es,to G Spanish,4.3,1, +stop_times,stop_headsign,es,to H Spanish,4.3,2, +stop_times,stop_headsign,fr,to G French,,,to G +stop_times,stop_headsign,fr,to H French,,,to H +feed_info,feed_publisher_name,es,Fake Feed Publisher Spanish,,, +feed_info,feed_publisher_url,es,http://fake.example.es,,, +feed_info,feed_publisher_name,fr,Fake Feed Publisher French,,, +feed_info,feed_publisher_url,fr,http://fake.example.fr,,, +wrong_table_name,agency_name,es,Fake Agency Spanish,agency,, +agency,wrong_column_name,es,Fake Agency Spanish,agency,, +agency,agency_name,es,No record id,,, + diff --git a/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/trips.txt b/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/trips.txt new file mode 100644 index 000000000..239380c87 --- /dev/null +++ b/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/trips.txt @@ -0,0 +1,16 @@ +route_id,service_id,trip_id,shape_id,block_id,wheelchair_accessible,trip_headsign +1,alldays,1.1,,,1,headsign +1,alldays,1.2,,,1,headsign +1,alldays,1.3,,,1,headsign +2,alldays,2.1,,,0,headsign +2,alldays,2.2,,,0,headsign +3,alldays,3.1,,,1,headsign +3,alldays,3.2,,,1,headsign +4,weekdays,4.1,4,,,headsign +4,weekdays,4.2,4,,,headsign +4,weekdays,4.3,4,,,headsign +5,alldays,5.1,5,,,headsign +6,alldays,6.1,,block.1,,headsign +7,alldays,7.1,,block.1,,headsign +6,alldays,6.2,,block.2,,headsign +7,alldays,7.2,,block.2,,headsign diff --git a/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/vehicles.txt b/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/vehicles.txt new file mode 100644 index 000000000..a06af272a --- /dev/null +++ b/onebusaway-gtfs/src/test/resources/org/onebusaway/gtfs/testagency-vehicles-ext/vehicles.txt @@ -0,0 +1,2 @@ +vehicle_id,icon_id +123,ICO \ No newline at end of file diff --git a/pom.xml b/pom.xml index a9183dbf7..7a0e9f7f9 100644 --- a/pom.xml +++ b/pom.xml @@ -1,24 +1,37 @@ 4.0.0 - - org.onebusaway - onebusaway - 1.2.9 - + org.onebusaway onebusaway-gtfs-modules - 1.4.15-openmove-5 + 5.0.1-openmove-1 pom onebusaway-gtfs-modules A collection of GTFS libraries and tools. https://github.com/OneBusAway/onebusaway-gtfs-modules/wiki/ + + + Apache License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0.html + + + + + + Sheldon Brown + + + Leonard Ehrenfried + + + - 1.1.7 - 1.2.8 - 2.0.6 - 0.0.13 + UTF-8 + UTF-8 + 2.0.16 + 0.0.13 + 5.11.4 @@ -60,7 +73,7 @@ scm:git:https://github.com/openmove/onebusaway-gtfs-modules.git scm:git:ssh://git@github.com/openmove/onebusaway-gtfs-modules.git https://github.com/openmove/onebusaway-gtfs-modules - onebusaway-gtfs-modules-1.4.15 + HEAD @@ -80,12 +93,13 @@ + onebusaway-csv-entities + onebusaway-collections onebusaway-gtfs onebusaway-gtfs-hibernate onebusaway-gtfs-hibernate-cli onebusaway-gtfs-transformer onebusaway-gtfs-transformer-cli - onebusaway-gtfs-transformer-cli-aws onebusaway-gtfs-merge onebusaway-gtfs-merge-cli @@ -95,91 +109,86 @@ org.onebusaway onebusaway-csv-entities - ${onebusaway_csv_entities_version} - - - slf4j-api - org.slf4j - - + ${project.version} - org.onebusaway - onebusaway-collections - ${onebusaway_collections_version} + org.slf4j + slf4j + ${slf4j.version} + + + org.slf4j + slf4j-api + ${slf4j.version} - - org.slf4j - slf4j - ${slf4j_version} - - - org.slf4j - slf4j-api - ${slf4j_version} - org.slf4j slf4j-simple - ${slf4j_version} + ${slf4j.version} - junit - junit - 4.4 + org.junit.jupiter + junit-jupiter-api + ${junit.version} test org.mockito mockito-core - 1.8.0 + 5.14.2 test javax.xml.bind jaxb-api - 2.3.0 + 2.3.1 com.sun.xml.bind jaxb-core - 2.3.0 + 4.0.5 com.sun.xml.bind jaxb-impl - 2.3.0 + 4.0.5 - + org.apache.maven.plugins - maven-project-info-reports-plugin - 3.4.2 + maven-compiler-plugin + 3.13.0 + + 17 + 17 + + + + org.apache.maven.plugins + maven-source-plugin + 3.3.1 + + + attach-sources + + jar-no-fork + + + - - - - - - org.apache.maven.plugins maven-javadoc-plugin - 3.4.1 + 3.11.2 - - -Xdoclint:none - 8 + none false - attach-javadocs @@ -190,34 +199,77 @@ org.apache.maven.plugins - maven-compiler-plugin - 3.10.1 + maven-release-plugin + 3.1.1 - 1.8 - 1.8 + true + v@{project.version} org.apache.maven.plugins - maven-site-plugin - 3.12.1 - - - org.apache.maven.plugins - maven-jar-plugin - 3.0.1 + maven-surefire-plugin + 3.5.2 + + + me.fabriciorby + maven-surefire-junit5-tree-reporter + 1.4.0 + + + + plain + + true + + + UNICODE + + - com.mycila - license-maven-plugin + com.google.cloud.tools + jib-maven-plugin + 3.4.4 -
LICENSE.txt
- - **/ci.yml - **/simplelogger.properties - + + + + amd64 + linux + + + arm64 + linux + + + + + ${env.CONTAINER_REGISTRY_NAMESPACE}/${project.artifactId}:${project.version} + + ${env.CONTAINER_REGISTRY_USER} + ${env.CONTAINER_REGISTRY_PASSWORD} + +
+ + + + + org.apache.maven.plugins + maven-shade-plugin + 3.6.0 + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.3 + + +
+ +
diff --git a/renovate.json5 b/renovate.json5 new file mode 100644 index 000000000..129499fbb --- /dev/null +++ b/renovate.json5 @@ -0,0 +1,57 @@ +{ + $schema: 'https://docs.renovatebot.com/renovate-schema.json', + extends: [ + 'config:recommended', + ], + prConcurrentLimit: 3, + rebaseWhen: 'conflicted', + labels: [ + 'dependencies', + ], + packageRules: [ + { + description: 'Automerge test dependencies in a single PR', + groupName: 'Test dependencies', + matchPackageNames: [ + 'org.mockito:mockito-core', + 'com.tngtech.archunit:archunit', + 'org.apache.maven.plugins:maven-surefire-plugin', + 'me.fabriciorby:maven-surefire-junit5-tree-reporter', + 'com.google.truth:truth', + 'org.jacoco:jacoco-maven-plugin', + 'org.apache.commons:commons-compress', + 'org.junit.jupiter:{/,}**', + ], + automerge: true, + schedule: 'on the 17th day of the month', + }, + { + description: 'Automerge Maven plugins in a single PR', + groupName: 'Maven plugins', + schedule: 'on the 23rd day of the month', + automerge: true, + matchPackageNames: [ + 'org.apache.maven.plugins:{/,}**', + ], + }, + { + description: 'Automerge logging dependencies in a single PR', + groupName: 'logging dependencies', + automerge: true, + schedule: 'on the 4th day of the month', + matchPackageNames: [ + 'org.slf4j:{/,}**', + 'ch.qos.logback:{/,}**', + ], + }, + { + description: 'Automerge dependencies', + matchPackageNames: [ + 'io.github.classgraph:classgraph', + ], + automerge: true, + schedule: 'monthly', + }, + ], + timezone: 'Europe/Berlin', +} diff --git a/src/site/apt/onebusaway-gtfs-merge-cli.apt.vm b/src/site/apt/onebusaway-gtfs-merge-cli.apt.vm deleted file mode 100644 index d6ebbef5a..000000000 --- a/src/site/apt/onebusaway-gtfs-merge-cli.apt.vm +++ /dev/null @@ -1,133 +0,0 @@ - ------ -GTFS Merge Command-Line Application - ------ - ------ - ------ - -Introduction - - <>: This tool is a work in progress! This documentation may not be up-to-date. - - The <<>> command-line application is a simple command-line tool for merging -{{{https://developers.google.com/transit/gtfs}GTFS}} feeds. - -Getting the Application - - You can download the application here: - -#if( $currentVersion.endsWith("-SNAPSHOT")) - #set( $repository = 'snapshots' ) -#else - #set( $repository = 'releases' ) -#end - -#set( $url = 'https://repo.camsys-apps.com/' + $repository + '/org/onebusaway/onebusaway-gtfs-merge-cli/' + ${currentVersion} + '/onebusaway-gtfs-merge-cli-' + ${currentVersion} + '.jar' ) - - {{{${url}}onebusaway-gtfs-merge-cli-${currentVersion}.jar}} - -Using the Application - - You'll need a Java 11 runtime installed to run the client. To run the application: - -+---+ -java -jar onebusaway-gtfs-merge-cli.jar [--args] input_gtfs_path_a input_gtfs_path_b ... output_gtfs_path -+---+ - - <>: Merging large GTFS feeds is often processor and memory intensive. You'll likely need to increase the -max amount of memory allocated to Java with an option like <<<-Xmx1G>>> (adjust the limit as needed). I also recommend -adding the <<<-server>>> argument if you are running the Oracle or OpenJDK, as it can really increase performance. - -Configuring the Application - - The merge application supports a number of options and arguments for configuring the application's behavior. The -general pattern is to specify options for each type of file in a GTFS feed using the <<<--file>>> option, specifying -specific options for each file type after the <<<--file>>> option. Here's a quick example: - -+---+ ---file=routes.txt --duplicateDetection=identity --file=calendar.txt --logDroppedDuplicates ... -+---+ - - The merge application supports merging the following files: - - * <<>> - - * <<>> - - * <<>> - - * <<>> and <<>> - - * <<>> and <<>> - - * <<>> - - * <<>> - - * <<>> - - * <<>> - - * <<>> - - [] - - You can specify merge options for each of these files using the <<<--file=gtfs_file.txt>>> option. File types listed -together (eg. <<> and <<>>) are handled by the same merge strategy, so specifying options for -either will have the same effect. For details on options you might specify, read on. - -Handling Duplicates - - The main issue to considering when merging GTFS feeds is the handling of duplicate entries between the two feeds, -including how to identify duplicates and what to do with duplicates when they are found. - -* Identifying Duplicates - - We support a couple of methods for determining when entries from two different feeds are actually duplicates. By default, -the merge tool will attempt to automatically determine the best merge strategy to use. You can also control the specific -strategy used on a per-file basis using the <<<--duplicateDetection>>> argument. You can specify any of the following -strategies for duplicate detection. - - * <<<--duplicateDetection=identity>>> - If two entries have the same id (eg. stop id, route id, trip id), then they are - considered the same. This is the more strict matching policy. - - * <<<--duplicateDetection=fuzzy>>> - If two entries have common elements (eg. stop name or location, route short name, - trip stop sequence), then they are considered the same. This is the more lenient matching policy, and is highly - dependent on the type of GTFS entry being matched. - - * <<<--duplicateDetection=none>>> - Entries between two feeds are never considered to be duplicates, even if they have - the same id or similar properties. - -* Logging Duplicates - - Sometimes your feed might have unexpected duplicates. You can tell the merge tool to log duplicates it finds or even -immediately exit with the following arguments: - - * <<<--logDroppedDuplicates>>> - log a message when a duplicate is found - - * <<<--errorOnDroppedDuplicates>>> - throw an exception when a duplicate is found, stopping the program - -Examples - -* Handling a Service Change - - Agencies often schedule major changes to their system around a particular date, with one GTFS feed for before the -service change and a different GTFS feed for after. We'd like to be able to merge these disjoint feeds into one -feed with continuous coverage. - - In our example, an agency produces two feeds where the entries in <<>> and <<>> are exactly -the same, so the default policy of identifying and dropping duplicates will work fine there. The <<>> file -is a bit trickier, since the route ids are different between the two feeds but the entries are largely the same. We -will use fuzzy duplicate detection to match the routes between the two feeds. - - The next issue is the <<>> file. The agency uses the same <<>> values in both feeds -(eg. <<>>, <<>>, <<>>) with different start and end dates in the two feeds. If the default policy of -dropping duplicate entries was used, we'd lose the dates in one of the service periods. Instead, we rename duplicates -such that the service ids from the second feed will be renamed to <<>>, <<>>, etc. and all -<<>> entries in the second feed will be updated appropriately. The result is that trips from the first -and second feed will both have the proper calendar entries in the merged feed. - - Putting it all together, here is what the command-line options for the application would look like: - -+---+ ---file=routes.txt --fuzzyDuplicates --file=calendar.txt --renameDuplicates -+---+ \ No newline at end of file diff --git a/src/site/apt/onebusaway-gtfs-transformer-cli.apt.vm b/src/site/apt/onebusaway-gtfs-transformer-cli.apt.vm deleted file mode 100644 index 10d283c4a..000000000 --- a/src/site/apt/onebusaway-gtfs-transformer-cli.apt.vm +++ /dev/null @@ -1,494 +0,0 @@ - ------ -GTFS Transformation Command-Line Application - ------ -Brian Ferris - ------ -2011-08-17 - ------ - -Introduction - - The <<>> command-line application is a simple command-line tool for transforming -{{{https://developers.google.com/transit/gtfs}GTFS}} feeds. - -%{toc} - -Requirements - - * Java 11 or greater - -Getting the Application - - You can download the application here: - -#if( $currentVersion.endsWith("-SNAPSHOT")) - #set( $repository = 'snapshots' ) -#else - #set( $repository = 'releases' ) -#end - -#set( $url = 'https://repo.camsys-apps.com/' + $repository + '/org/onebusaway/onebusaway-gtfs-transformer-cli/' + ${currentVersion} + '/onebusaway-gtfs-transformer-cli-' + ${currentVersion} + '.jar' ) - - {{{${url}}onebusaway-gtfs-transformer-cli-${currentVersion}.jar}} - -Using the Application - - To run the application: - -+---+ -java -jar onebusaway-gtfs-transformer-cli.jar [-args] input_gtfs_path ... output_gtfs_path -+---+ - -<<>> and <<>> can be either a directory containing a GTFS feed or a .zip file. - - <>: Transforming large GTFS feeds is processor and memory intensive. You'll likely need to increase the -max amount of memory allocated to Java with an option like <<<-Xmx1G>>> or greater. Adding the <<<-server>>> argument -if you are running the Oracle or OpenJDK can also increase performance. - -* Arguments - - * <<<--transform=...>>> : specify a transformation to apply to the input GTFS feed (see syntax below) - - * <<<--agencyId=id>>> : specify a default agency id for the input GTFS feed - - * <<<--overwriteDuplicates>>> : specify that duplicate GTFS entities should overwrite each other when read - - [] - -* Transform Syntax - - Transforms are specified as snippets of example. A simple example to remove a stop might look like: - -+---+ -{"op":"remove","match":{"file":"stops.txt","stop_name":"Stop NameP"}} -+---+ - - You can pass those snippets to the application in a couple of ways. The simplest is directly on the command line. - -+---+ ---transform='{...}' -+---+ - - You can have multiple <<<--transform>>> arguments to specify multiple transformations. However, if you have a LOT of -transformations that you wish to apply, it can be easier to put them in a file, with a JSON snippet per line. Then -specify the file on the command-line: - -+---+ - --transform=path/to/local-file -+---+ - - You can even specify a URL where the transformations will be read: - -+---+ ---transform=http://server/path -+--+ - -Matching - - We provide a number of configurable transformations out-of-the-box that can do simple operations like adding, -updating, retaining, and removing GTFS entities. Many of the transforms accept a "`match`" term that controls how the -rule matches against entities: - -+---+ -{"op":..., "match":{"file":"routes.txt", "route_short_name":"574"}} -+---+ - - Here, the match snippet at minimum requires a `file` property that specifies the type of GTFS entity to match. -Any file name defined in the {{{https://developers.google.com/transit/gtfs/reference#FeedFiles}GTFS specification}} -can be used. - - You can specify additional properties and values to match against as needed. Again, use the field names defined for -each file name in the GTFS specification. For example, the snippet above will match any entry in `routes.txt` with a -`route_short_name` value of `574`. - -* Regular Expressions - - Property matching also supports regular expressions that allow you to match property values conforming to a regexp pattern. For example, the snippet below will match any entry in `stops.txt` with a `stop_id` starting with `de:08`. - -+---+ -{"op":..., "match":{"file":"stops.txt", "stop_id":"m/^de:08.*/"}} -+---+ - -* Compound Property Expressions - - Property matching also supports compound property expressions that allow you to match across GTFS relational -references. Let's look at a simple example: - -+---+ -{"op":..., "match":{"file":"trips.txt", "route.route_short_name":"10"}} -+---+ - - Here the special `routes` property references the route entry associated with each trip, allowing you to match -the properties of the route. You can even chain references, like `route.agency` to match against the agency -associated with the trip. Here is the full list of supported compound property references: - -+---+ -{"op":..., "match":{"file":"routes.txt", "agency.name":"Metro"}} -{"op":..., "match":{"file":"trips.txt", "route.route_short_name":"10"}} -{"op":..., "match":{"file":"stop_times.txt", "stop.stop_id":"153"}} -{"op":..., "match":{"file":"stop_times.txt", "trip.route.route_type":3}} -{"op":..., "match":{"file":"frequencies.txt", "trip.service_id":"WEEKDAY"}} -{"op":..., "match":{"file":"transfers.txt", "fromStop.stop_id":"173"}} -{"op":..., "match":{"file":"transfers.txt", "toStop.stop_id":"173"}} -{"op":..., "match":{"file":"fare_rules.txt", "fare.currencyType":"USD"}} -{"op":..., "match":{"file":"fare_rules.txt", "route.route_short_name":"10"}} -+---+ - -* Multi-Value Matches - - The compound property expressions shown above are all for 1-to-1 relations, but matching also supports a limited -form of multi-value matching for 1-to-N relations. Let's look at a simple example: - -+---+ -{"op":..., "match":{"file":"routes.txt", "any(trips.trip_headsign)":"Downtown"}} -+---+ - - Notice the addition of `any(...)` around the property name. Here we are using a special `trips` property that -expands to include all trips associated with each route. Now, if *any* trip belonging to the route has the specified -`trip_headsign` value, then the route matches. Here is the full list of supported multi-value property matches: - -+---+ -{"op":..., "match":{"file":"agency.txt", "any(routes.X)":"Y"}} -{"op":..., "match":{"file":"routes.txt", "any(trips.X)":"Y"}} -{"op":..., "match":{"file":"trips.txt", "any(stop_times.X)":"Y"}} -+---+ - -* Collection-Like Entities - - There are a number of GTFS entites that are more effectively collections identified by a common key. For example, -shape points in `shapes.txt` linked by a common `shape_id` value or `calendar.txt` and `calendar_dates.txt` entries -linked by a common `service_id` value. You can use a special `collection `match clause to match against the entire -collection. - -+---+ -{"op":..., "match":{"collection":"shape", "shape_id":"XYZ"}} -{"op":..., "match":{"collection":"calendar", "service_id":"XYZ"}} -+---+ - - You can use the calendar collection matches, for example, to retain a calendar, including all `calendar.txt`, -`calendar_dates.txt`, and `trip.txt` entries that reference the specified `service_id` value. This convenient -short-hand is easier than writing the equivalent expression using references to the three file types separately. - -Types of Transforms - -* Add an Entity - - Create and add a new entity to the feed. - -+---+ -{"op":"add","obj":{"file":"agency.txt", "agency_id":"ST", "agency_name":"Sound Transit", -"agency_url":"http://www.soundtransit.org", "agency_timezone":"America/Los_Angeles"}} -+---+ - -* Update an Entity - - You can update arbitrary fields of a GTFS entity. - -+---+ -{"op":"update", "match":{"file":"routes.txt", "route_short_name":"574"}, "update":{"agency_id":"ST"}} -+---+ - - Normally, update values are used as-is. However, we support a number of - special update operations: - -** Find/Replace - -+---+ -{"op":"update", "match":{"file":"trips.txt"}, "update":{"trip_short_name":"s/North/N/"}} -+---+ - - By using <<>> syntax in the update value, the update will perform - a find-replace operation on the specified property value. Consider the - following example: - -+---+ -{"op":"update", "match":{"file":"trips.txt"}, "update":{"trip_short_name":"s/North/N/"}} -+---+ - - Here, a trip with a headsign of <<>> will be updated to - <<>>. - -** Path Expressions - - By using <<>> syntax in the update value, the expression will be - treated as a compound Java bean properties path expression. This path - expression will be evaluated against the target entity to produce the update - value. Consider the following example: - -+---+ -{"op":"update", "match":{"file":"trips.txt"}, "update":{"trip_short_name":"path(route.longName)"}} -+---+ - - Here, the <<>> field is updated for each trip in the feed. - The value will be copied from the <<>> field of each trip's - associated route. - -* Retain an Entity - - We also provide a powerful mechanism for selecting just a sub-set of a feed. - You can apply retain operations to entities you wish to keep and all the supporting entities referenced - by the retained entity will be retained as well. Unreferenced entities will be pruned. - - In the following example, only route B15 will be retained, along with all the stops, trips, stop times, shapes, and agencies linked to directly by that route. - -+---+ -{"op":"retain", "match":{"file":"routes.txt", "route_short_name":"B15"}} -+---+ - - By default, we retain across {{{https://developers.google.com/transit/gtfs/reference#trips_block_id_field}block_id}} values -specified in trips.txt. That means if a particular trip is retained (perhaps because its parent route is retained), -and the trip specifies a block_id, then all the trips referencing that block_id will be retained as well, along with -their own routes, stop times, and shapes. This can potentially lead to unexpected results if you retain one route and -suddenly see other routes included because they are linked by block_id. - - You can disable this feature by specifying <<>> in the JSON transformer snippet. Here is an -example: - -+---+ -{"op":"retain","match":{"file":"routes.txt", "route_short_name":"B15"}, "retainBlocks":false} -+---+ - -* Remove an Entity - - You can remove a specific entity from a feed. - -+---+ -{"op":"remove", "match":{"file":"stops.txt", "stop_name":"Stop Name"}} -+---+ - - Note that removing an entity has a cascading effect. If you remove a trip, all the stop times that depend on that -trip will also be removed. If you remove a route, all the trips and stop times for that route will be removed. - -* Trim a Trip - - You can remove stop times from the beginning or end of a trip using the "trim_trip" operation. Example: - -+---+ -{"op":"trim_trip", "match":{"file":"trips.txt", "route_id":"R10"}, "from_stop_id":"138S"} -+---+ - - For any trip belonging to the specified route and passing through the specified stop, all stop times from the specified -stop onward will be removed from the trip. You can also remove stop times from the beginning of the trip as well: - -+---+ -{"op":"trim_trip", "match":{"file":"trips.txt", "route_id":"R10"}, "to_stop_id":"138S"} -+---+ - - Or both: - -+---+ -{"op":"trim_trip", "match":{"file":"trips.txt", "route_id":"R10"}, "to_stop_id":"125S", "from_stop_id":"138S"} -+---+ - -* Generate Stop Times - - You can generate stop time entries for a trip. Example: - -+---+ -{"op":"stop_times_factory", "trip_id":"TRIP01", "start_time":"06:00:00", "end_time":"06:20:00", "stop_ids":["S01", "S02", "S03"]} -+---+ - - A series of entries in `stop_times.txt` will be generated for the specified trip, traveling along the specified sequence of -stops. The departure time for the first stop will be set from the `start_time` field, the arrival time for the last stop will -be set from the `end_time` field, and the times for intermediate stops will be interpolated based on their distance along the -trip. - -* Extend Service Calendars - - Sometimes you need to extend the service dates in a GTFS feed, perhaps in order to temporarily extend an expired feed. Extending -the feed by hand can be a tedious task, especially when the feed uses a complex combination of `calendar.txt` and `calendar_dates.txt` -entries. Fortunately, the GTFS tranformer tool supports a `calendar_extension` operation that can help simplify the work. Example: - -+---+ -{"op":"calendar_extension", "end_date":"20130331"} -+---+ - - The operation requires just one argument by default: `end_date` to specify the new end-date for the feed. The operation does -its best to intelligently extend each service calendar, as identified by a `service_id` in `calendar.txt` or `calendar_dates.txt`. -There are a few wrinkles to be aware of, however. - - Extending a `calendar.txt` entry is usually just a matter of setting a new `end_date` value in the feed. Extending a service -calendar represented only through `calendar_dates.txt` entries is a bit more complex. For such a service calendar, we attempt to -determine which days of the week are typically active for the calendar and extend only those. For example, is the calendar is -always active on Saturday but has one or two Sunday entries, we will only add entries for Saturday when extending the calendar. - - Also note that we will not extend "inactive" service calendars. A service calendar is considered inactive if its last active -service date is already in the past. By default, any calendar that's been expired for more than two weeks is considered inactive. -This helps handle feeds that have merged two service periods in one feed. For example, one calendar active from June 1 - July 31 -and a second calendar active from August 1 to September 31. If it's the last week of September and you are extending the feed, -you typically only mean to extend the second service calendar. You can control this inactive calendar cutoff with an optional -argument: - -+---+ -{"op":"calendar_extension", "end_date":"20130331", "inactive_calendar_cutoff":"20121031"} -+---+ - - Calendars that have expired before the specified date will be considered inactive and won't be extended. - - <>: We don't make any effort to extend canceled service dates, as specified in `calendar_dates.txt` for holidays and -other special events. It's too tricky to automatically determine how they should be handled. You may need to still handle -those manually. - -* Deduplicate Calendar Entries - - Finds GTFS service_ids that have the exact same set of active days and consolidates each set of duplicated -ids to a single service_id entry. - -+---+ -{"op":"deduplicate_service_ids"} -+---+ - - -* Merge Trips and Simplify Calendar Entries - - Some agencies model their transit schedule favoring multiple entries in calendar_dates.txt as opposed to a more concise -entry in calendar.txt. A smaller number of agencies take this scheme even further, creating trips.txt entries for each -service date, even when the underlying trips are exactly the same. This can cause the size of the GTFS to grow dramatically -as trips and stop times are duplicated. - - We provide a simple transformer that can attempt to detect these duplicate trips, remove them, and simplify the underlying -calendar entries to match. To run it, apply the following transform: - -+---+ -{"op":"calendar_simplification"} -+---+ - - The transform takes additional optional arguments to control its behavior: - - * min_number_of_weeks_for_calendar_entry - how many weeks does a service id need to - span before it gets its own entry in calendar.txt (default=3) - - * day_of_the_week_inclusion_ratio - if a service id is being modeled with a - calendar.txt entry, how frequent does a day of the week need to before it's - modeled positively in calendar.txt with any negative exceptions noted in - calendar_dates.txt, vs making no entry for that day of the week in - calendar.txt and instead noting any positive exceptions in - calendar_dates.txt. This is useful for filtering out a calendar that is - always active on Sunday, but has one or two Mondays for a holiday. - Frequency is defined as how often the target day of the week occurs vs the - count for day of the week appearing MOST frequently for the service id - (default=0.5) - - * undo_google_transit_data_feed_merge_tool - set to true to indicate that merged trip ids, - as produced by the {{{http://code.google.com/p/googletransitdatafeed/wiki/Merge}GoogleTransitDataFeedMergeTool}}, - should be un-mangled where possible. Merged trip ids will often have the form - <<>>. We attempt to set the trip id back to <<>> - where appropriate. - - [] - -* Shift Negative Stop Times - - Some agencies have trips that they model as starting BEFORE midnight on a given service date. For these agencies, it -would be convenient to represent these trips with negative arrival and departure times in stop_times.txt. The GTFS spec and -many GTFS consumers do not support negative stop times, however. - - To help these agencies, we provide a transform to "fix" GTFS feeds with negative stop times by identifying such trips, -shifting the arrival and departure times to make them positive, and updating the service calendar entries for these trips -such that the resulting schedule is semantically the same. - - To run it, apply the following transform: - -+---+ -{"op":"shift_negative_stop_times"} -+---+ - - <> When writing negative stop times, the negative value ONLY applies to the hour portion - of the time. Here are a few examples: - - * "-01:45:00" => "23:45:00" on the previous day - - * "-05:13:32" => "19:13:32" on the previous day - -* Remove non-revenue stops - - Stop_times which do not allow pick up or drop off are also known as non-revenue stops. Some GTFS consumers display - these stops as if they were stops that passengers can use, at which point it is helpful to remove them. - - To remove them, apply the following transform: - -+---+ -{"op":"remove_non_revenue_stops"} -+---+ - - Terminals (the first and last stop_time of a trip) can be excluded from removal with the following transform: - -+---+ -{"op":"remove_non_revenue_stops_excluding_terminals"}} -+---+ - -* Replacing trip_headsign with the last stop - - Certain feeds contain unhelpful or incorrect trip_headsign. They can be replaced with the last stop's stop_name. - -+---+ -{"op":"last_stop_to_headsign"} -+---+ - -* Arbitrary Transform - - We also allow you to specify arbitrary transformations as well. Here, you specify your transformation class and we will -automatically instantiate it for use in the transform pipeline. - -+---+ -{"op":"transform", "class":"some.class.implementing.GtfsTranformStrategy"} -+---+ - - We additionally provide a mechanism for setting additional properties of the transform. For all additional properties -specified in the JSON snippet, we will attempt to set that Java bean property value on the instantiated transformation object. -See for example: - -+---+ -{"op":"transform", "class":"org.onebusaway.gtfs_transformer.updates.ShapeTransformStrategy", "shape_id":"6010031", \ -"shape":"wjb~G|abmVpAz]v_@@?wNE_GDaFs@?@dFX`GGjN__@A"} -+--+ - - Here, we set additional properties on the `ShapeTransformStrategy`, making it possible to reuse and configure a generic -transformer to your needs. - -Additional Examples - -* How to Reduce your GTFS - - We can apply a modification that retains certain GTFS entities and all other entities required directly or indirectly by -those entities. For example, create a file with the following contents (call it modifications.txt, as an example): - -+---+ -{"op":"retain", "match":{"file":"routes.txt", "route_short_name":"B15"}} -{"op":"retain", "match":{"file":"routes.txt", "route_short_name":"B62"}} -{"op":"retain", "match":{"file":"routes.txt", "route_short_name":"B63"}} -{"op":"retain", "match":{"file":"routes.txt", "route_short_name":"BX19"}} -{"op":"retain", "match":{"file":"routes.txt", "route_short_name":"Q54"}} -{"op":"retain", "match":{"file":"routes.txt", "route_short_name":"S53"}} -+---+ - - Then run: - -+---+ -java -jar onebusaway-gtfs-transformer-cli.jar --transform=modifications.txt source-gtfs.zip target-gtfs.zip -+--+ - - The resulting GTFS will have the retained only the routes with the matching short names and all other entities required -to support those routes. - -* Add a Full Schedule to an Existing Feed - - Consider an existing feed with a number of routes and stops. We can add an entirely new route, with trips and stop-times -and frequency-based service, using the transform. This can be handy to add temporary service to an existing feed. - -+---+ -{"op":"add", "obj":{"file":"routes.txt", "route_id":"r0", "route_long_name":"Temporary Shuttle", "route_type":3}} - -{"op":"add", "obj":{"file":"calendar.txt", "service_id":"WEEKDAY", "start_date":"20120601", "end_date":"20130630", "monday":1, "tuesday":1, "wednesday":1, "thursday":1, "friday":1}} - -{"op":"add", "obj":{"file":"trips.txt", "trip_id":"t0", "route_id":"r0", "service_id":"WEEKDAY", "trip_headsign":"Inbound"}} -{"op":"add", "obj":{"file":"trips.txt", "trip_id":"t1", "route_id":"r0", "service_id":"WEEKDAY", "trip_headsign":"Outbound"}} - -{"op":"add","obj":{"file":"frequencies.txt","trip_id":"t0","start_time":"06:00:00","end_time":"22:00:00","headway_secs":900}} -{"op":"add","obj":{"file":"frequencies.txt","trip_id":"t1","start_time":"06:00:00","end_time":"22:00:00","headway_secs":900}} - -{"op":"stop_times_factory", "trip_id":"t0", "start_time":"06:00:00", "end_time":"06:20:00", "stop_ids":["s0", "s1", "s2", "s3"]} -{"op":"stop_times_factory", "trip_id":"t1", "start_time":"06:00:00", "end_time":"06:20:00", "stop_ids":["s3", "s2", "s1", "s0"]} -+---+ - - diff --git a/src/site/apt/release-notes.apt.vm b/src/site/apt/release-notes.apt.vm deleted file mode 100644 index 1cde88f3d..000000000 --- a/src/site/apt/release-notes.apt.vm +++ /dev/null @@ -1,223 +0,0 @@ -Release Notes - -* ${currentVersion} - - * Support for "extensions", allowing users of the library to add new GTFS fields for existing files, for reading and writing, without - needing to modify OneBusAway source code. For more details, see {{{./index.html#Reading_Custom_Fields}Reading Custom Fields}}. - - * Support for the proposed <<>> field in <<>>. - - * Support for the <<>> field in <<>>. - - * Bug fix for default-value <<>> field in <<>> - {{{https://github.com/OneBusAway/onebusaway-gtfs-modules/issues/46}issue}} - - * Bug fix to ensure fare_attributes.txt transfers column is always included in GTFS output - {{{https://github.com/OneBusAway/onebusaway-gtfs-modules/issues/50}issue}} - - * Support for "<<>>" expressions in GTFS transformer <<>> ops - {{{https://github.com/OneBusAway/onebusaway-gtfs-modules/issues/48}issue}} - - * Support for "<<>>" expressions in GTFS transformer <<>> ops - {{{https://github.com/OneBusAway/onebusaway-gtfs-modules/issues/52}issue}} - - * Full Documentation: {{${site_base_url}/onebusaway-gtfs-modules/${currentVersion}/}} - -* 1.3.3 - - * Support for the proposed <<>> field in <<>>. - - * Support for <<>> field in both <<>> and <<>>, - with 0 = undefined, 1 = bikes allowed, 2 = bikes NOT allowed. Matches - the wheel-chair accessibility semantics. - - * New transformer to simply <<>> and <<>> entries, - combining service_ids that resolve to the exact same set of service - dates. - - * New transform strategy to fix shapes which are used for the wrong direction of travel. - - * Fix to properly handle using the library in non-English locales. {{{https://github.com/OneBusAway/onebusaway-gtfs-modules/issues/29}issue}} - - * Full Documentation: {{${site_base_url}/onebusaway-gtfs-modules/1.3.3/}} - -* 1.3.2 - - * Introduce <<>>, a utility for loading GTFS into a database - {{{./onebusaway-gtfs-hibernate-cli.html}details}} - - * Support for <<>> <<>> field - {{{https://github.com/OneBusAway/onebusaway-gtfs-modules/pull/27}issue}} - - * Support for negative arrival and departure times in <<>>, along with a "shift_negative_stop_times" GTFS transformer - operation for normalizing feeds with negative stop-times. - - * Fix workaround for Java timezone bug - {{{https://github.com/OneBusAway/onebusaway-gtfs-modules/pull/28}issue}} - - * Introduce <<>>, a GTFS transformer strategy - {{{./apidocs/org/onebusaway/gtfs_transformer/updates/RemoveRepeatedStopTimesInSameTripStrategy.html}javadoc}} - - * Full Documentation: {{${site_base_url}/onebusaway-gtfs-modules/1.3.2/}} - -* 1.3.1 - - * Support for <<>> <<>> field - {{{https://github.com/OneBusAway/onebusaway-gtfs-modules/issues/21}issue}} - - * Support trip-2-trip transfers extension to transfers.txt - {{{https://github.com/OneBusAway/onebusaway-gtfs-modules/issues/24}issue}} - - * Reduced memory consumption for ShapePoints and StopTimes - {{{https://github.com/OneBusAway/onebusaway-gtfs-modules/issues/7}issue}} - - * Always include <<>> and <<>> in <<>> output - {{{https://github.com/OneBusAway/onebusaway-gtfs-modules/issues/12}issue}} - - * onebusaway-gtfs-transformer-cli: - - * Use GTFS file and field names instead of OBA Java object and property names in matchers and updaters. - - * Support for "any(...)" matches. - - * New transforms: "trim_trip", "stop_times_factory", "calendar_extension", and "calendar_simplification". - - * onebusaway-gtfs-merge-cli: - - * Introduce a new OneBusAway GTFS merge tool for combining GTFS feeds. - - * Bug fixes: - - * NPE in ServiceDateUserType - {{{https://github.com/OneBusAway/onebusaway-gtfs-modules/issues/9}issue}} - - * Better handling of <<>> agency resolution - {{{https://github.com/OneBusAway/onebusaway-gtfs-modules/issues/18}issue}} - - * Crash with calendar simplification transform for <<>> with no active dates - {{{https://github.com/OneBusAway/onebusaway-gtfs-modules/issues/20}issue}} - - * More useful file name when throwing CsvEntityIOException -{{{https://github.com/OneBusAway/onebusaway-gtfs-modules/issues/23}issue}} - - * Full Documentation: {{${site_base_url}/onebusaway-gtfs-modules/1.3.1/}} - -* 1.3.0 - - * Support for more natural field order in header when writing CSV file - {{{https://github.com/OneBusAway/onebusaway-gtfs-modules/issues/2}issue}} - - * Support for excluding optional columns when no values are specified - {{{https://github.com/OneBusAway/onebusaway-gtfs-modules/issues/3}issue}} - - * Fix for Daylight Saving Time service calendar computation bug - {{{https://github.com/OneBusAway/onebusaway-gtfs-modules/issues/1}issue}} - - * Fix for bogus-timezone bug - {{{https://github.com/OneBusAway/onebusaway-gtfs-modules/issues/6}issue}} - - * Support for week-long stop_times.txt values - {{{https://github.com/OneBusAway/onebusaway-gtfs-modules/pull/5}issue}} - - * More flexible CSV parsing support from {{${site_base_url}/onebusaway-csv-entities/1.1.0/}} - - * Full Documentation: {{${site_base_url}/onebusaway-gtfs-modules/1.3.0/}} - -* 1.2.6 - - * Migrate to GitHub. - - * When doing GTFS graph retention, allow the agency id referenced by stops, shapes, and service ids to refer to a - non-existent agency. These elements don't technically require an agency in the same way routes do, so it's ok if - they have a non-existent reference. - - * Add a custom field mapping for stop_lat and stop_lon in stops.txt that will better serialize their values when - writing back to the output file. We ran into an issue when working with stop locations very close to the prime - meridian where the stop_lon value was serialized using scientific notation. The new field mapping introduces a - custom formatter that will enforce a more normal decimal representation when the value is written to the output - file. - - * Allow input of multiple GTFS files to GTFS importer. - - * Better support for missing values when reading entity ids in the CSV-to-Object serialization library. - - * Support for frequencies.txt label_only field. - - * Better support for injecting new entities with the GtfsTransformer. - - * Full Documentation: {{${site_base_url}/onebusaway-gtfs-modules/1.2.6/}} - -* 1.2.5 - - * Support for {{{https://developers.google.com/transit/gtfs/reference#feed_info_fields}feed_info.txt}} - - * Support for {{{https://developers.google.com/transit/gtfs/reference#agency_fields}agency.txt}} <<>> field - - * Support for {{{https://developers.google.com/transit/gtfs/reference#frequencies_fields}frequencies.txt}} <<>> field - - * Mark Hibernate GTFS classes as mutable (see {{{https://groups.google.com/group/onebusaway-developers/browse_thread/thread/219d40f7a99c9709}discussion}}) - - * Add <<>> method to {{{./apidocs/org/onebusaway/gtfs/services/GtfsRelationalDao.html#getStopsForStation}GtfsRelationalDao}} (see {{{https://groups.google.com/group/onebusaway-developers/browse_thread/thread/95f4335fcc0d056e}discussion}}) - - * Add <<>> method to {{{./apidocs/org/onebusaway/gtfs/model/calendar/CalendarServiceData.html#makeReadOnly()}CalendarServiceData}} - - * {{{./apidocs/org/onebusaway/gtfs/model/calendar/ServiceDate.html#parseString}ServiceDate.parseString()}} now throws a ParseException - - * Add {{{./apidocs/org/onebusaway/gtfs_transformer/updates/CalendarSimplicationStrategy.html}CalendarSimplicationStrategy}} for simplifying redundant trips / calendar entries - see {{{./onebusaway-gtfs-transformer-cli.html#Merge_Trips_and_Refactor_Calendar_Entries}usage instructions}} - - * Update {{{./apidocs/org/onebusaway/gtfs_transformer/updates/ShapeTransformStrategy.html}ShapeTransformStrategy}} to support updating just the start or end of a shape - - * Full Documentation: {{${site_base_url}/onebusaway-gtfs-modules/1.2.5/}} - -* 1.2.4 - - * More documentation for {{{./onebusaway-gtfs-transformer-cli.html}onebusaway-gtfs-transformer-cli}}. - - * Add <<>> method to {{{./apidocs/org/onebusaway/gtfs/services/GtfsRelationalDao.html#getAllShapeIds()}GtfsRelationalDao}} - - * Add <<>> property to {{{./apidocs/org/onebusaway/gtfs/model/FareAttribute.html}FareAttribute}} - - * Better support for entity id matching in <<>>. - - * Full Documentation: {{${site_base_url}/onebusaway-gtfs-modules/1.2.4/}} - -* 1.2.3 - - * More documentation for {{{./onebusaway-gtfs-transformer-cli.html}onebusaway-gtfs-transformer-cli}}. - - * Better support for entity id matching in <<>>. - - * Full Documentation: {{${site_base_url}/onebusaway-gtfs-modules/1.2.3/}} - -* 1.2.2 - - * Add sorting of calendar.txt and calendar_dates.txt entries by service id when writing to output - - * Add additional manipulation methods to {{{./apidocs/org/onebusaway/gtfs/model/calendar/ServiceDate.html} ServiceDate}} - - * Initial entry for {{{./apidocs/org/onebusaway/gtfs/impl/GenericMutableDaoWrapper.html} GenericMutableDaoWrapper}} - - * Initial entry for {{{./apidocs/org/onebusaway/gtfs_transformer/services/GtfsEntityTransformStrategy.html} GtfsEntityTransformStrategy}} - - * Support in gtfs-transformer for the new entity tranformation strategy - - * Full Documentation: {{${site_base_url}/onebusaway-gtfs-modules/1.2.2/}} - -* 1.2.1 - - * Support for {{{http://groups.google.com/group/gtfs-changes/browse_frm/thread/42a6863ae3661bba/d65149383c0d65e5?lnk=gst&q=bicycle#d65149383c0d65e5} bicycle accessibility}} proposal. - - * Support for {{{http://groups.google.com/group/gtfs-changes/browse_frm/thread/e6ef325c1ae92b6c} exact_times}} proposal. - - * Bump to {{{${site_base_url}/onebusaway-csv-entities/1.0.1/} onebusaway-csv-entities-1.0.1}}. - - * Add {{{./apidocs/org/onebusaway/gtfs/model/AgencyAndIdInstance.html} AgencyAndIdInstance}} class. - - * Full Documentation: {{${site_base_url}/onebusaway-gtfs-modules/1.2.1/}} - -* 1.2.0 - - * Initial Site Documentation - - * Bump to require Maven 3 - - * Refactor CSV entity reading and writing support into its own module: {{${site_base_url}/onebusaway-csv-entities/1.0.0/}} - - * Full Documentation: {{${site_base_url}/onebusaway-gtfs-modules/1.2.0/}} - -* 1.1.11 - -* 1.1.10 - -* 1.1.9 - -* 1.1.8 - -* 1.1.7 - -* 1.1.6 - -* 1.1.5 - -* 1.1.4 - -* 1.1.3 diff --git a/src/site/site.xml b/src/site/site.xml deleted file mode 100644 index 05d5ad552..000000000 --- a/src/site/site.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - org.apache.maven.skins - maven-fluido-skin - 1.11.1 - -