diff --git a/.github/workflows/package_installers.yml b/.github/workflows/package_installers.yml index 1e58a5c4aa..d327e74c41 100644 --- a/.github/workflows/package_installers.yml +++ b/.github/workflows/package_installers.yml @@ -13,6 +13,19 @@ on: types: [ prereleased, released ] workflow_dispatch: +env: + MACOS_APP_NAME: "GTFS Validator" + MACOS_NOTARIZATION_NAME: "notarization.zip" + MACOS_TARGET_PATH: "app/pkg/build/jpackage/GTFS Validator.app" + MACOS_TARGET_DEST_PATH: "app/pkg/build/jpackage" + MACOS_CERTIFICATE: ${{ secrets.MACOS_DEVELOPER_ID_APPLICATION_CERTIFICATE_P12_BASE64 }} + MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_DEVELOPER_ID_APPLICATION_CERTIFICATE_PASSWORD }} + MACOS_CERTIFICATE_NAME: ${{ secrets.MACOS_DEVELOPER_ID_APPLICATION_CERTIFICATE_NAME }} + MACOS_CI_KEYCHAIN_PWD: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }} + MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.MACOS_NOTARIZATION_USERNAME }} + MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }} + MACOS_NOTARIZATION_PWD: ${{ secrets.MACOS_NOTARIZATION_PASSWORD }} + jobs: validate_gradle_wrapper: runs-on: ubuntu-latest @@ -25,7 +38,7 @@ jobs: name: Build and upload packaged app runs-on: ${{ matrix.os }} strategy: -# Adding fail-fast: false so at least some artifacts gets uploaded if others fail + # Adding fail-fast: false so at least some artifacts gets uploaded if others fail fail-fast: false matrix: os: [ macos-latest, windows-latest, ubuntu-latest ] @@ -47,17 +60,18 @@ jobs: # We create a code-signing keychain on MacOS before building and packaging the app, as the # app will be signed as part of the jpackage build phase. - name: "MacOS - Import Certificate: Developer ID Application" + id: codesign if: matrix.os == 'macos-latest' && (github.event_name == 'push' || github.event_name == 'release') - uses: devbotsxyz/import-signing-certificate@v1 - with: - # This should be a certificate + private key, exported as a p12 file and base64 encoded as - # a GitHub secret. The certificate should be the: - # 'Developer ID Application: The International Data Organization For Transport (BF2U75HN4D)' - # certificate, locked with the specified passphrase. - certificate-data: ${{ secrets.MACOS_DEVELOPER_ID_APPLICATION_CERTIFICATE_P12_BASE64 }} - certificate-passphrase: ${{ secrets.MACOS_DEVELOPER_ID_APPLICATION_CERTIFICATE_PASSWORD }} - # The resulting keychain will be locked with this separate password. - keychain-password: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }} + run: | + # Turn our base64-encoded certificate back to a regular .p12 file + echo $MACOS_CERTIFICATE | base64 --decode > certificate.p12 + + # We need to create a new keychain, otherwise using the certificate will prompt with a UI dialog asking for the certificate password, which we can't use in a headless CI environment + security create-keychain -p "${MACOS_CI_KEYCHAIN_PWD}" build.keychain + security default-keychain -s build.keychain + security unlock-keychain -p "${MACOS_CI_KEYCHAIN_PWD}" build.keychain + security import certificate.p12 -k build.keychain -P "${MACOS_CERTIFICATE_PWD}" -T /usr/bin/codesign + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "${MACOS_CI_KEYCHAIN_PWD}" build.keychain - name: "Package GUI app installer with Gradle" uses: gradle/gradle-build-action@v2 @@ -69,24 +83,35 @@ jobs: # On MacOS, we now submit the app for "notarization", where Apple will scan the app for # malware and other issues. This step can take a few minutes or more; the action will wait # until the report is available. - - name: "MacOS - Notarize Release Build" + - name: "MacOS - Notarize & Staple Release Build" if: matrix.os == 'macos-latest' && (github.event_name == 'push' || github.event_name == 'release') - uses: devbotsxyz/xcode-notarize@v1 - with: - product-path: "app/pkg/build/jpackage/GTFS Validator.app" - # The Apple developer account used to run notarization. This account will receive an - # email every time notarization is run. - appstore-connect-username: ${{ secrets.MACOS_NOTARIZATION_USERNAME }} - # The app-specific password configured for the Apple developer account that will be used - # for authentication (different from the main password for the dev account). - appstore-connect-password: ${{ secrets.MACOS_NOTARIZATION_PASSWORD }} + id: notarize_staple_macos_app + run: | + # Store the notarization credentials so that we can prevent a UI password dialog from blocking the CI + + echo "Create keychain profile" + xcrun notarytool store-credentials "notarytool-profile" --apple-id "${MACOS_NOTARIZATION_APPLE_ID}" --team-id "${MACOS_NOTARIZATION_TEAM_ID}" --password "${MACOS_NOTARIZATION_PWD}" + + # We can't notarize an app bundle directly, but we need to compress it as an archive. + # Therefore, we create a zip file containing our app bundle, so that we can send it to the + # notarization service + + echo "Creating temp notarization archive" + ditto -c -k --keepParent "${{ env.MACOS_TARGET_PATH }}" ${{ env.MACOS_NOTARIZATION_NAME }} + + # Here we send the notarization request to the Apple's Notarization service, waiting for the result. + # This typically takes a few seconds inside a CI environment, but it might take more depending on the App + # characteristics. Visit the Notarization docs for more information and strategies on how to optimize it if + # you're curious + + echo "Notarize app" + xcrun notarytool submit "${{ env.MACOS_NOTARIZATION_NAME }}" --keychain-profile "notarytool-profile" --wait + + # Finally, we need to "attach the staple" to our executable, which will allow our app to be + # validated by macOS even when an internet connection is not available. + echo "Attach staple" + xcrun stapler staple "${{ env.MACOS_TARGET_PATH }}" - # Now that we have a notarization report, we attach it to the app binary. - - name: "Mac OS - Staple Release Build" - if: matrix.os == 'macos-latest' && (github.event_name == 'push' || github.event_name == 'release') - uses: devbotsxyz/xcode-staple@v1 - with: - product-path: "app/pkg/build/jpackage/GTFS Validator.app" # Now that we have a notarized app, we can package it. - name: "Mac OS - Package the app" @@ -97,26 +122,26 @@ jobs: appVersion=${appVersion//-SNAPSHOT/} jpackage \ --type dmg \ - --name 'GTFS Validator' \ + --name "${{ env.MACOS_APP_NAME }}" \ --app-version ${appVersion} \ - --app-image app/pkg/build/jpackage/GTFS\ Validator.app \ - --dest app/pkg/build/jpackage + --app-image "${{ env.MACOS_TARGET_PATH }}" \ + --dest ${{ env.MACOS_TARGET_DEST_PATH }} jpackage \ --type pkg \ --name 'GTFS Validator' \ --app-version ${appVersion} \ - --app-image app/pkg/build/jpackage/GTFS\ Validator.app \ - --dest app/pkg/build/jpackage + --app-image "${{ env.MACOS_TARGET_PATH }}" \ + --dest ${{ env.MACOS_TARGET_DEST_PATH }} - name: "Upload Installer" uses: actions/upload-artifact@v3 with: name: Installer - ${{matrix.os}} path: | - app/pkg/build/jpackage/*.msi - app/pkg/build/jpackage/*.dmg - app/pkg/build/jpackage/*.pkg - app/pkg/build/jpackage/*.deb + ${{ env.MACOS_TARGET_DEST_PATH }}/*.msi + ${{ env.MACOS_TARGET_DEST_PATH }}/*.dmg + ${{ env.MACOS_TARGET_DEST_PATH }}/*.pkg + ${{ env.MACOS_TARGET_DEST_PATH }}/*.deb - name: "Upload assets to release" if: github.event_name == 'prerelease' || github.event_name == 'release' diff --git a/core/src/main/java/org/mobilitydata/gtfsvalidator/table/GtfsFeedContainer.java b/core/src/main/java/org/mobilitydata/gtfsvalidator/table/GtfsFeedContainer.java index 735cb01105..9b96d3d68d 100644 --- a/core/src/main/java/org/mobilitydata/gtfsvalidator/table/GtfsFeedContainer.java +++ b/core/src/main/java/org/mobilitydata/gtfsvalidator/table/GtfsFeedContainer.java @@ -80,7 +80,10 @@ public Collection> getTables() { public String tableTotalsText() { List totalList = new ArrayList<>(); for (GtfsTableContainer table : tables.values()) { - if (table.getTableStatus() == TableStatus.MISSING_FILE && !table.isRequired()) continue; + if (table.getTableStatus() == TableStatus.MISSING_FILE + && !table.getDescriptor().isRequired()) { + continue; + } totalList.add( table.gtfsFilename() + "\t" diff --git a/core/src/main/java/org/mobilitydata/gtfsvalidator/table/GtfsTableContainer.java b/core/src/main/java/org/mobilitydata/gtfsvalidator/table/GtfsTableContainer.java index 1a39cc23da..010da6a1da 100644 --- a/core/src/main/java/org/mobilitydata/gtfsvalidator/table/GtfsTableContainer.java +++ b/core/src/main/java/org/mobilitydata/gtfsvalidator/table/GtfsTableContainer.java @@ -33,15 +33,23 @@ */ public abstract class GtfsTableContainer { + private final GtfsTableDescriptor descriptor; + private final TableStatus tableStatus; private final CsvHeader header; - public GtfsTableContainer(TableStatus tableStatus, CsvHeader header) { + public GtfsTableContainer( + GtfsTableDescriptor descriptor, TableStatus tableStatus, CsvHeader header) { + this.descriptor = descriptor; this.tableStatus = tableStatus; this.header = header; } + public GtfsTableDescriptor getDescriptor() { + return descriptor; + } + public TableStatus getTableStatus() { return tableStatus; } @@ -96,24 +104,6 @@ public boolean isMissingFile() { return tableStatus == TableStatus.MISSING_FILE; } - /** - * Tells if the file is recommended according to GTFS. - * - *

Note that a recommended file may be empty. - * - * @return true if the file is recommended, false otherwise - */ - public abstract boolean isRecommended(); - - /** - * Tells if the file is required according to GTFS. - * - *

Note that a required file may be empty. - * - * @return true if the file is required, false otherwise - */ - public abstract boolean isRequired(); - /** * Tells if the file was successfully parsed. * @@ -135,7 +125,7 @@ public boolean isParsedSuccessfully() { case PARSABLE_HEADERS_AND_ROWS: return true; case MISSING_FILE: - return !isRequired(); + return !descriptor.isRequired(); default: return false; } diff --git a/core/src/main/java/org/mobilitydata/gtfsvalidator/table/GtfsTableDescriptor.java b/core/src/main/java/org/mobilitydata/gtfsvalidator/table/GtfsTableDescriptor.java index 43805ff84e..01f3b773db 100644 --- a/core/src/main/java/org/mobilitydata/gtfsvalidator/table/GtfsTableDescriptor.java +++ b/core/src/main/java/org/mobilitydata/gtfsvalidator/table/GtfsTableDescriptor.java @@ -8,6 +8,10 @@ import org.mobilitydata.gtfsvalidator.parsing.CsvHeader; public abstract class GtfsTableDescriptor { + + // True if the specified file is required in a feed. + private boolean required; + public abstract GtfsTableContainer createContainerForInvalidStatus( GtfsTableContainer.TableStatus tableStatus); @@ -24,7 +28,13 @@ public abstract GtfsTableContainer createContainerForHeaderAndEntities( public abstract boolean isRecommended(); - public abstract boolean isRequired(); + public boolean isRequired() { + return this.required; + } + + public void setRequired(boolean required) { + this.required = required; + } public abstract Optional maxCharsPerColumn(); diff --git a/core/src/test/java/org/mobilitydata/gtfsvalidator/testgtfs/GtfsTestTableContainer.java b/core/src/test/java/org/mobilitydata/gtfsvalidator/testgtfs/GtfsTestTableContainer.java index 425d602d27..e84ab8041a 100644 --- a/core/src/test/java/org/mobilitydata/gtfsvalidator/testgtfs/GtfsTestTableContainer.java +++ b/core/src/test/java/org/mobilitydata/gtfsvalidator/testgtfs/GtfsTestTableContainer.java @@ -30,13 +30,14 @@ public class GtfsTestTableContainer extends GtfsTableContainer { private List entities; - private GtfsTestTableContainer(CsvHeader header, List entities) { - super(TableStatus.PARSABLE_HEADERS_AND_ROWS, header); + private GtfsTestTableContainer( + GtfsTestTableDescriptor descriptor, CsvHeader header, List entities) { + super(descriptor, TableStatus.PARSABLE_HEADERS_AND_ROWS, header); this.entities = entities; } public GtfsTestTableContainer(TableStatus tableStatus) { - super(tableStatus, CsvHeader.EMPTY); + super(new GtfsTestTableDescriptor(), tableStatus, CsvHeader.EMPTY); this.entities = new ArrayList<>(); } @@ -50,16 +51,6 @@ public String gtfsFilename() { return GtfsTestEntity.FILENAME; } - @Override - public boolean isRecommended() { - return false; - } - - @Override - public boolean isRequired() { - return true; - } - @Override public List getEntities() { return entities; @@ -67,20 +58,14 @@ public List getEntities() { /** Creates a table with given header and entities */ public static GtfsTestTableContainer forHeaderAndEntities( - CsvHeader header, List entities, NoticeContainer noticeContainer) { - GtfsTestTableContainer table = new GtfsTestTableContainer(header, entities); + GtfsTestTableDescriptor descriptor, + CsvHeader header, + List entities, + NoticeContainer noticeContainer) { + GtfsTestTableContainer table = new GtfsTestTableContainer(descriptor, header, entities); return table; } - /** - * Creates a table with given entities and empty header. This method is intended to be used in - * tests. - */ - public static GtfsTestTableContainer forEntities( - List entities, NoticeContainer noticeContainer) { - return forHeaderAndEntities(CsvHeader.EMPTY, entities, noticeContainer); - } - @Override public ImmutableList getKeyColumnNames() { return KEY_COLUMN_NAMES; diff --git a/core/src/test/java/org/mobilitydata/gtfsvalidator/testgtfs/GtfsTestTableDescriptor.java b/core/src/test/java/org/mobilitydata/gtfsvalidator/testgtfs/GtfsTestTableDescriptor.java index 4e6d73b0b2..58dcdae890 100644 --- a/core/src/test/java/org/mobilitydata/gtfsvalidator/testgtfs/GtfsTestTableDescriptor.java +++ b/core/src/test/java/org/mobilitydata/gtfsvalidator/testgtfs/GtfsTestTableDescriptor.java @@ -21,7 +21,7 @@ public GtfsTableContainer createContainerForInvalidStatus( @Override public GtfsTableContainer createContainerForHeaderAndEntities( CsvHeader header, List entities, NoticeContainer noticeContainer) { - return GtfsTestTableContainer.forHeaderAndEntities(header, entities, noticeContainer); + return GtfsTestTableContainer.forHeaderAndEntities(this, header, entities, noticeContainer); } @Override diff --git a/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/DateTripsValidatorTest.java b/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/DateTripsValidatorTest.java index 56108aab5f..bbea78238c 100644 --- a/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/DateTripsValidatorTest.java +++ b/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/DateTripsValidatorTest.java @@ -18,6 +18,7 @@ import org.mobilitydata.gtfsvalidator.notice.NoticeContainer; import org.mobilitydata.gtfsvalidator.notice.ValidationNotice; import org.mobilitydata.gtfsvalidator.table.*; +import org.mobilitydata.gtfsvalidator.table.GtfsTableContainer.TableStatus; import org.mobilitydata.gtfsvalidator.type.GtfsDate; import org.mobilitydata.gtfsvalidator.util.CalendarUtilTest; @@ -85,8 +86,8 @@ private List validateSimpleServiceWindow( ImmutableSet.copyOf(DayOfWeek.values())); var calendarTable = GtfsCalendarTableContainer.forEntities(ImmutableList.of(calendar), noticeContainer); - var dateTable = new GtfsCalendarDateTableContainer(GtfsTableContainer.TableStatus.EMPTY_FILE); - var frequencyTable = new GtfsFrequencyTableContainer(GtfsTableContainer.TableStatus.EMPTY_FILE); + var dateTable = GtfsCalendarDateTableContainer.forStatus(TableStatus.EMPTY_FILE); + var frequencyTable = GtfsFrequencyTableContainer.forStatus(TableStatus.EMPTY_FILE); var tripBlock = createTripBlock(serviceId, 6, "b1"); var tripContainer = GtfsTripTableContainer.forEntities(tripBlock, noticeContainer); diff --git a/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/ExpiredCalendarValidatorTest.java b/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/ExpiredCalendarValidatorTest.java index caad6d6d94..31c2bc13c0 100644 --- a/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/ExpiredCalendarValidatorTest.java +++ b/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/ExpiredCalendarValidatorTest.java @@ -31,6 +31,7 @@ import org.mobilitydata.gtfsvalidator.input.CurrentDateTime; import org.mobilitydata.gtfsvalidator.notice.NoticeContainer; import org.mobilitydata.gtfsvalidator.table.*; +import org.mobilitydata.gtfsvalidator.table.GtfsTableContainer.TableStatus; import org.mobilitydata.gtfsvalidator.type.GtfsDate; @RunWith(JUnit4.class) @@ -66,7 +67,7 @@ public void calendarEndDateOneDayAgoShouldGenerateNotice() { GtfsCalendarTableContainer calendarTable = GtfsCalendarTableContainer.forEntities(calendars, container); - var dateTable = new GtfsCalendarDateTableContainer(GtfsTableContainer.TableStatus.EMPTY_FILE); + var dateTable = GtfsCalendarDateTableContainer.forStatus(TableStatus.EMPTY_FILE); new ExpiredCalendarValidator(new CurrentDateTime(TEST_NOW), calendarTable, dateTable) .validate(container); assertThat(container.getValidationNotices()) @@ -93,7 +94,7 @@ public void calendarEndDateTodayShouldNotGenerateNotice() { GtfsCalendarTableContainer calendarTable = GtfsCalendarTableContainer.forEntities(calendars, container); - var dateTable = new GtfsCalendarDateTableContainer(GtfsTableContainer.TableStatus.EMPTY_FILE); + var dateTable = GtfsCalendarDateTableContainer.forStatus(TableStatus.EMPTY_FILE); new ExpiredCalendarValidator(new CurrentDateTime(TEST_NOW), calendarTable, dateTable) .validate(container); assertThat(container.getValidationNotices()).isEmpty(); @@ -119,7 +120,7 @@ public void calendarEndDateOneDayFromNowShouldNotGenerateNotice() { GtfsCalendarTableContainer calendarTable = GtfsCalendarTableContainer.forEntities(calendars, container); - var dateTable = new GtfsCalendarDateTableContainer(GtfsTableContainer.TableStatus.EMPTY_FILE); + var dateTable = GtfsCalendarDateTableContainer.forStatus(TableStatus.EMPTY_FILE); new ExpiredCalendarValidator(new CurrentDateTime(TEST_NOW), calendarTable, dateTable) .validate(container); assertThat(container.getValidationNotices()).isEmpty(); @@ -211,7 +212,7 @@ public void calendarWithNoDaysShouldNotGenerateNotice() { GtfsCalendarTableContainer calendarTable = GtfsCalendarTableContainer.forEntities(calendars, container); - var dateTable = new GtfsCalendarDateTableContainer(GtfsTableContainer.TableStatus.EMPTY_FILE); + var dateTable = GtfsCalendarDateTableContainer.forStatus(TableStatus.EMPTY_FILE); new ExpiredCalendarValidator(new CurrentDateTime(TEST_NOW), calendarTable, dateTable) .validate(container); assertThat(container.getValidationNotices()).isEmpty(); diff --git a/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/MatchingFeedAndAgencyLangValidatorTest.java b/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/MatchingFeedAndAgencyLangValidatorTest.java index 51394eec7b..bfeef3dc7c 100644 --- a/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/MatchingFeedAndAgencyLangValidatorTest.java +++ b/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/MatchingFeedAndAgencyLangValidatorTest.java @@ -77,8 +77,8 @@ public static GtfsFeedInfo createFeedInfo(int csvRowNumber, @Nullable Locale fee public void noFeedInfoShouldNotGenerateNotice() { assertThat( generateNotices( - new GtfsAgencyTableContainer(TableStatus.EMPTY_FILE), - new GtfsFeedInfoTableContainer(TableStatus.EMPTY_FILE))) + GtfsAgencyTableContainer.forStatus(TableStatus.EMPTY_FILE), + GtfsFeedInfoTableContainer.forStatus(TableStatus.EMPTY_FILE))) .isEmpty(); } diff --git a/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/MissingCalendarAndCalendarDateValidatorTest.java b/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/MissingCalendarAndCalendarDateValidatorTest.java index 3c5cd74502..afb6e585c1 100644 --- a/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/MissingCalendarAndCalendarDateValidatorTest.java +++ b/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/MissingCalendarAndCalendarDateValidatorTest.java @@ -118,8 +118,8 @@ public void calendarDateOnlyProvidedShouldNotGenerateNotice() { public void bothMissingFilesShouldGenerateNotice() { assertThat( generateNotices( - new GtfsCalendarTableContainer(TableStatus.MISSING_FILE), - new GtfsCalendarDateTableContainer(TableStatus.MISSING_FILE))) + GtfsCalendarTableContainer.forStatus(TableStatus.MISSING_FILE), + GtfsCalendarDateTableContainer.forStatus(TableStatus.MISSING_FILE))) .containsExactly(new MissingCalendarAndCalendarDateFilesNotice()); } } diff --git a/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/TimepointTimeValidatorTest.java b/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/TimepointTimeValidatorTest.java index 159105a2af..8d3c3455fa 100644 --- a/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/TimepointTimeValidatorTest.java +++ b/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/TimepointTimeValidatorTest.java @@ -38,6 +38,7 @@ import org.mobilitydata.gtfsvalidator.parsing.CsvHeader; import org.mobilitydata.gtfsvalidator.table.GtfsStopTime; import org.mobilitydata.gtfsvalidator.table.GtfsStopTimeTableContainer; +import org.mobilitydata.gtfsvalidator.table.GtfsStopTimeTableDescriptor; import org.mobilitydata.gtfsvalidator.type.GtfsTime; import org.mobilitydata.gtfsvalidator.validator.TimepointTimeValidator.MissingTimepointValueNotice; import org.mobilitydata.gtfsvalidator.validator.TimepointTimeValidator.StopTimeTimepointWithoutTimesNotice; @@ -46,7 +47,8 @@ public class TimepointTimeValidatorTest { private static GtfsStopTimeTableContainer createTable( CsvHeader header, List stopTimes, NoticeContainer noticeContainer) { - return GtfsStopTimeTableContainer.forHeaderAndEntities(header, stopTimes, noticeContainer); + return GtfsStopTimeTableContainer.forHeaderAndEntities( + new GtfsStopTimeTableDescriptor(), header, stopTimes, noticeContainer); } private static List generateNotices( diff --git a/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/TranslationFieldAndReferenceValidatorTest.java b/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/TranslationFieldAndReferenceValidatorTest.java index 56ae7c366c..5436b3e87e 100644 --- a/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/TranslationFieldAndReferenceValidatorTest.java +++ b/main/src/test/java/org/mobilitydata/gtfsvalidator/validator/TranslationFieldAndReferenceValidatorTest.java @@ -37,6 +37,7 @@ import org.mobilitydata.gtfsvalidator.table.GtfsStopTimeTableContainer; import org.mobilitydata.gtfsvalidator.table.GtfsTranslation; import org.mobilitydata.gtfsvalidator.table.GtfsTranslationTableContainer; +import org.mobilitydata.gtfsvalidator.table.GtfsTranslationTableDescriptor; import org.mobilitydata.gtfsvalidator.validator.TranslationFieldAndReferenceValidator.TranslationForeignKeyViolationNotice; import org.mobilitydata.gtfsvalidator.validator.TranslationFieldAndReferenceValidator.TranslationUnexpectedValueNotice; import org.mobilitydata.gtfsvalidator.validator.TranslationFieldAndReferenceValidator.TranslationUnknownTableNameNotice; @@ -65,7 +66,7 @@ private static List generateNotices( NoticeContainer noticeContainer = new NoticeContainer(); GtfsTranslationTableContainer translationTable = GtfsTranslationTableContainer.forHeaderAndEntities( - translationHeader, translations, noticeContainer); + new GtfsTranslationTableDescriptor(), translationHeader, translations, noticeContainer); new TranslationFieldAndReferenceValidator( translationTable, new GtfsFeedContainer( diff --git a/processor/src/main/java/org/mobilitydata/gtfsvalidator/processor/TableContainerGenerator.java b/processor/src/main/java/org/mobilitydata/gtfsvalidator/processor/TableContainerGenerator.java index 3e8ea1c44f..f215ecdf0a 100644 --- a/processor/src/main/java/org/mobilitydata/gtfsvalidator/processor/TableContainerGenerator.java +++ b/processor/src/main/java/org/mobilitydata/gtfsvalidator/processor/TableContainerGenerator.java @@ -31,6 +31,7 @@ import org.mobilitydata.gtfsvalidator.notice.NoticeContainer; import org.mobilitydata.gtfsvalidator.parsing.CsvHeader; import org.mobilitydata.gtfsvalidator.table.GtfsTableContainer; +import org.mobilitydata.gtfsvalidator.table.GtfsTableDescriptor; /** * Generates code for a container for a loaded GTFS table. @@ -43,10 +44,15 @@ public class TableContainerGenerator { private final GtfsEntityClasses classNames; private final TableContainerIndexGenerator indexGenerator; + private final ParameterizedTypeName tableDescriptorType; + public TableContainerGenerator(GtfsFileDescriptor fileDescriptor) { this.fileDescriptor = fileDescriptor; this.classNames = new GtfsEntityClasses(fileDescriptor); this.indexGenerator = new TableContainerIndexGenerator(fileDescriptor); + this.tableDescriptorType = + ParameterizedTypeName.get( + ClassName.get(GtfsTableDescriptor.class), classNames.entityImplementationTypeName()); } public JavaFile generateGtfsContainerJavaFile() { @@ -78,22 +84,6 @@ public TypeSpec generateGtfsContainerClass() { .addStatement("return $T.FILENAME", classNames.entityImplementationTypeName()) .build()); - typeSpec.addMethod( - MethodSpec.methodBuilder("isRecommended") - .addAnnotation(Override.class) - .addModifiers(Modifier.PUBLIC) - .returns(boolean.class) - .addStatement("return $L", fileDescriptor.recommended()) - .build()); - - typeSpec.addMethod( - MethodSpec.methodBuilder("isRequired") - .addAnnotation(Override.class) - .addModifiers(Modifier.PUBLIC) - .returns(boolean.class) - .addStatement("return $L", fileDescriptor.required()) - .build()); - typeSpec.addField( ParameterizedTypeName.get(ClassName.get(List.class), gtfsEntityType), "entities", @@ -111,6 +101,7 @@ public TypeSpec generateGtfsContainerClass() { typeSpec.addMethod(generateConstructorWithStatus()); typeSpec.addMethod(generateForHeaderAndEntitiesMethod()); typeSpec.addMethod(generateForEntitiesMethod()); + typeSpec.addMethod(generateForStatusMethod()); indexGenerator.generateMethods(typeSpec); @@ -120,12 +111,13 @@ public TypeSpec generateGtfsContainerClass() { private MethodSpec generateConstructorWithEntities() { return MethodSpec.constructorBuilder() .addModifiers(Modifier.PRIVATE) + .addParameter(tableDescriptorType, "descriptor") .addParameter(CsvHeader.class, "header") .addParameter( ParameterizedTypeName.get( ClassName.get(List.class), classNames.entityImplementationTypeName()), "entities") - .addStatement("super(TableStatus.PARSABLE_HEADERS_AND_ROWS, header)") + .addStatement("super(descriptor, TableStatus.PARSABLE_HEADERS_AND_ROWS, header)") .addStatement("this.entities = entities") .build(); } @@ -133,8 +125,9 @@ private MethodSpec generateConstructorWithEntities() { private MethodSpec generateConstructorWithStatus() { return MethodSpec.constructorBuilder() .addModifiers(Modifier.PUBLIC) + .addParameter(tableDescriptorType, "descriptor") .addParameter(GtfsTableContainer.TableStatus.class, "tableStatus") - .addStatement("super(tableStatus, $T.EMPTY)", CsvHeader.class) + .addStatement("super(descriptor, tableStatus, $T.EMPTY)", CsvHeader.class) .addStatement("this.entities = new $T<>()", ArrayList.class) .build(); } @@ -145,6 +138,7 @@ private MethodSpec generateForHeaderAndEntitiesMethod() { .returns(tableContainerTypeName) .addJavadoc("Creates a table with given header and entities") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + .addParameter(tableDescriptorType, "descriptor") .addParameter(CsvHeader.class, "header") .addParameter( ParameterizedTypeName.get( @@ -152,7 +146,9 @@ private MethodSpec generateForHeaderAndEntitiesMethod() { "entities") .addParameter(NoticeContainer.class, "noticeContainer") .addStatement( - "$T table = new $T(header, entities)", tableContainerTypeName, tableContainerTypeName) + "$T table = new $T(descriptor, header, entities)", + tableContainerTypeName, + tableContainerTypeName) .addStatement("table.setupIndices(noticeContainer)") .addStatement("return table") .build(); @@ -172,7 +168,25 @@ private MethodSpec generateForEntitiesMethod() { "entities") .addParameter(NoticeContainer.class, "noticeContainer") .addStatement( - "return forHeaderAndEntities($T.EMPTY, entities, noticeContainer)", CsvHeader.class) + "return forHeaderAndEntities(new $T(), $T.EMPTY, entities, noticeContainer)", + classNames.tableDescriptorTypeName(), + CsvHeader.class) + .build(); + } + + private MethodSpec generateForStatusMethod() { + TypeName tableContainerTypeName = classNames.tableContainerTypeName(); + return MethodSpec.methodBuilder("forStatus") + .returns(tableContainerTypeName) + .addJavadoc( + "Creates a table with the given TableStatus. This method is intended to be" + + " used in tests.") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + .addParameter(GtfsTableContainer.TableStatus.class, "tableStatus") + .addStatement( + "return new $T(new $T(), tableStatus)", + tableContainerTypeName, + classNames.tableDescriptorTypeName()) .build(); } } diff --git a/processor/src/main/java/org/mobilitydata/gtfsvalidator/processor/TableDescriptorGenerator.java b/processor/src/main/java/org/mobilitydata/gtfsvalidator/processor/TableDescriptorGenerator.java index 304bc93c83..514bbe98c9 100644 --- a/processor/src/main/java/org/mobilitydata/gtfsvalidator/processor/TableDescriptorGenerator.java +++ b/processor/src/main/java/org/mobilitydata/gtfsvalidator/processor/TableDescriptorGenerator.java @@ -105,6 +105,8 @@ public TypeSpec generateGtfsTableDescriptorClass() { .addAnnotation(Generated.class) .addModifiers(Modifier.PUBLIC, Modifier.FINAL); + typeSpec.addMethod(generateConstructor()); + typeSpec.addMethod(generateCreateContainerForInvalidStatusMethod()); typeSpec.addMethod(generateCreateContainerForHeaderAndEntitiesMethod()); typeSpec.addMethod(generateCreateEntityBuilderMethod()); @@ -114,12 +116,18 @@ public TypeSpec generateGtfsTableDescriptorClass() { typeSpec.addMethod(generateGtfsFilenameMethod()); typeSpec.addMethod(generateIsRecommendedMethod()); - typeSpec.addMethod(generateIsRequiredMethod()); typeSpec.addMethod(generateMaxCharsPerColumnMethod()); return typeSpec.build(); } + private MethodSpec generateConstructor() { + return MethodSpec.constructorBuilder() + .addModifiers(Modifier.PUBLIC) + .addStatement("setRequired($L)", fileDescriptor.required()) + .build(); + } + private MethodSpec generateCreateContainerForHeaderAndEntitiesMethod() { return MethodSpec.methodBuilder("createContainerForHeaderAndEntities") .addAnnotation(Override.class) @@ -132,7 +140,7 @@ private MethodSpec generateCreateContainerForHeaderAndEntitiesMethod() { .addParameter(NoticeContainer.class, "noticeContainer") .returns(GtfsTableContainer.class) .addStatement( - "return $T.forHeaderAndEntities(header, entities, noticeContainer)", + "return $T.forHeaderAndEntities(this, header, entities, noticeContainer)", classNames.tableContainerTypeName()) .build(); } @@ -143,7 +151,7 @@ private MethodSpec generateCreateContainerForInvalidStatusMethod() { .addModifiers(Modifier.PUBLIC) .addParameter(GtfsTableContainer.TableStatus.class, "tableStatus") .returns(GtfsTableContainer.class) - .addStatement("return new $T(tableStatus)", classNames.tableContainerTypeName()) + .addStatement("return new $T(this, tableStatus)", classNames.tableContainerTypeName()) .build(); } @@ -303,15 +311,6 @@ private MethodSpec generateIsRecommendedMethod() { .build(); } - private MethodSpec generateIsRequiredMethod() { - return MethodSpec.methodBuilder("isRequired") - .addModifiers(Modifier.PUBLIC) - .returns(boolean.class) - .addAnnotation(Override.class) - .addStatement("return $L", fileDescriptor.required()) - .build(); - } - private MethodSpec generateMaxCharsPerColumnMethod() { MethodSpec.Builder m = MethodSpec.methodBuilder("maxCharsPerColumn")