diff --git a/src/main/java/no/entur/antu/config/NetexDataConfig.java b/src/main/java/no/entur/antu/config/NetexDataConfig.java index 48ccb3c9..51adf847 100644 --- a/src/main/java/no/entur/antu/config/NetexDataConfig.java +++ b/src/main/java/no/entur/antu/config/NetexDataConfig.java @@ -1,7 +1,10 @@ package no.entur.antu.config; +import static no.entur.antu.config.cache.CacheConfig.ACTIVE_DATES_CACHE; import static no.entur.antu.config.cache.CacheConfig.LINE_INFO_CACHE; +import static no.entur.antu.config.cache.CacheConfig.SERVICE_JOURNEY_DAY_TYPES_CACHE; import static no.entur.antu.config.cache.CacheConfig.SERVICE_JOURNEY_INTERCHANGE_INFO_CACHE; +import static no.entur.antu.config.cache.CacheConfig.SERVICE_JOURNEY_OPERATING_DAYS_CACHE; import static no.entur.antu.config.cache.CacheConfig.SERVICE_JOURNEY_STOPS_CACHE; import java.util.List; @@ -25,6 +28,15 @@ NetexDataRepository netexDataRepository( @Qualifier( SERVICE_JOURNEY_STOPS_CACHE ) Map>> serviceJourneyStopsCache, + @Qualifier( + SERVICE_JOURNEY_DAY_TYPES_CACHE + ) Map> serviceJourneyDayTypesCache, + @Qualifier( + ACTIVE_DATES_CACHE + ) Map> activeDatesCache, + @Qualifier( + SERVICE_JOURNEY_OPERATING_DAYS_CACHE + ) Map> serviceJourneyOperatingDaysCache, @Qualifier( SERVICE_JOURNEY_INTERCHANGE_INFO_CACHE ) Map> serviceJourneyInterchangeInfoCache @@ -33,6 +45,9 @@ NetexDataRepository netexDataRepository( redissonClient, lineInfoCache, serviceJourneyStopsCache, + serviceJourneyDayTypesCache, + activeDatesCache, + serviceJourneyOperatingDaysCache, serviceJourneyInterchangeInfoCache ); } diff --git a/src/main/java/no/entur/antu/config/TimetableDataValidatorConfig.java b/src/main/java/no/entur/antu/config/TimetableDataValidatorConfig.java index 8ff9f374..3ee40014 100644 --- a/src/main/java/no/entur/antu/config/TimetableDataValidatorConfig.java +++ b/src/main/java/no/entur/antu/config/TimetableDataValidatorConfig.java @@ -18,15 +18,19 @@ import java.util.List; import java.util.Set; +import no.entur.antu.netexdata.collectors.DatedServiceJourneysCollector; import no.entur.antu.netexdata.collectors.LineInfoCollector; +import no.entur.antu.netexdata.collectors.ServiceJourneyDayTypesCollector; import no.entur.antu.netexdata.collectors.ServiceJourneyInterchangeInfoCollector; import no.entur.antu.netexdata.collectors.ServiceJourneyStopsCollector; +import no.entur.antu.netexdata.collectors.activedatecollector.ActiveDatesCollector; import no.entur.antu.organisation.OrganisationRepository; import no.entur.antu.validation.validator.id.NetexIdValidator; import no.entur.antu.validation.validator.interchange.distance.UnexpectedInterchangeDistanceValidator; import no.entur.antu.validation.validator.interchange.duplicate.DuplicateInterchangesValidator; import no.entur.antu.validation.validator.interchange.mandatoryfields.MandatoryFieldsValidator; import no.entur.antu.validation.validator.interchange.stoppoints.StopPointsInVehicleJourneyValidator; +import no.entur.antu.validation.validator.interchange.waittime.UnexpectedWaitTimeAndActiveDatesValidator; import no.entur.antu.validation.validator.journeypattern.stoppoint.distance.UnexpectedDistanceBetweenStopPointsValidator; import no.entur.antu.validation.validator.journeypattern.stoppoint.identicalstoppoints.IdenticalStopPointsValidator; import no.entur.antu.validation.validator.journeypattern.stoppoint.samequayref.SameQuayRefValidator; @@ -42,7 +46,10 @@ import no.entur.antu.validation.validator.servicelink.distance.UnexpectedDistanceInServiceLinkValidator; import no.entur.antu.validation.validator.servicelink.stoppoints.MismatchedStopPointsValidator; import no.entur.antu.validation.validator.xpath.EnturTimetableDataValidationTreeFactory; -import org.entur.netex.validation.validator.*; +import org.entur.netex.validation.validator.DatasetValidator; +import org.entur.netex.validation.validator.NetexValidatorsRunner; +import org.entur.netex.validation.validator.ValidationReportEntryFactory; +import org.entur.netex.validation.validator.XPathValidator; import org.entur.netex.validation.validator.id.NetexIdUniquenessValidator; import org.entur.netex.validation.validator.id.NetexReferenceValidator; import org.entur.netex.validation.validator.id.ReferenceToValidEntityTypeValidator; @@ -106,6 +113,19 @@ public DuplicateLineNameValidator duplicateLineNameValidator( ); } + @Bean + public UnexpectedWaitTimeAndActiveDatesValidator unexpectedWaitTimeValidator( + @Qualifier( + "validationReportEntryFactory" + ) ValidationReportEntryFactory validationReportEntryFactory, + NetexDataRepository netexDataRepository + ) { + return new UnexpectedWaitTimeAndActiveDatesValidator( + validationReportEntryFactory, + netexDataRepository + ); + } + @Bean public NetexValidatorsRunner timetableDataValidatorsRunner( @Qualifier( @@ -125,10 +145,14 @@ public NetexValidatorsRunner timetableDataValidatorsRunner( ) NetexIdUniquenessValidator netexIdUniquenessValidator, StopPointsInVehicleJourneyValidator stopPointsInVehicleJourneyValidator, DuplicateLineNameValidator duplicateLineNameValidator, + UnexpectedWaitTimeAndActiveDatesValidator unexpectedWaitTimeAndActiveDatesValidator, LineInfoCollector lineInfoCollector, ServiceJourneyStopsCollector serviceJourneyStopsCollector, ServiceJourneyInterchangeInfoCollector serviceJourneyInterchangeInfoCollector, - CommonDataRepositoryLoader commonDataRepository, + ActiveDatesCollector activeDatesCollector, + ServiceJourneyDayTypesCollector serviceJourneyDayTypesCollector, + DatedServiceJourneysCollector datedServiceJourneysCollector, + CommonDataRepositoryLoader commonDataRepositoryLoader, NetexDataRepository netexDataRepository, StopPlaceRepository stopPlaceRepository ) { @@ -165,13 +189,17 @@ public NetexValidatorsRunner timetableDataValidatorsRunner( List netexTimetableDatasetValidators = List.of( duplicateLineNameValidator, - stopPointsInVehicleJourneyValidator + stopPointsInVehicleJourneyValidator, + unexpectedWaitTimeAndActiveDatesValidator ); List commonDataCollectors = List.of( lineInfoCollector, serviceJourneyInterchangeInfoCollector, - serviceJourneyStopsCollector + serviceJourneyStopsCollector, + activeDatesCollector, + serviceJourneyDayTypesCollector, + datedServiceJourneysCollector ); return NetexValidatorsRunner @@ -182,7 +210,7 @@ public NetexValidatorsRunner timetableDataValidatorsRunner( .withJaxbValidators(jaxbValidators) .withDatasetValidators(netexTimetableDatasetValidators) .withNetexDataCollectors(commonDataCollectors) - .withCommonDataRepository(commonDataRepository) + .withCommonDataRepository(commonDataRepositoryLoader) .withNetexDataRepository(netexDataRepository) .withStopPlaceRepository(stopPlaceRepository) .withValidationReportEntryFactory(validationReportEntryFactory) diff --git a/src/main/java/no/entur/antu/config/cache/CacheConfig.java b/src/main/java/no/entur/antu/config/cache/CacheConfig.java index 7f88d9ef..0eab98a4 100644 --- a/src/main/java/no/entur/antu/config/cache/CacheConfig.java +++ b/src/main/java/no/entur/antu/config/cache/CacheConfig.java @@ -40,8 +40,13 @@ public class CacheConfig { public static final String LINE_INFO_CACHE = "linesInfoCache"; public static final String SERVICE_JOURNEY_INTERCHANGE_INFO_CACHE = "serviceJourneyInterchangeInfoCache"; + public static final String SERVICE_JOURNEY_DAY_TYPES_CACHE = + "serviceJourneyDayTypesCache"; public static final String SERVICE_JOURNEY_STOPS_CACHE = "serviceJourneyStopsCache"; + public static final String SERVICE_JOURNEY_OPERATING_DAYS_CACHE = + "serviceJourneyOperatingDaysCache"; + public static final String ACTIVE_DATES_CACHE = "activeDatesCache"; public static final String QUAY_ID_NOT_FOUND_CACHE = "quayIdNotFoundCache"; private static final Kryo5Codec DEFAULT_CODEC = new Kryo5Codec(); @@ -172,6 +177,17 @@ public Map> serviceJourneyInterchangeInfoCache( ); } + @Bean(name = SERVICE_JOURNEY_DAY_TYPES_CACHE) + public Map> serviceJourneyDayTypesCache( + RedissonClient redissonClient + ) { + return getOrCreateReportScopedCache( + redissonClient, + SERVICE_JOURNEY_DAY_TYPES_CACHE, + new CompositeCodec(new StringCodec(), new StringCodec()) + ); + } + @Bean(name = SERVICE_JOURNEY_STOPS_CACHE) public Map>> serviceJourneyStopsCache( RedissonClient redissonClient @@ -183,6 +199,28 @@ public Map>> serviceJourneyStopsCache( ); } + @Bean(name = ACTIVE_DATES_CACHE) + public Map> activeDatesCache( + RedissonClient redissonClient + ) { + return getOrCreateReportScopedCache( + redissonClient, + ACTIVE_DATES_CACHE, + new CompositeCodec(new StringCodec(), new StringCodec()) + ); + } + + @Bean(name = SERVICE_JOURNEY_OPERATING_DAYS_CACHE) + public Map> serviceJourneyOperatingDaysCache( + RedissonClient redissonClient + ) { + return getOrCreateReportScopedCache( + redissonClient, + SERVICE_JOURNEY_OPERATING_DAYS_CACHE, + new CompositeCodec(new StringCodec(), new StringCodec()) + ); + } + @Bean public NetexIdRepository netexIdRepository( RedissonClient redissonClient, diff --git a/src/main/java/no/entur/antu/config/cache/NetexDataCollectorConfig.java b/src/main/java/no/entur/antu/config/cache/NetexDataCollectorConfig.java index 5e8e9316..c8787e86 100644 --- a/src/main/java/no/entur/antu/config/cache/NetexDataCollectorConfig.java +++ b/src/main/java/no/entur/antu/config/cache/NetexDataCollectorConfig.java @@ -1,14 +1,20 @@ package no.entur.antu.config.cache; +import static no.entur.antu.config.cache.CacheConfig.ACTIVE_DATES_CACHE; import static no.entur.antu.config.cache.CacheConfig.LINE_INFO_CACHE; +import static no.entur.antu.config.cache.CacheConfig.SERVICE_JOURNEY_DAY_TYPES_CACHE; import static no.entur.antu.config.cache.CacheConfig.SERVICE_JOURNEY_INTERCHANGE_INFO_CACHE; +import static no.entur.antu.config.cache.CacheConfig.SERVICE_JOURNEY_OPERATING_DAYS_CACHE; import static no.entur.antu.config.cache.CacheConfig.SERVICE_JOURNEY_STOPS_CACHE; import java.util.List; import java.util.Map; +import no.entur.antu.netexdata.collectors.DatedServiceJourneysCollector; import no.entur.antu.netexdata.collectors.LineInfoCollector; +import no.entur.antu.netexdata.collectors.ServiceJourneyDayTypesCollector; import no.entur.antu.netexdata.collectors.ServiceJourneyInterchangeInfoCollector; import no.entur.antu.netexdata.collectors.ServiceJourneyStopsCollector; +import no.entur.antu.netexdata.collectors.activedatecollector.ActiveDatesCollector; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; @@ -25,6 +31,29 @@ public LineInfoCollector lineInfoScraper( return new LineInfoCollector(redissonClient, lineInfoCache); } + @Bean + public ActiveDatesCollector activeDatesCollector( + RedissonClient redissonClient, + @Qualifier( + ACTIVE_DATES_CACHE + ) Map> activeDatesCache + ) { + return new ActiveDatesCollector(redissonClient, activeDatesCache); + } + + @Bean + public DatedServiceJourneysCollector datedServiceJourneysCollector( + RedissonClient redissonClient, + @Qualifier( + SERVICE_JOURNEY_OPERATING_DAYS_CACHE + ) Map> serviceJourneyOperatingDaysCache + ) { + return new DatedServiceJourneysCollector( + redissonClient, + serviceJourneyOperatingDaysCache + ); + } + @Bean public ServiceJourneyInterchangeInfoCollector serviceJourneyInterchangeInfoCollector( RedissonClient redissonClient, @@ -38,6 +67,19 @@ public ServiceJourneyInterchangeInfoCollector serviceJourneyInterchangeInfoColle ); } + @Bean + public ServiceJourneyDayTypesCollector serviceJourneyDayTypesCollector( + RedissonClient redissonClient, + @Qualifier( + SERVICE_JOURNEY_DAY_TYPES_CACHE + ) Map> serviceJourneyDayTypesCache + ) { + return new ServiceJourneyDayTypesCollector( + redissonClient, + serviceJourneyDayTypesCache + ); + } + @Bean public ServiceJourneyStopsCollector serviceJourneyStopsCollector( RedissonClient redissonClient, diff --git a/src/main/java/no/entur/antu/netexdata/DefaultNetexDataRepository.java b/src/main/java/no/entur/antu/netexdata/DefaultNetexDataRepository.java index e7125cb7..1e260ac1 100644 --- a/src/main/java/no/entur/antu/netexdata/DefaultNetexDataRepository.java +++ b/src/main/java/no/entur/antu/netexdata/DefaultNetexDataRepository.java @@ -5,6 +5,7 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; import no.entur.antu.exception.AntuException; import org.entur.netex.validation.validator.model.ActiveDates; import org.entur.netex.validation.validator.model.ActiveDatesId; @@ -23,15 +24,24 @@ public class DefaultNetexDataRepository implements NetexDataRepositoryLoader { private final Map> lineInfoCache; private final Map>> serviceJourneyStopsCache; + private final Map> serviceJourneyDayTypesCache; + private final Map> activeDatesCache; + private final Map> serviceJourneyOperatingDaysCache; private final Map> serviceJourneyInterchangeInfoCache; public DefaultNetexDataRepository( Map> lineInfoCache, Map>> serviceJourneyStopsCache, + Map> serviceJourneyDayTypesCache, + Map> activeDatesCache, + Map> serviceJourneyOperatingDaysCache, Map> serviceJourneyInterchangeInfoCache ) { this.lineInfoCache = lineInfoCache; this.serviceJourneyStopsCache = serviceJourneyStopsCache; + this.serviceJourneyDayTypesCache = serviceJourneyDayTypesCache; + this.activeDatesCache = activeDatesCache; + this.serviceJourneyOperatingDaysCache = serviceJourneyOperatingDaysCache; this.serviceJourneyInterchangeInfoCache = serviceJourneyInterchangeInfoCache; } @@ -67,40 +77,79 @@ public Map> serviceJourneyStops( ); } - @Override - public List serviceJourneyInterchangeInfos( + public Map> serviceJourneyDayTypes( String validationReportId ) { - return Optional - .ofNullable(serviceJourneyInterchangeInfoCache) - .map(Map::entrySet) + return serviceJourneyDayTypesCache + .keySet() .stream() - .flatMap(Set::stream) - .filter(entry -> entry.getKey().startsWith(validationReportId)) - .flatMap(entry -> entry.getValue().stream()) - .map(ServiceJourneyInterchangeInfo::fromString) - .toList(); + .filter(k -> k.startsWith(validationReportId)) + .map(serviceJourneyDayTypesCache::get) + .flatMap(m -> m.entrySet().stream()) + .collect( + Collectors.toMap( + entry -> ServiceJourneyId.ofValidId(entry.getKey()), + entry -> + Stream.of(entry.getValue().split(",")).map(DayTypeId::new).toList(), + (p, n) -> n + ) + ); } @Override - public Map> serviceJourneyDayTypes( + public Map> serviceJourneyOperatingDays( String validationReportId ) { - throw new UnsupportedOperationException(); + return serviceJourneyOperatingDaysCache + .keySet() + .stream() + .filter(k -> k.startsWith(validationReportId)) + .map(serviceJourneyOperatingDaysCache::get) + .flatMap(m -> m.entrySet().stream()) + .collect( + Collectors.toMap( + entry -> ServiceJourneyId.ofValidId(entry.getKey()), + entry -> + Stream + .of(entry.getValue().split(",")) + .map(OperatingDayId::new) + .toList(), + (p, n) -> n + ) + ); } - @Override public Map activeDates( String validationReportId ) { - throw new UnsupportedOperationException(); + return activeDatesCache + .keySet() + .stream() + .filter(k -> k.startsWith(validationReportId)) + .map(activeDatesCache::get) + .flatMap(m -> m.entrySet().stream()) + .collect( + Collectors.toMap( + entry -> ActiveDatesId.of(entry.getKey()), + entry -> ActiveDates.fromString(entry.getValue()), + (p, n) -> n + ) + ); } @Override - public Map> serviceJourneyOperatingDays( + public List serviceJourneyInterchangeInfos( String validationReportId ) { - throw new UnsupportedOperationException(); + return Optional + .ofNullable(serviceJourneyInterchangeInfoCache) + .map(Map::entrySet) + .stream() + .flatMap(Set::stream) + .filter(entry -> entry.getKey().startsWith(validationReportId)) + .flatMap(entry -> entry.getValue().stream()) + .map(ServiceJourneyInterchangeInfo::fromString) + .toList(); } @Override diff --git a/src/main/java/no/entur/antu/netexdata/RedisNetexDataRepository.java b/src/main/java/no/entur/antu/netexdata/RedisNetexDataRepository.java index 1ec4efc6..89fabdc0 100644 --- a/src/main/java/no/entur/antu/netexdata/RedisNetexDataRepository.java +++ b/src/main/java/no/entur/antu/netexdata/RedisNetexDataRepository.java @@ -12,11 +12,17 @@ public RedisNetexDataRepository( RedissonClient redissonClient, Map> lineInfoCache, Map>> serviceJourneyStopsCache, + Map> serviceJourneyDayTypesCache, + Map> activeDatesCache, + Map> serviceJourneyOperatingDaysCache, Map> serviceJourneyInterchangeInfoCache ) { super( lineInfoCache, serviceJourneyStopsCache, + serviceJourneyDayTypesCache, + activeDatesCache, + serviceJourneyOperatingDaysCache, serviceJourneyInterchangeInfoCache ); this.redissonClient = redissonClient; diff --git a/src/main/java/no/entur/antu/netexdata/collectors/DatedServiceJourneysCollector.java b/src/main/java/no/entur/antu/netexdata/collectors/DatedServiceJourneysCollector.java new file mode 100644 index 00000000..d4866fd1 --- /dev/null +++ b/src/main/java/no/entur/antu/netexdata/collectors/DatedServiceJourneysCollector.java @@ -0,0 +1,150 @@ +package no.entur.antu.netexdata.collectors; + +import static no.entur.antu.config.cache.CacheConfig.SERVICE_JOURNEY_OPERATING_DAYS_CACHE; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; +import jakarta.xml.bind.JAXBElement; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import org.entur.netex.validation.validator.jaxb.JAXBValidationContext; +import org.entur.netex.validation.validator.jaxb.NetexDataCollector; +import org.redisson.api.RLock; +import org.redisson.api.RMap; +import org.redisson.api.RedissonClient; +import org.rutebanken.netex.model.DatedServiceJourney; +import org.rutebanken.netex.model.ServiceJourneyRefStructure; + +public class DatedServiceJourneysCollector extends NetexDataCollector { + + private final RedissonClient redissonClient; + private final Map> serviceJourneyOperatingDaysCache; + + public DatedServiceJourneysCollector( + RedissonClient redissonClient, + Map> serviceJourneyOperatingDaysCache + ) { + this.redissonClient = redissonClient; + this.serviceJourneyOperatingDaysCache = serviceJourneyOperatingDaysCache; + } + + @Override + protected void collectDataFromLineFile( + JAXBValidationContext validationContext + ) { + Map operatingDaysPerServiceJourney = + getOperatingDaysPerServiceJourneyIsAsStrings(validationContext); + + if (!operatingDaysPerServiceJourney.isEmpty()) { + addServiceJourneyOperatingDays( + validationContext.getValidationReportId(), + validationContext.getFileName(), + operatingDaysPerServiceJourney + ); + } + } + + static Map getOperatingDaysPerServiceJourneyIsAsStrings( + JAXBValidationContext validationContext + ) { + Multimap serviceJourneyOperatingDays = validationContext + .datedServiceJourneys() + .stream() + .map(DatedServiceJourneysCollector::operatingDaysRefsPerServiceJourney) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(toMultimap(Map.Entry::getKey, Map.Entry::getValue)); + + return serviceJourneyOperatingDays + .asMap() + .entrySet() + .stream() + .collect( + Collectors.toMap( + Map.Entry::getKey, + entry -> String.join(",", entry.getValue()) + ) + ); + } + + @Override + protected void collectDataFromCommonFile( + JAXBValidationContext validationContext + ) { + // No service journeys and journey patterns in common files + } + + /** + * List of operating days references per service journey. + * There is only one serviceJourneyRef per datedServiceJourney. + */ + private static Optional> operatingDaysRefsPerServiceJourney( + DatedServiceJourney datedServiceJourney + ) { + return datedServiceJourney + .getJourneyRef() + .stream() + .map(JAXBElement::getValue) + .filter(ServiceJourneyRefStructure.class::isInstance) + .map(ServiceJourneyRefStructure.class::cast) + .filter(serviceJourneyRef -> serviceJourneyRef.getRef() != null) + .map(serviceJourneyRef -> + Map.entry( + serviceJourneyRef.getRef(), + datedServiceJourney.getOperatingDayRef().getRef() + ) + ) + .findFirst(); + } + + private void addServiceJourneyOperatingDays( + String validationReportId, + String filename, + Map serviceJourneyOperatingDays + ) { + RLock lock = redissonClient.getLock(validationReportId); + try { + lock.lock(); + + String keyName = + validationReportId + + "_" + + SERVICE_JOURNEY_OPERATING_DAYS_CACHE + + "_" + + filename; + + RMap serviceJourneyOperatingDaysForFile = + redissonClient.getMap(keyName); + serviceJourneyOperatingDaysForFile.putAll(serviceJourneyOperatingDays); + serviceJourneyOperatingDaysCache.put( + keyName, + serviceJourneyOperatingDaysForFile + ); + } finally { + if (lock.isHeldByCurrentThread()) { + lock.unlock(); + } + } + } + + static Collector> toMultimap( + Function keyMapper, + Function valueMapper + ) { + return Collector.of( + ArrayListMultimap::create, // Supplier: Create a new Multimap + (multimap, assignment) -> + multimap.put( + keyMapper.apply(assignment), + valueMapper.apply(assignment) + ), // Accumulator + (m1, m2) -> { // Combiner + m1.putAll(m2); + return m1; + } + ); + } +} diff --git a/src/main/java/no/entur/antu/netexdata/collectors/LineInfoCollector.java b/src/main/java/no/entur/antu/netexdata/collectors/LineInfoCollector.java index e763ea14..9a78e9ac 100644 --- a/src/main/java/no/entur/antu/netexdata/collectors/LineInfoCollector.java +++ b/src/main/java/no/entur/antu/netexdata/collectors/LineInfoCollector.java @@ -35,7 +35,7 @@ protected void collectDataFromLineFile( @Override protected void collectDataFromCommonFile( - JAXBValidationContext validationContext + JAXBValidationContext jaxbValidationContext ) { // No Lines in common files } diff --git a/src/main/java/no/entur/antu/netexdata/collectors/ServiceJourneyDayTypesCollector.java b/src/main/java/no/entur/antu/netexdata/collectors/ServiceJourneyDayTypesCollector.java new file mode 100644 index 00000000..8fe80316 --- /dev/null +++ b/src/main/java/no/entur/antu/netexdata/collectors/ServiceJourneyDayTypesCollector.java @@ -0,0 +1,103 @@ +package no.entur.antu.netexdata.collectors; + +import static no.entur.antu.config.cache.CacheConfig.SERVICE_JOURNEY_DAY_TYPES_CACHE; + +import java.util.Map; +import java.util.stream.Collectors; +import no.entur.antu.validation.validator.support.NetexUtils; +import org.entur.netex.validation.validator.jaxb.JAXBValidationContext; +import org.entur.netex.validation.validator.jaxb.NetexDataCollector; +import org.entur.netex.validation.validator.model.DayTypeId; +import org.redisson.api.RLock; +import org.redisson.api.RMap; +import org.redisson.api.RedissonClient; + +public class ServiceJourneyDayTypesCollector extends NetexDataCollector { + + private final RedissonClient redissonClient; + private final Map> serviceJourneyDayTypesCache; + + public ServiceJourneyDayTypesCollector( + RedissonClient redissonClient, + Map> serviceJourneyDayTypesCache + ) { + this.redissonClient = redissonClient; + this.serviceJourneyDayTypesCache = serviceJourneyDayTypesCache; + } + + @Override + protected void collectDataFromLineFile( + JAXBValidationContext validationContext + ) { + Map serviceJourneyDayTypes = + getDayTypesPerServiceJourneyIdAsStrings(validationContext); + + if (!serviceJourneyDayTypes.isEmpty()) { + addServiceJourneyDayTypes( + validationContext.getValidationReportId(), + validationContext.getFileName(), + serviceJourneyDayTypes + ); + } + } + + static Map getDayTypesPerServiceJourneyIdAsStrings( + JAXBValidationContext validationContext + ) { + return NetexUtils + .validServiceJourneys(validationContext) + .stream() + .map(serviceJourney -> + Map.entry( + serviceJourney.getId(), + DayTypeId + .of(serviceJourney) + .stream() + .map(DayTypeId::toString) + .toList() + ) + ) + .filter(entry -> !entry.getValue().isEmpty()) + .collect( + Collectors.toMap( + Map.Entry::getKey, + entry -> String.join(",", entry.getValue()) + ) + ); + } + + @Override + protected void collectDataFromCommonFile( + JAXBValidationContext validationContext + ) { + // No service journeys and journey patterns in common files + } + + private void addServiceJourneyDayTypes( + String validationReportId, + String filename, + Map serviceJourneyDayTypes + ) { + RLock lock = redissonClient.getLock(validationReportId); + try { + lock.lock(); + + String keyName = + validationReportId + + "_" + + SERVICE_JOURNEY_DAY_TYPES_CACHE + + "_" + + filename; + + RMap serviceJourneyDayTypesMap = redissonClient.getMap( + keyName + ); + serviceJourneyDayTypesMap.putAll(serviceJourneyDayTypes); + serviceJourneyDayTypesCache.put(keyName, serviceJourneyDayTypesMap); + } finally { + if (lock.isHeldByCurrentThread()) { + lock.unlock(); + } + } + } +} diff --git a/src/main/java/no/entur/antu/netexdata/collectors/ServiceJourneyStopsCollector.java b/src/main/java/no/entur/antu/netexdata/collectors/ServiceJourneyStopsCollector.java index caa29eb3..03a9222d 100644 --- a/src/main/java/no/entur/antu/netexdata/collectors/ServiceJourneyStopsCollector.java +++ b/src/main/java/no/entur/antu/netexdata/collectors/ServiceJourneyStopsCollector.java @@ -13,9 +13,7 @@ import org.redisson.api.RLock; import org.redisson.api.RMap; import org.redisson.api.RedissonClient; -import org.springframework.stereotype.Component; -@Component public class ServiceJourneyStopsCollector extends NetexDataCollector { private final RedissonClient redissonClient; @@ -38,8 +36,8 @@ protected void collectDataFromLineFile( // service journeys in them? // if (validationContext.serviceJourneyInterchanges().findAny().isPresent()) {} - Map> serviceJourneyStops = validationContext - .serviceJourneys() + Map> serviceJourneyStops = NetexUtils + .validServiceJourneys(validationContext) .stream() .map(serviceJourney -> { Map scheduledStopPointIdMap = diff --git a/src/main/java/no/entur/antu/netexdata/collectors/activedatecollector/ActiveDatesCollector.java b/src/main/java/no/entur/antu/netexdata/collectors/activedatecollector/ActiveDatesCollector.java new file mode 100644 index 00000000..08972bf8 --- /dev/null +++ b/src/main/java/no/entur/antu/netexdata/collectors/activedatecollector/ActiveDatesCollector.java @@ -0,0 +1,210 @@ +package no.entur.antu.netexdata.collectors.activedatecollector; + +import static no.entur.antu.config.cache.CacheConfig.ACTIVE_DATES_CACHE; +import static no.entur.antu.netexdata.collectors.activedatecollector.calender.CalendarUtilities.getValidityForFrameOrDefault; + +import jakarta.xml.bind.JAXBElement; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import no.entur.antu.netexdata.collectors.activedatecollector.calender.ActiveDatesBuilder; +import no.entur.antu.netexdata.collectors.activedatecollector.calender.ServiceCalendarFrameObject; +import org.entur.netex.validation.validator.jaxb.JAXBValidationContext; +import org.entur.netex.validation.validator.jaxb.NetexDataCollector; +import org.redisson.api.RLock; +import org.redisson.api.RMap; +import org.redisson.api.RedissonClient; +import org.rutebanken.netex.model.CompositeFrame; +import org.rutebanken.netex.model.ServiceCalendarFrame; +import org.rutebanken.netex.model.ValidBetween; + +public class ActiveDatesCollector extends NetexDataCollector { + + private final RedissonClient redissonClient; + private final Map> activeDatesCache; + + public ActiveDatesCollector( + RedissonClient redissonClient, + Map> activeDatesCache + ) { + this.redissonClient = redissonClient; + this.activeDatesCache = activeDatesCache; + } + + @Override + protected void collectDataFromLineFile( + JAXBValidationContext validationContext + ) { + collectData(validationContext); + } + + @Override + protected void collectDataFromCommonFile( + JAXBValidationContext validationContext + ) { + collectData(validationContext); + } + + private void collectData(JAXBValidationContext validationContext) { + List serviceCalendarFrameObjects = + parseServiceCalendarFrame(validationContext); + + Map activeDates = getActiveDatesPerIdAsMapOfStrings( + serviceCalendarFrameObjects + ); + + if (!activeDates.isEmpty()) { + storeActiveDates( + validationContext.getValidationReportId(), + validationContext.getFileName(), + activeDates + ); + } + } + + static List parseServiceCalendarFrame( + JAXBValidationContext validationContext + ) { + // TODO: Is it possible that the file has ServiceCalendarFrames has ServiceCalendarFrames outside the compositeFrame, + // while compositeFrame also exists? if yes, parse both and combine the result. + if (validationContext.hasCompositeFrames()) { + return validationContext + .compositeFrames() + .stream() + .map(ActiveDatesCollector::getServiceCalendarFrameObjects) + .flatMap(List::stream) + .toList(); + } else if (validationContext.hasServiceCalendarFrames()) { + return validationContext + .serviceCalendarFrames() + .stream() + .map(ActiveDatesCollector::getServiceCalendarFrameObjects) + .toList(); + } + return List.of(); + } + + static Map getActiveDatesPerIdAsMapOfStrings( + List serviceCalendarFrameObjects + ) { + ActiveDatesBuilder activeDatesBuilder = new ActiveDatesBuilder(); + + Map activeDatesPerDayTypes = getActiveDatesPerDayTypeId( + serviceCalendarFrameObjects, + activeDatesBuilder + ); + + // This is needed for DatedServiceJourneys + Map activeDatesPerOperationDays = + getActiveDatesPerOperatingDayId( + serviceCalendarFrameObjects, + activeDatesBuilder + ); + + return Stream + .concat( + activeDatesPerDayTypes.entrySet().stream(), + activeDatesPerOperationDays.entrySet().stream() + ) + .collect( + Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v1) + ); + } + + private static Map getActiveDatesPerOperatingDayId( + List serviceCalendarFrameObjects, + ActiveDatesBuilder activeDatesBuilder + ) { + return serviceCalendarFrameObjects + .stream() + .map(activeDatesBuilder::buildPerOperatingDay) + .flatMap(map -> map.entrySet().stream()) + .filter(entry -> entry.getValue().isValid()) + .collect( + Collectors.toMap( + entry -> entry.getKey().toString(), + entry -> entry.getValue().toString(), + (v1, v2) -> v1 + ) + ); + } + + private static Map getActiveDatesPerDayTypeId( + List serviceCalendarFrameObjects, + ActiveDatesBuilder activeDatesBuilder + ) { + return serviceCalendarFrameObjects + .stream() + .map(activeDatesBuilder::buildPerDayType) + .flatMap(map -> map.entrySet().stream()) + .filter(entry -> entry.getValue().isValid()) + .collect( + Collectors.toMap( + entry -> entry.getKey().toString(), + entry -> entry.getValue().toString(), + (v1, v2) -> v1 + ) + ); + } + + private void storeActiveDates( + String validationReportId, + String filename, + Map activeDates + ) { + RLock lock = redissonClient.getLock(validationReportId); + try { + lock.lock(); + + String keyName = + validationReportId + "_" + ACTIVE_DATES_CACHE + "_" + filename; + + RMap activeDatesForFile = redissonClient.getMap(keyName); + activeDatesForFile.putAll(activeDates); + activeDatesCache.put(keyName, activeDatesForFile); + } finally { + if (lock.isHeldByCurrentThread()) { + lock.unlock(); + } + } + } + + private static List getServiceCalendarFrameObjects( + CompositeFrame compositeFrame + ) { + // When grouping Frames into a CompositeFrame, ValidityCondition must be the same for all its frames. + // That is, ValidityCondition is not set per frame, but is implicitly controlled from the CompositeFrame. + ValidBetween validityForCompositeFrame = getValidityForFrameOrDefault( + compositeFrame, + null + ); + return compositeFrame + .getFrames() + .getCommonFrame() + .stream() + .map(JAXBElement::getValue) + .filter(ServiceCalendarFrame.class::isInstance) + .map(ServiceCalendarFrame.class::cast) + .map(serviceCalendarFrame -> + ServiceCalendarFrameObject.ofNullable( + serviceCalendarFrame, + validityForCompositeFrame + ) + ) + .toList(); + } + + private static ServiceCalendarFrameObject getServiceCalendarFrameObjects( + ServiceCalendarFrame serviceCalendarFrame + ) { + ValidBetween validityForServiceCalendarFrame = getValidityForFrameOrDefault( + serviceCalendarFrame, + null + ); + return ServiceCalendarFrameObject.ofNullable( + serviceCalendarFrame, + validityForServiceCalendarFrame + ); + } +} diff --git a/src/main/java/no/entur/antu/netexdata/collectors/activedatecollector/calender/ActiveDatesBuilder.java b/src/main/java/no/entur/antu/netexdata/collectors/activedatecollector/calender/ActiveDatesBuilder.java new file mode 100644 index 00000000..f96e1d06 --- /dev/null +++ b/src/main/java/no/entur/antu/netexdata/collectors/activedatecollector/calender/ActiveDatesBuilder.java @@ -0,0 +1,359 @@ +package no.entur.antu.netexdata.collectors.activedatecollector.calender; + +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.mapping; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; +import static no.entur.antu.netexdata.collectors.activedatecollector.calender.CalendarUtilities.getOrDefault; +import static no.entur.antu.netexdata.collectors.activedatecollector.calender.CalendarUtilities.isWithinValidRange; + +import jakarta.xml.bind.JAXBElement; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Predicate; +import org.entur.netex.validation.validator.model.ActiveDates; +import org.entur.netex.validation.validator.model.DayTypeId; +import org.entur.netex.validation.validator.model.OperatingDayId; +import org.rutebanken.netex.model.DayOfWeekEnumeration; +import org.rutebanken.netex.model.DayType; +import org.rutebanken.netex.model.DayTypeAssignment; +import org.rutebanken.netex.model.OperatingDay; +import org.rutebanken.netex.model.OperatingDay_VersionStructure; +import org.rutebanken.netex.model.PropertyOfDay; +import org.rutebanken.netex.model.ValidBetween; +import org.rutebanken.netex.model.VersionOfObjectRefStructure; + +public class ActiveDatesBuilder { + + private final Map activeDatesForDayTypeRef = + new HashMap<>(); + private final List excludedDates = new ArrayList<>(); + private int intDayTypes = 0; + + public Map buildPerDayType( + ServiceCalendarFrameObject serviceCalendarFrameObject + ) { + // Creating the DayTypes for ServiceCalendarFrame + serviceCalendarFrameObject + .calendarData() + .dayTypes() + .forEach((dayTypeRef, dayType) -> { + activeDatesForDayTypeRef.put( + dayTypeRef, + new ActiveDates(new ArrayList<>()) + ); + addDayType(dayType); + }); + + if (serviceCalendarFrameObject.serviceCalendar() != null) { + // Creating the DayTypes for ServiceCalendar + serviceCalendarFrameObject + .serviceCalendar() + .calendarData() + .dayTypes() + .forEach((dayTypeRef, dayType) -> { + activeDatesForDayTypeRef.put( + dayTypeRef, + new ActiveDates(new ArrayList<>()) + ); + addDayType(dayType); + }); + } + + // Creating ActiveDates form DayTypeAssignments for Dates in ServiceCalendarFrame + activeDatesForDates( + serviceCalendarFrameObject.calendarData(), + serviceCalendarFrameObject.validBetween() + ); + + if (serviceCalendarFrameObject.serviceCalendar() != null) { + // Creating ActiveDates form DayTypeAssignments for Dates in ServiceCalendar + activeDatesForDates( + serviceCalendarFrameObject.serviceCalendar().calendarData(), + serviceCalendarFrameObject.serviceCalendar().validBetween() + ); + } + + // Creating ActiveDates form DayTypeAssignments for OperatingDays for ServiceCalendarFrame + activeDatesForOperatingDays( + serviceCalendarFrameObject.calendarData(), + serviceCalendarFrameObject.validBetween() + ); + + if (serviceCalendarFrameObject.serviceCalendar() != null) { + // Creating ActiveDates form DayTypeAssignments for OperatingDays for ServiceCalendar + activeDatesForOperatingDays( + serviceCalendarFrameObject.serviceCalendar().calendarData(), + serviceCalendarFrameObject.serviceCalendar().validBetween() + ); + } + + // Creating ActiveDates form DayTypeAssignments for OperatingPeriods for ServiceCalendarFrame + activeDatesForOperatingPeriods( + serviceCalendarFrameObject.calendarData(), + serviceCalendarFrameObject.validBetween() + ); + + if (serviceCalendarFrameObject.serviceCalendar() != null) { + // Creating ActiveDates form DayTypeAssignments for OperatingPeriods for ServiceCalendar + activeDatesForOperatingPeriods( + serviceCalendarFrameObject.serviceCalendar().calendarData(), + serviceCalendarFrameObject.serviceCalendar().validBetween() + ); + } + + return Map.copyOf(activeDatesForDayTypeRef); + } + + public Map buildPerOperatingDay( + ServiceCalendarFrameObject serviceCalendarFrameObject + ) { + return serviceCalendarFrameObject + .calendarData() + .operatingDays() + .entrySet() + .stream() + .filter(entry -> + isWithinValidRange( + entry.getValue().getCalendarDate(), + serviceCalendarFrameObject.validBetween() + ) + ) + .collect( + toMap( + Map.Entry::getKey, + entry -> + new ActiveDates( + List.of(entry.getValue().getCalendarDate().toLocalDate()) + ) + ) + ); + } + + private void addDayType(DayType dayType) { + if (dayType.getProperties() != null) { + for (PropertyOfDay propertyOfDay : dayType + .getProperties() + .getPropertyOfDay()) { + List daysOfWeeks = propertyOfDay.getDaysOfWeek(); + + for (DayOfWeekEnumeration dayOfWeek : daysOfWeeks) { + List dayTypeEnums = convertDayOfWeek(dayOfWeek); + + for (DayOfWeekEnumeration dayTypeEnum : dayTypeEnums) { + int mask = 1 << dayTypeEnum.ordinal(); + this.intDayTypes |= mask; + } + } + } + } + } + + private void activeDatesForDates( + CalendarData calendarData, + ValidBetween validBetween + ) { + // Dates + for (DayTypeId dayTypeId : calendarData.dayTypeAssignments().keySet()) { + Collection dayTypeAssignments = calendarData + .dayTypeAssignments() + .get(dayTypeId); + Map> includedAndExcludedDates = + findIncludedAndExcludedDates(dayTypeAssignments, validBetween); + + Optional + .ofNullable(includedAndExcludedDates.get(Boolean.FALSE)) + .filter(Predicate.not(List::isEmpty)) + .ifPresent(excludedDates::addAll); + + // It should be true here, otherwise error for missing reference should already be reported. + // If it's false, it means that the DayTypeAssignment is referring to a dayType that is not defined in the dayTypes. + if (activeDatesForDayTypeRef.containsKey(dayTypeId)) { + Optional + .ofNullable(includedAndExcludedDates.get(Boolean.TRUE)) + .ifPresent(activeDatesForDayTypeRef.get(dayTypeId).dates()::addAll); + } + } + } + + private void activeDatesForOperatingDays( + CalendarData calendarData, + ValidBetween validBetween + ) { + // Operating days + for (DayTypeId dayTypeId : calendarData.dayTypeAssignments().keySet()) { + Collection dayTypeAssignments = calendarData + .dayTypeAssignments() + .get(dayTypeId); + Map> includedAndExcludedDates = + findIncludedAndExcludedOperatingDays( + dayTypeAssignments, + validBetween, + calendarData.operatingDays() + ); + + Optional + .ofNullable(includedAndExcludedDates.get(Boolean.FALSE)) + .filter(Predicate.not(List::isEmpty)) + .ifPresent(excludedDates::addAll); + + // It should be true here, otherwise error for missing reference should already be reported. + // If it's false, it means that the DayTypeAssignment is referring to a dayType that is not defined in the dayTypes. + if (activeDatesForDayTypeRef.containsKey(dayTypeId)) { + Optional + .ofNullable(includedAndExcludedDates.get(Boolean.TRUE)) + .ifPresent(activeDatesForDayTypeRef.get(dayTypeId).dates()::addAll); + } + } + } + + private void activeDatesForOperatingPeriods( + CalendarData calendarData, + ValidBetween validBetween + ) { + for (DayTypeId dayTypeId : calendarData.dayTypeAssignments().keys()) { + Collection dayTypeAssignments = calendarData + .dayTypeAssignments() + .get(dayTypeId); + dayTypeAssignments.forEach(dayTypeAssignment -> + Optional + .ofNullable(dayTypeAssignment.getOperatingPeriodRef()) + .map(JAXBElement::getValue) + .map(VersionOfObjectRefStructure::getRef) + .map(calendarData.operatingPeriods()::get) + .map(operatingPeriod -> + ValidOperatingPeriod.of( + operatingPeriod, + validBetween, + calendarData.operatingDays() + ) + ) + .filter(ValidOperatingPeriod::isValid) + .ifPresent(validOperatingPeriod -> + activeDatesForDayTypeRef + .get(dayTypeId) + .dates() + .addAll(validOperatingPeriod.toDates(excludedDates, intDayTypes)) + ) + ); + } + } + + private static Map> findIncludedAndExcludedDates( + Collection dayTypeAssignments, + ValidBetween validBetween + ) { + return dayTypeAssignments + .stream() + .filter(dayTypeAssignment -> + Optional + .ofNullable(dayTypeAssignment.getDate()) + .filter(date -> isWithinValidRange(date, validBetween)) + .isPresent() + ) + .collect( + groupingBy( + dayTypeAssignment -> + getOrDefault(dayTypeAssignment.isIsAvailable(), Boolean.TRUE), + mapping( + dayTypeAssignment -> dayTypeAssignment.getDate().toLocalDate(), + toList() + ) + ) + ); + } + + private Map> findIncludedAndExcludedOperatingDays( + Collection dayTypeAssignments, + ValidBetween validBetween, + Map operatingDays + ) { + return dayTypeAssignments + .stream() + .filter(dayTypeAssignment -> + Optional + .ofNullable(OperatingDayId.of(dayTypeAssignment)) + .map(operatingDays::get) + .map(OperatingDay::getCalendarDate) + .filter(dateOfOperation -> + isWithinValidRange(dateOfOperation, validBetween) + ) + .isPresent() + ) + .collect( + groupingBy( + dta -> getOrDefault(dta.isIsAvailable(), Boolean.TRUE), + mapping( + dta -> + Optional + .ofNullable(OperatingDayId.of(dta)) + .map(operatingDays::get) + .map(OperatingDay_VersionStructure::getCalendarDate) + .map(LocalDateTime::toLocalDate) + .orElse(null), + toList() + ) + ) + ); + } + + private static List convertDayOfWeek( + DayOfWeekEnumeration dayOfWeek + ) { + List days = new ArrayList<>(); + + switch (dayOfWeek) { + case MONDAY: + days.add(DayOfWeekEnumeration.MONDAY); + break; + case TUESDAY: + days.add(DayOfWeekEnumeration.TUESDAY); + break; + case WEDNESDAY: + days.add(DayOfWeekEnumeration.WEDNESDAY); + break; + case THURSDAY: + days.add(DayOfWeekEnumeration.THURSDAY); + break; + case FRIDAY: + days.add(DayOfWeekEnumeration.FRIDAY); + break; + case SATURDAY: + days.add(DayOfWeekEnumeration.SATURDAY); + break; + case SUNDAY: + days.add(DayOfWeekEnumeration.SUNDAY); + break; + case EVERYDAY: + days.add(DayOfWeekEnumeration.MONDAY); + days.add(DayOfWeekEnumeration.TUESDAY); + days.add(DayOfWeekEnumeration.WEDNESDAY); + days.add(DayOfWeekEnumeration.THURSDAY); + days.add(DayOfWeekEnumeration.FRIDAY); + days.add(DayOfWeekEnumeration.SATURDAY); + days.add(DayOfWeekEnumeration.SUNDAY); + break; + case WEEKDAYS: + days.add(DayOfWeekEnumeration.MONDAY); + days.add(DayOfWeekEnumeration.TUESDAY); + days.add(DayOfWeekEnumeration.WEDNESDAY); + days.add(DayOfWeekEnumeration.THURSDAY); + days.add(DayOfWeekEnumeration.FRIDAY); + break; + case WEEKEND: + days.add(DayOfWeekEnumeration.SATURDAY); + days.add(DayOfWeekEnumeration.SUNDAY); + break; + case NONE: + // None + break; + } + return days; + } +} diff --git a/src/main/java/no/entur/antu/netexdata/collectors/activedatecollector/calender/CalendarData.java b/src/main/java/no/entur/antu/netexdata/collectors/activedatecollector/calender/CalendarData.java new file mode 100644 index 00000000..0f03c81b --- /dev/null +++ b/src/main/java/no/entur/antu/netexdata/collectors/activedatecollector/calender/CalendarData.java @@ -0,0 +1,17 @@ +package no.entur.antu.netexdata.collectors.activedatecollector.calender; + +import com.google.common.collect.Multimap; +import java.util.Map; +import org.entur.netex.validation.validator.model.DayTypeId; +import org.entur.netex.validation.validator.model.OperatingDayId; +import org.rutebanken.netex.model.DayType; +import org.rutebanken.netex.model.DayTypeAssignment; +import org.rutebanken.netex.model.OperatingDay; +import org.rutebanken.netex.model.OperatingPeriod; + +public record CalendarData( + Map dayTypes, + Map operatingPeriods, + Map operatingDays, + Multimap dayTypeAssignments +) {} diff --git a/src/main/java/no/entur/antu/netexdata/collectors/activedatecollector/calender/CalendarUtilities.java b/src/main/java/no/entur/antu/netexdata/collectors/activedatecollector/calender/CalendarUtilities.java new file mode 100644 index 00000000..9420aa5d --- /dev/null +++ b/src/main/java/no/entur/antu/netexdata/collectors/activedatecollector/calender/CalendarUtilities.java @@ -0,0 +1,136 @@ +package no.entur.antu.netexdata.collectors.activedatecollector.calender; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; +import java.time.LocalDateTime; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collector; +import org.rutebanken.netex.model.AvailabilityCondition; +import org.rutebanken.netex.model.Common_VersionFrameStructure; +import org.rutebanken.netex.model.DayTypeAssignment; +import org.rutebanken.netex.model.ValidBetween; +import org.rutebanken.netex.model.ValidityConditions_RelStructure; + +public class CalendarUtilities { + + static Collector> toMultimap( + Function keyMapper, + Function valueMapper + ) { + return Collector.of( + ArrayListMultimap::create, // Supplier: Create a new Multimap + (multimap, assignment) -> + multimap.put( + keyMapper.apply(assignment), + valueMapper.apply(assignment) + ), // Accumulator + (m1, m2) -> { // Combiner + m1.putAll(m2); + return m1; + } + ); + } + + static ValidBetween getValidBetween( + ValidityConditions_RelStructure validityConditionStruct + ) { + return Optional + .ofNullable(validityConditionStruct) + .map( + ValidityConditions_RelStructure::getValidityConditionRefOrValidBetweenOrValidityCondition_ + ) + .filter(elements -> !elements.isEmpty()) + .map(elements -> elements.get(0)) + .flatMap(CalendarUtilities::toValidBetween) + .orElse(null); + } + + private static Optional toValidBetween( + Object validityConditionElement + ) { + if (validityConditionElement instanceof ValidBetween) { + return Optional.of((ValidBetween) validityConditionElement); + } + + if (validityConditionElement instanceof javax.xml.bind.JAXBElement) { + return handleJaxbElement( + (javax.xml.bind.JAXBElement) validityConditionElement + ); + } + + throw new RuntimeException( + "Only support ValidBetween and AvailabilityCondition as validityCondition" + ); + } + + private static Optional handleJaxbElement( + javax.xml.bind.JAXBElement jaxbElement + ) { + Object value = jaxbElement.getValue(); + + if (value instanceof AvailabilityCondition availabilityCondition) { + return Optional.of( + new ValidBetween() + .withFromDate(availabilityCondition.getFromDate()) + .withToDate(availabilityCondition.getToDate()) + ); + } + + throw new RuntimeException( + "Only support ValidBetween and AvailabilityCondition as validityCondition" + ); + } + + public static ValidBetween getValidityForFrameOrDefault( + Common_VersionFrameStructure frameStructure, + ValidBetween defaultValidity + ) { + if (frameStructure.getContentValidityConditions() != null) { + return getValidBetween(frameStructure.getContentValidityConditions()); + } + + if (frameStructure.getValidityConditions() != null) { + return getValidBetween(frameStructure.getValidityConditions()); + } + + if ( + frameStructure.getValidBetween() != null && + !frameStructure.getValidBetween().isEmpty() + ) { + return frameStructure.getValidBetween().get(0); + } + return defaultValidity; + } + + static boolean isWithinValidRange( + LocalDateTime dateOfOperation, + ValidBetween validBetween + ) { + if (validBetween == null) { + // Always valid + return true; + } else if ( + validBetween.getFromDate() != null && validBetween.getToDate() != null + ) { + // Limited by both from and to date + return ( + !dateOfOperation.isBefore(validBetween.getFromDate()) && + !dateOfOperation.isAfter(validBetween.getToDate()) + ); + } else if (validBetween.getFromDate() != null) { + // Must be after valid start date + return !dateOfOperation.isBefore(validBetween.getFromDate()); + } else if (validBetween.getToDate() != null) { + // Must be before valid start date + return dateOfOperation.isBefore(validBetween.getToDate()); + } else { + // Both from and to empty + return true; + } + } + + static V getOrDefault(V value, V defaultValue) { + return value != null ? value : defaultValue; + } +} diff --git a/src/main/java/no/entur/antu/netexdata/collectors/activedatecollector/calender/ServiceCalendarFrameObject.java b/src/main/java/no/entur/antu/netexdata/collectors/activedatecollector/calender/ServiceCalendarFrameObject.java new file mode 100644 index 00000000..81564c2d --- /dev/null +++ b/src/main/java/no/entur/antu/netexdata/collectors/activedatecollector/calender/ServiceCalendarFrameObject.java @@ -0,0 +1,133 @@ +package no.entur.antu.netexdata.collectors.activedatecollector.calender; + +import static no.entur.antu.netexdata.collectors.activedatecollector.calender.CalendarUtilities.getValidityForFrameOrDefault; +import static no.entur.antu.netexdata.collectors.activedatecollector.calender.CalendarUtilities.toMultimap; + +import com.google.common.collect.Multimap; +import jakarta.xml.bind.JAXBElement; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; +import no.entur.antu.exception.AntuException; +import org.entur.netex.validation.validator.model.DayTypeId; +import org.entur.netex.validation.validator.model.OperatingDayId; +import org.rutebanken.netex.model.DayType; +import org.rutebanken.netex.model.DayTypeAssignment; +import org.rutebanken.netex.model.DayTypeAssignmentsInFrame_RelStructure; +import org.rutebanken.netex.model.DayTypesInFrame_RelStructure; +import org.rutebanken.netex.model.EntityStructure; +import org.rutebanken.netex.model.OperatingDay; +import org.rutebanken.netex.model.OperatingDaysInFrame_RelStructure; +import org.rutebanken.netex.model.OperatingPeriod; +import org.rutebanken.netex.model.OperatingPeriodsInFrame_RelStructure; +import org.rutebanken.netex.model.ServiceCalendarFrame; +import org.rutebanken.netex.model.ValidBetween; + +public record ServiceCalendarFrameObject( + ValidBetween validBetween, + CalendarData calendarData, + ServiceCalendarObject serviceCalendar +) { + public static ServiceCalendarFrameObject ofNullable( + ServiceCalendarFrame serviceCalendarFrame + ) { + return ofNullable(serviceCalendarFrame, null); + } + + public static ServiceCalendarFrameObject ofNullable( + ServiceCalendarFrame serviceCalendarFrame, + ValidBetween compositeFrameValidity + ) { + if (serviceCalendarFrame == null) { + throw new AntuException( + "ServiceCalendarFrame_VersionFrameStructure is null" + ); + } + ValidBetween serviceCalendarFrameValidity = getValidityForFrameOrDefault( + serviceCalendarFrame, + compositeFrameValidity + ); + return new ServiceCalendarFrameObject( + serviceCalendarFrameValidity, + new CalendarData( + getDayTypes(serviceCalendarFrame), + getOperatingPeriods(serviceCalendarFrame), + getOperatingDays(serviceCalendarFrame), + getDayTypeAssignmentByDayTypeId(serviceCalendarFrame) + ), + ServiceCalendarObject.ofNullable( + serviceCalendarFrame.getServiceCalendar(), + serviceCalendarFrameValidity + ) + ); + } + + private static Multimap getDayTypeAssignmentByDayTypeId( + ServiceCalendarFrame serviceCalendarFrame + ) { + return Optional + .ofNullable(serviceCalendarFrame.getDayTypeAssignments()) + .map(DayTypeAssignmentsInFrame_RelStructure::getDayTypeAssignment) + .stream() + .flatMap(Collection::stream) + .collect(toMultimap(DayTypeId::of, Function.identity())); + } + + private static Map getOperatingDays( + ServiceCalendarFrame serviceCalendarFrame + ) { + return Optional + .ofNullable(serviceCalendarFrame.getOperatingDays()) + .map(OperatingDaysInFrame_RelStructure::getOperatingDay) + .stream() + .flatMap(Collection::stream) + .collect(Collectors.toMap(OperatingDayId::of, Function.identity())); + } + + private static Map getOperatingPeriods( + ServiceCalendarFrame serviceCalendarFrame + ) { + return Optional + .ofNullable(serviceCalendarFrame.getOperatingPeriods()) + .map( + OperatingPeriodsInFrame_RelStructure::getOperatingPeriodOrUicOperatingPeriod + ) + .stream() + .flatMap(List::stream) + .filter(OperatingPeriod.class::isInstance) + .map(OperatingPeriod.class::cast) + .collect(Collectors.toMap(EntityStructure::getId, Function.identity())); + } + + private static Map getDayTypes( + ServiceCalendarFrame serviceCalendarFrame + ) { + return Optional + .ofNullable(serviceCalendarFrame.getDayTypes()) + .map(ServiceCalendarFrameObject::parseDayTypes) + .stream() + .flatMap(List::stream) + .filter(dayType -> DayTypeId.isValid(dayType.getId())) + .collect( + Collectors.toMap( + dayType -> new DayTypeId(dayType.getId()), + Function.identity() + ) + ); + } + + private static List parseDayTypes( + DayTypesInFrame_RelStructure element + ) { + return element + .getDayType_() + .stream() + .map(JAXBElement::getValue) + .filter(DayType.class::isInstance) + .map(DayType.class::cast) + .toList(); + } +} diff --git a/src/main/java/no/entur/antu/netexdata/collectors/activedatecollector/calender/ServiceCalendarObject.java b/src/main/java/no/entur/antu/netexdata/collectors/activedatecollector/calender/ServiceCalendarObject.java new file mode 100644 index 00000000..56c59f3d --- /dev/null +++ b/src/main/java/no/entur/antu/netexdata/collectors/activedatecollector/calender/ServiceCalendarObject.java @@ -0,0 +1,168 @@ +package no.entur.antu.netexdata.collectors.activedatecollector.calender; + +import static no.entur.antu.netexdata.collectors.activedatecollector.calender.CalendarUtilities.getValidBetween; +import static no.entur.antu.netexdata.collectors.activedatecollector.calender.CalendarUtilities.toMultimap; + +import com.google.common.collect.Multimap; +import jakarta.xml.bind.JAXBElement; +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.entur.netex.validation.validator.model.DayTypeId; +import org.entur.netex.validation.validator.model.OperatingDayId; +import org.rutebanken.netex.model.DayType; +import org.rutebanken.netex.model.DayTypeAssignment; +import org.rutebanken.netex.model.DayTypeAssignments_RelStructure; +import org.rutebanken.netex.model.DayTypes_RelStructure; +import org.rutebanken.netex.model.EntityStructure; +import org.rutebanken.netex.model.OperatingDay; +import org.rutebanken.netex.model.OperatingDays_RelStructure; +import org.rutebanken.netex.model.OperatingPeriod; +import org.rutebanken.netex.model.OperatingPeriod_VersionStructure; +import org.rutebanken.netex.model.OperatingPeriods_RelStructure; +import org.rutebanken.netex.model.ServiceCalendar; +import org.rutebanken.netex.model.ValidBetween; + +public record ServiceCalendarObject( + ValidBetween validBetween, + CalendarData calendarData +) { + static ServiceCalendarObject ofNullable( + ServiceCalendar serviceCalendar, + ValidBetween serviceCalendarFrameValidity + ) { + if (serviceCalendar == null) { + return null; + } + return new ServiceCalendarObject( + getServiceCalendarValidity(serviceCalendar, serviceCalendarFrameValidity), + new CalendarData( + getDayTypes(serviceCalendar), + getOperatingPeriods(serviceCalendar), + getOperatingDays(serviceCalendar), + getDayTypeAssignmentByDayTypeId(serviceCalendar) + ) + ); + } + + private static Multimap getDayTypeAssignmentByDayTypeId( + ServiceCalendar serviceCalendar + ) { + return Optional + .ofNullable(serviceCalendar.getDayTypeAssignments()) + .map(DayTypeAssignments_RelStructure::getDayTypeAssignment) + .stream() + .flatMap(Collection::stream) + .collect(toMultimap(DayTypeId::of, Function.identity())); + } + + private static Map getOperatingDays( + ServiceCalendar serviceCalendar + ) { + return Optional + .ofNullable(serviceCalendar.getOperatingDays()) + .map(OperatingDays_RelStructure::getOperatingDayRefOrOperatingDay) + .stream() + .flatMap(Collection::stream) + .filter(OperatingDay.class::isInstance) + .map(OperatingDay.class::cast) + .collect(Collectors.toMap(OperatingDayId::of, Function.identity())); + } + + private static Map getOperatingPeriods( + ServiceCalendar serviceCalendar + ) { + return Optional + .ofNullable(serviceCalendar.getOperatingPeriods()) + .map(ServiceCalendarObject::parseOperatingPeriods) + .stream() + .flatMap(List::stream) + .filter(OperatingPeriod.class::isInstance) + .map(OperatingPeriod.class::cast) + .collect(Collectors.toMap(EntityStructure::getId, Function.identity())); + } + + private static Map getDayTypes( + ServiceCalendar serviceCalendar + ) { + return Optional + .ofNullable(serviceCalendar.getDayTypes()) + .map(ServiceCalendarObject::parseDayTypes) + .stream() + .flatMap(List::stream) + .filter(dayType -> DayTypeId.isValid(dayType.getId())) + .collect( + Collectors.toMap( + dayType -> new DayTypeId(dayType.getId()), + Function.identity() + ) + ); + } + + private static ValidBetween getServiceCalendarValidity( + ServiceCalendar serviceCalendar, + ValidBetween serviceCalendarFrameValidity + ) { + if ( + serviceCalendar.getFromDate() != null && + serviceCalendar.getToDate() != null + ) { + LocalDateTime fromDateTime = serviceCalendar.getFromDate(); + LocalDateTime toDateTime = serviceCalendar.getToDate(); + return new ValidBetween() + .withFromDate(fromDateTime) + .withToDate(toDateTime); + } else { + ValidBetween entityValidity = getValidBetweenForServiceCalendar( + serviceCalendar + ); + if (entityValidity != null) { + return entityValidity; + } + return serviceCalendarFrameValidity; + } + } + + static ValidBetween getValidBetweenForServiceCalendar( + ServiceCalendar entityStruct + ) { + ValidBetween validBetween = null; + + if (entityStruct.getValidityConditions() != null) { + validBetween = getValidBetween(entityStruct.getValidityConditions()); + } else if ( + entityStruct.getValidBetween() != null && + !entityStruct.getValidBetween().isEmpty() + ) { + validBetween = entityStruct.getValidBetween().get(0); + } + + return validBetween; + } + + private static List parseDayTypes(DayTypes_RelStructure dayTypes) { + return dayTypes + .getDayTypeRefOrDayType_() + .stream() + .map(JAXBElement::getValue) + .filter(DayType.class::isInstance) + .map(DayType.class::cast) + .toList(); + } + + private static List parseOperatingPeriods( + OperatingPeriods_RelStructure operatingPeriods + ) { + return operatingPeriods + .getOperatingPeriodRefOrOperatingPeriodOrUicOperatingPeriod() + .stream() + .map(JAXBElement::getValue) + .filter(OperatingPeriod_VersionStructure.class::isInstance) + .map(OperatingPeriod_VersionStructure.class::cast) + .toList(); + } +} diff --git a/src/main/java/no/entur/antu/netexdata/collectors/activedatecollector/calender/ValidOperatingPeriod.java b/src/main/java/no/entur/antu/netexdata/collectors/activedatecollector/calender/ValidOperatingPeriod.java new file mode 100644 index 00000000..3ad9bf12 --- /dev/null +++ b/src/main/java/no/entur/antu/netexdata/collectors/activedatecollector/calender/ValidOperatingPeriod.java @@ -0,0 +1,115 @@ +package no.entur.antu.netexdata.collectors.activedatecollector.calender; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.entur.netex.validation.validator.model.OperatingDayId; +import org.rutebanken.netex.model.OperatingDay; +import org.rutebanken.netex.model.OperatingPeriod; +import org.rutebanken.netex.model.ValidBetween; + +record ValidOperatingPeriod(LocalDate startDate, LocalDate endDate) { + static ValidOperatingPeriod of( + OperatingPeriod operatingPeriod, + ValidBetween validBetween, + Map operatingDays + ) { + LocalDate fromDate = getFromDate(operatingPeriod, operatingDays); + LocalDate toDate = getToDate(operatingPeriod, operatingDays); + + return cutToValidityCondition(fromDate, toDate, validBetween); + } + + private static LocalDate getFromDate( + OperatingPeriod operatingPeriod, + Map operatingDays + ) { + return Optional + .ofNullable(OperatingDayId.ofFromOperatingDayRef(operatingPeriod)) + .map(operatingDays::get) + .map(OperatingDay::getCalendarDate) + .map(LocalDateTime::toLocalDate) + .orElseGet(() -> operatingPeriod.getFromDate().toLocalDate()); + } + + private static LocalDate getToDate( + OperatingPeriod operatingPeriod, + Map operatingDays + ) { + return Optional + .ofNullable(OperatingDayId.ofToOperatingDayRef(operatingPeriod)) + .map(operatingDays::get) + .map(OperatingDay::getCalendarDate) + .map(LocalDateTime::toLocalDate) + .orElseGet(() -> operatingPeriod.getToDate().toLocalDate()); + } + + // Adjust operating period to validity condition + private static ValidOperatingPeriod cutToValidityCondition( + LocalDate startDate, + LocalDate endDate, + ValidBetween validBetween + ) { + if (validBetween == null) { + return new ValidOperatingPeriod(startDate, endDate); + } + + LocalDate validFrom = validBetween.getFromDate() != null + ? validBetween.getFromDate().toLocalDate() + : null; + LocalDate validTo = validBetween.getToDate() != null + ? validBetween.getToDate().toLocalDate() + : null; + + // Check if the period is completely outside the valid range + if ( + (validFrom != null && endDate.isBefore(validFrom)) || + (validTo != null && startDate.isAfter(validTo)) + ) { + return new ValidOperatingPeriod(null, null); + } + + // Adjust the start and end dates to be within the valid range + LocalDate adjustedStart = ( + validFrom != null && startDate.isBefore(validFrom) + ) + ? validFrom + : startDate; + LocalDate adjustedEnd = (validTo != null && endDate.isAfter(validTo)) + ? validTo + : endDate; + + return new ValidOperatingPeriod(adjustedStart, adjustedEnd); + } + + List toDates(List excludedDates, int intDayTypes) { + if (!isValid()) { + return List.of(); + } + + List dates = new ArrayList<>(); + + if (intDayTypes != 0) { + LocalDate date = startDate; + + while (!date.isAfter(endDate)) { + int aDayOfWeek = date.getDayOfWeek().getValue() - 1; + int aDayOfWeekFlag = 1 << aDayOfWeek; + if ((intDayTypes & aDayOfWeekFlag) == aDayOfWeekFlag) { + if (excludedDates == null || !excludedDates.contains(date)) { + dates.add(date); + } + } + date = date.plusDays(1); + } + } + return dates; + } + + public boolean isValid() { + return startDate != null && endDate != null; + } +} diff --git a/src/main/java/no/entur/antu/validation/validator/interchange/waittime/UnexpectedWaitTimeAndActiveDatesContext.java b/src/main/java/no/entur/antu/validation/validator/interchange/waittime/UnexpectedWaitTimeAndActiveDatesContext.java new file mode 100644 index 00000000..1f3a765d --- /dev/null +++ b/src/main/java/no/entur/antu/validation/validator/interchange/waittime/UnexpectedWaitTimeAndActiveDatesContext.java @@ -0,0 +1,144 @@ +package no.entur.antu.validation.validator.interchange.waittime; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; +import org.entur.netex.validation.validator.jaxb.NetexDataRepository; +import org.entur.netex.validation.validator.model.ActiveDates; +import org.entur.netex.validation.validator.model.ActiveDatesId; +import org.entur.netex.validation.validator.model.DayTypeId; +import org.entur.netex.validation.validator.model.OperatingDayId; +import org.entur.netex.validation.validator.model.ScheduledStopPointId; +import org.entur.netex.validation.validator.model.ServiceJourneyId; +import org.entur.netex.validation.validator.model.ServiceJourneyInterchangeInfo; +import org.entur.netex.validation.validator.model.ServiceJourneyStop; + +public record UnexpectedWaitTimeAndActiveDatesContext( + ServiceJourneyInterchangeInfo serviceJourneyInterchangeInfo, + // ServiceJourneyStop at the fromStopPoint in fromJourneyRef from Cache + ServiceJourneyStop fromServiceJourneyStop, + // ServiceJourneySStop at the toStopPoint in toJourneyRef from Cache + ServiceJourneyStop toServiceJourneyStop, + // Active dates for the fromJourneyRef from Cache + List fromServiceJourneyActiveDates, + // Active dates for the toJourneyRef from Cache + List toServiceJourneyActiveDates +) { + public static class Builder { + + private final String validationReportId; + private final NetexDataRepository netexDataRepository; + private Map> serviceJourneyIdListMap; + private Map> serviceJourneyDayTypesMap; + private Map activeDatesMap; + private Map> serviceJourneyOperatingDaysMap; + + public Builder( + String validationReportId, + NetexDataRepository netexDataRepository + ) { + this.validationReportId = validationReportId; + this.netexDataRepository = netexDataRepository; + } + + public UnexpectedWaitTimeAndActiveDatesContext.Builder primeCache() { + serviceJourneyIdListMap = + netexDataRepository.serviceJourneyStops(validationReportId); + serviceJourneyDayTypesMap = + netexDataRepository.serviceJourneyDayTypes(validationReportId); + activeDatesMap = netexDataRepository.activeDates(validationReportId); + serviceJourneyOperatingDaysMap = + netexDataRepository.serviceJourneyOperatingDays(validationReportId); + return this; + } + + public UnexpectedWaitTimeAndActiveDatesContext build( + ServiceJourneyInterchangeInfo serviceJourneyInterchangeInfo + ) { + return new UnexpectedWaitTimeAndActiveDatesContext( + serviceJourneyInterchangeInfo, + serviceJourneyStopAtScheduleStopPoint( + serviceJourneyInterchangeInfo.fromJourneyRef(), + serviceJourneyInterchangeInfo.fromStopPoint() + ), + serviceJourneyStopAtScheduleStopPoint( + serviceJourneyInterchangeInfo.toJourneyRef(), + serviceJourneyInterchangeInfo.toStopPoint() + ), + activeDatesForServiceJourney( + serviceJourneyInterchangeInfo.fromJourneyRef() + ), + activeDatesForServiceJourney( + serviceJourneyInterchangeInfo.toJourneyRef() + ) + ); + } + + private List activeDatesForServiceJourney( + ServiceJourneyId serviceJourneyId + ) { + List activeDateOfDayTypes = Optional + .ofNullable(serviceJourneyId) + .map(serviceJourneyDayTypesMap::get) + .map(dayTypeIds -> + dayTypeIds + .stream() + .map(activeDatesMap::get) + .map(ActiveDates::dates) + .flatMap(List::stream) + .toList() + ) + .orElse(List.of()); + + List activeDateOfDatedServiceJourneys = Optional + .ofNullable(serviceJourneyId) + .map(serviceJourneyOperatingDaysMap::get) + .map(dayTypeIds -> + dayTypeIds + .stream() + .map(activeDatesMap::get) + .map(ActiveDates::dates) + .flatMap(List::stream) + .toList() + ) + .orElse(List.of()); + + return Stream + .of(activeDateOfDayTypes, activeDateOfDatedServiceJourneys) + .flatMap(List::stream) + .toList(); + } + + private ServiceJourneyStop serviceJourneyStopAtScheduleStopPoint( + ServiceJourneyId serviceJourneyId, + ScheduledStopPointId scheduledStopPointId + ) { + return Optional + .ofNullable(serviceJourneyId) + .map(serviceJourneyIdListMap::get) + .flatMap(serviceJourneyStops -> + serviceJourneyStops + .stream() + .filter(serviceJourneyStop -> + serviceJourneyStop + .scheduledStopPointId() + .equals(scheduledStopPointId) + ) + .map(ServiceJourneyStop::fixMissingTimeValues) + .findFirst() + ) + .orElse(null); + } + } + + public boolean isValid() { + return ( + serviceJourneyInterchangeInfo != null && + serviceJourneyInterchangeInfo.isValid() && + fromServiceJourneyStop != null && + toServiceJourneyStop != null + ); + } +} diff --git a/src/main/java/no/entur/antu/validation/validator/interchange/waittime/UnexpectedWaitTimeAndActiveDatesValidator.java b/src/main/java/no/entur/antu/validation/validator/interchange/waittime/UnexpectedWaitTimeAndActiveDatesValidator.java new file mode 100644 index 00000000..8cf7253a --- /dev/null +++ b/src/main/java/no/entur/antu/validation/validator/interchange/waittime/UnexpectedWaitTimeAndActiveDatesValidator.java @@ -0,0 +1,228 @@ +package no.entur.antu.validation.validator.interchange.waittime; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import no.entur.antu.validation.utilities.Comparison; +import org.entur.netex.validation.validator.AbstractDatasetValidator; +import org.entur.netex.validation.validator.DataLocation; +import org.entur.netex.validation.validator.Severity; +import org.entur.netex.validation.validator.ValidationIssue; +import org.entur.netex.validation.validator.ValidationReport; +import org.entur.netex.validation.validator.ValidationReportEntry; +import org.entur.netex.validation.validator.ValidationReportEntryFactory; +import org.entur.netex.validation.validator.ValidationRule; +import org.entur.netex.validation.validator.jaxb.NetexDataRepository; +import org.entur.netex.validation.validator.model.ServiceJourneyInterchangeInfo; +import org.entur.netex.validation.validator.model.ServiceJourneyStop; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Verify that wait time is not above configured threshold. + * Must check that the two vehicle journeys share at least one active date, + * or in the case of interchanges around midnight; consecutive dates. + * Chouette reference: + * 3-Interchange-8-1, + * 3-Interchange-8-2, + * 3-Interchange-10 + */ +public class UnexpectedWaitTimeAndActiveDatesValidator + extends AbstractDatasetValidator { + + static final ValidationRule RULE_NO_SHARED_ACTIVE_DATE_FOUND_IN_INTERCHANGE = + new ValidationRule( + "NO_SHARED_ACTIVE_DATE_FOUND_IN_INTERCHANGE", + "No shared active date found in interchange", + "No shared active date found in interchange between %s and %s", + Severity.WARNING + ); + + static final ValidationRule RULE_WAIT_TIME_IN_INTERCHANGE_EXCEEDS_WARNING_LIMIT = + new ValidationRule( + "WAIT_TIME_IN_INTERCHANGE_EXCEEDS_WARNING_LIMIT", + "Wait time in interchange exceeds warning limit", + "Wait time between stops (%s) and (%s) is expected %s sec. but was %s sec.", + Severity.WARNING + ); + + static final ValidationRule RULE_WAIT_TIME_IN_INTERCHANGE_EXCEEDS_MAX_LIMIT = + new ValidationRule( + "WAIT_TIME_IN_INTERCHANGE_EXCEEDS_MAX_LIMIT", + "Wait time in interchange exceeds maximum limit", + "Wait time between stops (%s) and (%s) is expected %s sec. but was %s sec.", + Severity.WARNING + ); + + private static final Logger LOGGER = LoggerFactory.getLogger( + UnexpectedWaitTimeAndActiveDatesValidator.class + ); + + // Marduk/Chouette config parameter: interchange_max_wait_seconds = 3600 Seconds + + // Warning wait time for interchange is 1 hour + private static final int INTERCHANGE_WARNING_WAIT_TIME_MILLIS = 3600000; // 1 Hour + + // Maximum wait time for interchange is 3 hours + private static final int INTERCHANGE_ERROR_WAIT_TIME_MILLIS = + INTERCHANGE_WARNING_WAIT_TIME_MILLIS * 3; // 3 Hours + + private final NetexDataRepository netexDataRepository; + + public UnexpectedWaitTimeAndActiveDatesValidator( + ValidationReportEntryFactory validationReportEntryFactory, + NetexDataRepository netexDataRepository + ) { + super(validationReportEntryFactory); + this.netexDataRepository = netexDataRepository; + } + + @Override + public ValidationReport validate(ValidationReport validationReport) { + LOGGER.info("Validating interchange wait time."); + + List serviceJourneyInterchangeInfos = + netexDataRepository.serviceJourneyInterchangeInfos( + validationReport.getValidationReportId() + ); + + if ( + serviceJourneyInterchangeInfos == null || + serviceJourneyInterchangeInfos.isEmpty() + ) { + return validationReport; + } + + UnexpectedWaitTimeAndActiveDatesContext.Builder builder = + new UnexpectedWaitTimeAndActiveDatesContext.Builder( + validationReport.getValidationReportId(), + netexDataRepository + ); + + builder.primeCache(); + + serviceJourneyInterchangeInfos + .stream() + .map(builder::build) + .filter(Objects::nonNull) + .filter(UnexpectedWaitTimeAndActiveDatesContext::isValid) + .map(this::validateWaitTime) + .filter(Objects::nonNull) + .forEach(validationReport::addValidationReportEntry); + + return validationReport; + } + + private ValidationReportEntry validateWaitTime( + UnexpectedWaitTimeAndActiveDatesContext context + ) { + long millisPerDay = 86400000L; + + int dayOffsetDiff = + context.toServiceJourneyStop().departureDayOffset() - + context.fromServiceJourneyStop().arrivalDayOffset(); + + long msWait = + ( + Optional + .ofNullable(context.toServiceJourneyStop().departureTime()) + .map(LocalTime::toSecondOfDay) + .orElse(0) - + Optional + .ofNullable(context.fromServiceJourneyStop().arrivalTime()) + .map(LocalTime::toSecondOfDay) + .orElse(0) + ) * + 1000L; + + if (msWait < 0) { + msWait = millisPerDay + msWait; + dayOffsetDiff--; + } + + if (!hasSharedActiveDate(context, dayOffsetDiff)) { + return createValidationReportEntry( + new ValidationIssue( + RULE_NO_SHARED_ACTIVE_DATE_FOUND_IN_INTERCHANGE, + new DataLocation( + context.serviceJourneyInterchangeInfo().interchangeId(), + context.serviceJourneyInterchangeInfo().filename(), + 0, + 0 + ), + context.fromServiceJourneyStop(), + context.toServiceJourneyStop() + ) + ); + } else if (msWait > INTERCHANGE_WARNING_WAIT_TIME_MILLIS) { + if (msWait > INTERCHANGE_ERROR_WAIT_TIME_MILLIS) { + return createValidationReportEntry( + RULE_WAIT_TIME_IN_INTERCHANGE_EXCEEDS_MAX_LIMIT, + context.serviceJourneyInterchangeInfo().interchangeId(), + context.serviceJourneyInterchangeInfo().filename(), + context.fromServiceJourneyStop(), + context.toServiceJourneyStop(), + Comparison.of( + String.valueOf(msWait / 1000), + String.valueOf(INTERCHANGE_ERROR_WAIT_TIME_MILLIS / 1000) + ) + ); + } else { + return createValidationReportEntry( + RULE_WAIT_TIME_IN_INTERCHANGE_EXCEEDS_WARNING_LIMIT, + context.serviceJourneyInterchangeInfo().interchangeId(), + context.serviceJourneyInterchangeInfo().filename(), + context.fromServiceJourneyStop(), + context.toServiceJourneyStop(), + Comparison.of( + String.valueOf(msWait / 1000), + String.valueOf(INTERCHANGE_WARNING_WAIT_TIME_MILLIS / 1000) + ) + ); + } + } + return null; + } + + private boolean hasSharedActiveDate( + UnexpectedWaitTimeAndActiveDatesContext context, + int daysOffset + ) { + List fromServiceJourneyActiveDates = + context.fromServiceJourneyActiveDates(); + for (LocalDate toServiceJourneyActiveDate : context.toServiceJourneyActiveDates()) { + LocalDate toServiceJourneyActiveDateWithOffset = + toServiceJourneyActiveDate.plusDays(daysOffset); + if ( + fromServiceJourneyActiveDates.contains( + toServiceJourneyActiveDateWithOffset + ) + ) { + return true; + } + } + return false; + } + + private ValidationReportEntry createValidationReportEntry( + ValidationRule rule, + String interchangeId, + String filename, + ServiceJourneyStop fromJourneyStop, + ServiceJourneyStop toJourneyStop, + Comparison comparison + ) { + return createValidationReportEntry( + new ValidationIssue( + rule, + new DataLocation(interchangeId, filename, 0, 0), + fromJourneyStop.scheduledStopPointId(), + toJourneyStop.scheduledStopPointId(), + comparison.expected(), + comparison.actual() + ) + ); + } +} diff --git a/src/main/java/no/entur/antu/validation/validator/support/NetexUtils.java b/src/main/java/no/entur/antu/validation/validator/support/NetexUtils.java index 4d92e9bd..de9c3def 100644 --- a/src/main/java/no/entur/antu/validation/validator/support/NetexUtils.java +++ b/src/main/java/no/entur/antu/validation/validator/support/NetexUtils.java @@ -6,10 +6,12 @@ import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.entur.netex.validation.validator.jaxb.JAXBValidationContext; import org.entur.netex.validation.validator.model.ScheduledStopPointId; import org.rutebanken.netex.model.JourneyPattern; import org.rutebanken.netex.model.PointInLinkSequence_VersionedChildStructure; import org.rutebanken.netex.model.PointsInJourneyPattern_RelStructure; +import org.rutebanken.netex.model.ServiceJourney; import org.rutebanken.netex.model.StopPointInJourneyPattern; import org.rutebanken.netex.model.TimetabledPassingTime; @@ -88,4 +90,30 @@ public static StopPointInJourneyPattern stopPointInJourneyPattern( .findFirst() .orElse(null); } + + /** + * Returns the Stream of all the valid ServiceJourneys in all the TimeTableFrames. + * The valid serviceJourneys are those that have number of timetabledPassingTime equals to number of StopPointsInJourneyPattern. + * This is validated with SERVICE_JOURNEY_10. + */ + public static List validServiceJourneys( + JAXBValidationContext validationContext + ) { + return validationContext + .serviceJourneys() + .stream() + .filter(serviceJourney -> { + JourneyPattern journeyPattern = validationContext.journeyPattern( + serviceJourney + ); + if (journeyPattern == null) { + return false; + } + return ( + stopPointsInJourneyPattern(journeyPattern).size() == + validationContext.timetabledPassingTimes(serviceJourney).size() + ); + }) + .toList(); + } } diff --git a/src/main/java/no/entur/antu/validation/validator/xpath/rules/ValidateAllowedCodespaces.java b/src/main/java/no/entur/antu/validation/validator/xpath/rules/ValidateAllowedCodespaces.java index 499bc8a3..4f18e22b 100644 --- a/src/main/java/no/entur/antu/validation/validator/xpath/rules/ValidateAllowedCodespaces.java +++ b/src/main/java/no/entur/antu/validation/validator/xpath/rules/ValidateAllowedCodespaces.java @@ -1,6 +1,6 @@ package no.entur.antu.validation.validator.xpath.rules; -import static org.entur.netex.validation.xml.NetexXMLParser.*; +import static org.entur.netex.validation.xml.NetexXMLParser.NETEX_NAMESPACE; import java.util.ArrayList; import java.util.List; diff --git a/src/main/resources/configuration.antu.yaml b/src/main/resources/configuration.antu.yaml index cae0e0aa..173861b7 100644 --- a/src/main/resources/configuration.antu.yaml +++ b/src/main/resources/configuration.antu.yaml @@ -129,3 +129,12 @@ validationRuleConfigs: - code: TO_POINT_REF_IN_INTERCHANGE_IS_NOT_PART_OF_TO_JOURNEY_REF name: ToPointRef in interchange is not a part of ToJourneyRef severity: WARNING + - code: NO_SHARED_ACTIVE_DATE_FOUND_IN_INTERCHANGE + name: No shared active date found in interchange + severity: WARNING + - code: WAIT_TIME_IN_INTERCHANGE_EXCEEDS_WARNING_LIMIT + name: Wait time in interchange exceeds warning limit + severity: WARNING + - code: WAIT_TIME_IN_INTERCHANGE_EXCEEDS_MAX_LIMIT + name: Wait time in interchange exceeds maximum limit + severity: WARNING diff --git a/src/test/java/no/entur/antu/netexdata/collectors/DatedServiceJourneysCollectorTest.java b/src/test/java/no/entur/antu/netexdata/collectors/DatedServiceJourneysCollectorTest.java new file mode 100644 index 00000000..166a208c --- /dev/null +++ b/src/test/java/no/entur/antu/netexdata/collectors/DatedServiceJourneysCollectorTest.java @@ -0,0 +1,117 @@ +package no.entur.antu.netexdata.collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.time.LocalDate; +import java.util.Map; +import java.util.stream.IntStream; +import no.entur.antu.netextestdata.NetexEntitiesTestFactory; +import org.entur.netex.index.api.NetexEntitiesIndex; +import org.entur.netex.validation.validator.jaxb.JAXBValidationContext; +import org.junit.jupiter.api.Test; + +class DatedServiceJourneysCollectorTest { + + @Test + void testOneDatedServiceJourneyPerServiceJourney() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + IntStream + .rangeClosed(1, 3) + .forEach(i -> + netexEntitiesTestFactory.createDatedServiceJourney( + i, + netexEntitiesTestFactory.createServiceJourney( + i, + netexEntitiesTestFactory.createJourneyPattern(i) + ), + netexEntitiesTestFactory + .createServiceCalendarFrame() + .createOperatingDay(i, LocalDate.of(2024, 1, 1)) + ) + ); + + Map operatingDaysPerServiceJourney = + DatedServiceJourneysCollector.getOperatingDaysPerServiceJourneyIsAsStrings( + createContext(netexEntitiesTestFactory.create()) + ); + + assertEquals(3, operatingDaysPerServiceJourney.size()); + } + + @Test + void testMultipleDatedServiceJourneysPerServiceJourney() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceJourney serviceJourney1 = + netexEntitiesTestFactory.createServiceJourney( + 1, + netexEntitiesTestFactory.createJourneyPattern(1) + ); + IntStream + .rangeClosed(1, 3) + .forEach(i -> + netexEntitiesTestFactory.createDatedServiceJourney( + i, + serviceJourney1, + netexEntitiesTestFactory + .createServiceCalendarFrame() + .createOperatingDay(i, LocalDate.of(2024, 1, 1 + i)) + ) + ); + + NetexEntitiesTestFactory.CreateServiceJourney serviceJourney2 = + netexEntitiesTestFactory.createServiceJourney( + 2, + netexEntitiesTestFactory.createJourneyPattern(2) + ); + IntStream + .rangeClosed(4, 5) + .forEach(i -> + netexEntitiesTestFactory.createDatedServiceJourney( + i, + serviceJourney2, + netexEntitiesTestFactory + .createServiceCalendarFrame() + .createOperatingDay(i, LocalDate.of(2024, 2, 1 + i)) + ) + ); + + Map operatingDaysPerServiceJourney = + DatedServiceJourneysCollector.getOperatingDaysPerServiceJourneyIsAsStrings( + createContext(netexEntitiesTestFactory.create()) + ); + + assertEquals(2, operatingDaysPerServiceJourney.size()); + assertEquals( + 3, + operatingDaysPerServiceJourney + .get(serviceJourney1.ref()) + .split(",") + .length + ); + assertEquals( + 2, + operatingDaysPerServiceJourney + .get(serviceJourney2.ref()) + .split(",") + .length + ); + } + + private static JAXBValidationContext createContext( + NetexEntitiesIndex netexEntitiesIndex + ) { + return new JAXBValidationContext( + "test123", + netexEntitiesIndex, + null, + null, + "TST", + "fileSchemaVersion", + null + ); + } +} diff --git a/src/test/java/no/entur/antu/netexdata/collectors/ServiceJourneyDayTypesCollectorTest.java b/src/test/java/no/entur/antu/netexdata/collectors/ServiceJourneyDayTypesCollectorTest.java new file mode 100644 index 00000000..f2ff7e1e --- /dev/null +++ b/src/test/java/no/entur/antu/netexdata/collectors/ServiceJourneyDayTypesCollectorTest.java @@ -0,0 +1,102 @@ +package no.entur.antu.netexdata.collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import java.util.Map; +import java.util.stream.IntStream; +import no.entur.antu.netextestdata.NetexEntitiesTestFactory; +import org.entur.netex.index.api.NetexEntitiesIndex; +import org.entur.netex.validation.validator.jaxb.JAXBValidationContext; +import org.junit.jupiter.api.Test; +import org.rutebanken.netex.model.DayOfWeekEnumeration; + +class ServiceJourneyDayTypesCollectorTest { + + @Test + void testValidServiceJourneysWithDayTypes() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + NetexEntitiesTestFactory.CreateJourneyPattern journeyPattern = + netexEntitiesTestFactory.createJourneyPattern(); + List stopPointsInJourneyPattern = + journeyPattern.createStopPointsInJourneyPattern(4); + + List serviceJourneys = + netexEntitiesTestFactory.createServiceJourneys(journeyPattern, 4); + + serviceJourneys.forEach(serviceJourney -> { + serviceJourney.createTimetabledPassingTimes(stopPointsInJourneyPattern); + serviceJourney.addDayTypeRefs( + netexEntitiesTestFactory + .createServiceCalendarFrame() + .createDayTypes( + 4, + DayOfWeekEnumeration.MONDAY, + DayOfWeekEnumeration.TUESDAY + ) + ); + }); + + Map dayTypesPerServiceJourneyId = + ServiceJourneyDayTypesCollector.getDayTypesPerServiceJourneyIdAsStrings( + createContext(netexEntitiesTestFactory.create()) + ); + + assertEquals(4, dayTypesPerServiceJourneyId.size()); + dayTypesPerServiceJourneyId.forEach((serviceJourneyId, dayTypes) -> + assertEquals(4, dayTypes.split(",").length) + ); + } + + @Test + void testInValidServiceJourneysFilteredOut() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + NetexEntitiesTestFactory.CreateJourneyPattern journeyPattern = + netexEntitiesTestFactory.createJourneyPattern(); + List stopPointsInJourneyPattern = + journeyPattern.createStopPointsInJourneyPattern(4); + + List serviceJourneys = + netexEntitiesTestFactory.createServiceJourneys(journeyPattern, 4); + + IntStream + .range(0, serviceJourneys.size() - 1) + .filter(i -> i % 2 == 0) + .mapToObj(serviceJourneys::get) + .forEach(serviceJourney -> { + serviceJourney.createTimetabledPassingTimes(stopPointsInJourneyPattern); + serviceJourney.addDayTypeRefs( + netexEntitiesTestFactory + .createServiceCalendarFrame() + .createDayTypes( + 4, + DayOfWeekEnumeration.MONDAY, + DayOfWeekEnumeration.TUESDAY + ) + ); + }); + + Map dayTypesPerServiceJourneyId = + ServiceJourneyDayTypesCollector.getDayTypesPerServiceJourneyIdAsStrings( + createContext(netexEntitiesTestFactory.create()) + ); + + assertEquals(2, dayTypesPerServiceJourneyId.size()); + } + + private static JAXBValidationContext createContext( + NetexEntitiesIndex netexEntitiesIndex + ) { + return new JAXBValidationContext( + "test123", + netexEntitiesIndex, + null, + null, + "TST", + "fileSchemaVersion", + null + ); + } +} diff --git a/src/test/java/no/entur/antu/netexdata/collectors/activedatecollector/ActiveDatesCollectorTest.java b/src/test/java/no/entur/antu/netexdata/collectors/activedatecollector/ActiveDatesCollectorTest.java new file mode 100644 index 00000000..c163c8a7 --- /dev/null +++ b/src/test/java/no/entur/antu/netexdata/collectors/activedatecollector/ActiveDatesCollectorTest.java @@ -0,0 +1,286 @@ +package no.entur.antu.netexdata.collectors.activedatecollector; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.Month; +import java.util.List; +import java.util.Map; +import no.entur.antu.netexdata.collectors.activedatecollector.calender.ServiceCalendarFrameObject; +import no.entur.antu.netextestdata.NetexEntitiesTestFactory; +import org.entur.netex.index.api.NetexEntitiesIndex; +import org.entur.netex.validation.validator.jaxb.JAXBValidationContext; +import org.junit.jupiter.api.Test; +import org.rutebanken.netex.model.DayOfWeekEnumeration; + +class ActiveDatesCollectorTest { + + @Test + void testParseServiceCalendarFrameInCompositeFrame() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + // Create a composite frame with a service calendar frame + netexEntitiesTestFactory + .createCompositeFrame() + .createServiceCalendarFrame(); + + List serviceCalendarFrameObjects = + ActiveDatesCollector.parseServiceCalendarFrame( + createContext(netexEntitiesTestFactory.create()) + ); + + assertEquals(1, serviceCalendarFrameObjects.size()); + } + + @Test + void testParseServiceCalendarFrame() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + // Create a service calendar frame + netexEntitiesTestFactory.createServiceCalendarFrame(); + + List serviceCalendarFrameObjects = + ActiveDatesCollector.parseServiceCalendarFrame( + createContext(netexEntitiesTestFactory.create()) + ); + + assertEquals(1, serviceCalendarFrameObjects.size()); + } + + @Test + void testParseMultipleServiceCalendarFrames() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + // Create a service calendar frame + netexEntitiesTestFactory.createServiceCalendarFrame(1); + netexEntitiesTestFactory.createServiceCalendarFrame(2); + netexEntitiesTestFactory.createServiceCalendarFrame(3); + + List serviceCalendarFrameObjects = + ActiveDatesCollector.parseServiceCalendarFrame( + createContext(netexEntitiesTestFactory.create()) + ); + + assertEquals(3, serviceCalendarFrameObjects.size()); + } + + @Test + void testParseMultipleServiceCalendarFramesInCompositeFrame() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateCompositeFrame compositeFrame = + netexEntitiesTestFactory.createCompositeFrame(); + + // Create a service calendar frame + compositeFrame.createServiceCalendarFrame(1); + compositeFrame.createServiceCalendarFrame(2); + compositeFrame.createServiceCalendarFrame(3); + + List serviceCalendarFrameObjects = + ActiveDatesCollector.parseServiceCalendarFrame( + createContext(netexEntitiesTestFactory.create()) + ); + + assertEquals(3, serviceCalendarFrameObjects.size()); + } + + /** + * Test that a service calendar frame in a composite frame is returned + * when there are both a service calendar frame outside composite frame also exists. + * TODO: or do we need to parse both and combine the result? + */ + @Test + void testParseServiceCalendarFrameInCompositeFrameIsPrioritized() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + // Create a composite frame with a service calendar frame + netexEntitiesTestFactory + .createCompositeFrame() + .createServiceCalendarFrame(1) + .createValidBetween( + LocalDateTime.of(2024, 11, 1, 0, 0), + LocalDateTime.of(2024, 11, 2, 0, 0) + ); + + // Create a service calendar frame + netexEntitiesTestFactory + .createServiceCalendarFrame(2) + .createValidBetween( + LocalDateTime.of(2024, 12, 1, 0, 0), + LocalDateTime.of(2024, 12, 2, 0, 0) + ); + + List serviceCalendarFrameObjects = + ActiveDatesCollector.parseServiceCalendarFrame( + createContext(netexEntitiesTestFactory.create()) + ); + + assertEquals(1, serviceCalendarFrameObjects.size()); + assertEquals( + Month.NOVEMBER, + serviceCalendarFrameObjects.get(0).validBetween().getFromDate().getMonth() + ); + } + + @Test + void testActiveDatesForDayTypeAssignmentsAndOperatingDays() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + + // Validity on service calendar frame + serviceCalendarFrame.createValidBetween( + LocalDateTime.of(2024, 11, 20, 0, 0), + LocalDateTime.of(2024, 11, 30, 0, 0) + ); + + NetexEntitiesTestFactory.CreateDayType dayType1 = serviceCalendarFrame + .createDayType(1) + .withDaysOfWeek( + DayOfWeekEnumeration.MONDAY, + DayOfWeekEnumeration.WEDNESDAY, + DayOfWeekEnumeration.FRIDAY + ); + + NetexEntitiesTestFactory.CreateDayType dayType2 = + serviceCalendarFrame.createDayType(2); + NetexEntitiesTestFactory.CreateDayType dayType3 = + serviceCalendarFrame.createDayType(3); + + serviceCalendarFrame + .createDayTypeAssignment(1, dayType1) + .withOperatingPeriodRef( + serviceCalendarFrame.createOperatingPeriod( + LocalDate.of(2024, 11, 25), + LocalDate.of(2024, 11, 30) + ) + ); + + serviceCalendarFrame + .createDayTypeAssignment(2, dayType2) + .withDate(LocalDate.of(2024, 11, 19)); + + NetexEntitiesTestFactory.CreateOperatingDay operatingDay = + serviceCalendarFrame.createOperatingDay(LocalDate.of(2024, 11, 23)); + serviceCalendarFrame + .createDayTypeAssignment(3, dayType3) + .withOperatingDayRef(operatingDay); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + Map activeDatesPerId = + ActiveDatesCollector.getActiveDatesPerIdAsMapOfStrings( + List.of(serviceCalendarFrameObject) + ); + + assertEquals( + "2024-11-25,2024-11-27,2024-11-29", + activeDatesPerId.get(dayType1.ref()) + ); + + assertEquals("2024-11-23", activeDatesPerId.get(operatingDay.ref())); + + assertEquals("2024-11-23", activeDatesPerId.get(dayType3.ref())); + } + + @Test + void testActiveDatesForOperatingDaysOnly() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + + // Validity on service calendar frame + serviceCalendarFrame.createValidBetween( + LocalDateTime.of(2024, 11, 20, 0, 0), + LocalDateTime.of(2024, 11, 30, 0, 0) + ); + + List operatingDays = + serviceCalendarFrame.createOperatingDays(3, LocalDate.of(2024, 11, 25)); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + Map activeDatesPerId = + ActiveDatesCollector.getActiveDatesPerIdAsMapOfStrings( + List.of(serviceCalendarFrameObject) + ); + + assertEquals(3, activeDatesPerId.size()); + assertEquals( + "2024-11-25", + activeDatesPerId.get(operatingDays.get(0).ref()) + ); + + assertEquals( + "2024-11-26", + activeDatesPerId.get(operatingDays.get(1).ref()) + ); + + assertEquals( + "2024-11-27", + activeDatesPerId.get(operatingDays.get(2).ref()) + ); + } + + @Test + void testActiveDatesWithoutAnyValidityProvided() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + + List operatingDays = + serviceCalendarFrame.createOperatingDays(3, LocalDate.of(2024, 11, 25)); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + Map activeDatesPerId = + ActiveDatesCollector.getActiveDatesPerIdAsMapOfStrings( + List.of(serviceCalendarFrameObject) + ); + + assertEquals(3, activeDatesPerId.size()); + assertEquals( + "2024-11-25", + activeDatesPerId.get(operatingDays.get(0).ref()) + ); + + assertEquals( + "2024-11-26", + activeDatesPerId.get(operatingDays.get(1).ref()) + ); + + assertEquals( + "2024-11-27", + activeDatesPerId.get(operatingDays.get(2).ref()) + ); + } + + private static JAXBValidationContext createContext( + NetexEntitiesIndex netexEntitiesIndex + ) { + return new JAXBValidationContext( + "test123", + netexEntitiesIndex, + null, + null, + "TST", + "fileSchemaVersion", + null + ); + } +} diff --git a/src/test/java/no/entur/antu/netexdata/collectors/activedatecollector/calender/ActiveDatesBuilderTest.java b/src/test/java/no/entur/antu/netexdata/collectors/activedatecollector/calender/ActiveDatesBuilderTest.java new file mode 100644 index 00000000..f01ef461 --- /dev/null +++ b/src/test/java/no/entur/antu/netexdata/collectors/activedatecollector/calender/ActiveDatesBuilderTest.java @@ -0,0 +1,1362 @@ +package no.entur.antu.netexdata.collectors.activedatecollector.calender; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import no.entur.antu.netextestdata.NetexEntitiesTestFactory; +import org.entur.netex.validation.validator.model.ActiveDates; +import org.entur.netex.validation.validator.model.DayTypeId; +import org.entur.netex.validation.validator.model.OperatingDayId; +import org.junit.jupiter.api.Test; +import org.rutebanken.netex.model.DayOfWeekEnumeration; + +class ActiveDatesBuilderTest { + + @Test + void testActiveDatesForDayTypeAssignmentsWithDates() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + + // Validity on service calendar frame + serviceCalendarFrame.createValidBetween( + LocalDateTime.of(2024, 11, 20, 0, 0), + LocalDateTime.of(2024, 11, 22, 0, 0) + ); + + List dayTypes = + serviceCalendarFrame.createDayTypes(3); + + serviceCalendarFrame.createDayTypeAssignmentsWithDates( + dayTypes, + LocalDate.of(2024, 11, 21) + ); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + ActiveDatesBuilder activeDatesBuilder = new ActiveDatesBuilder(); + + Map dayTypeIdActiveDatesMap = + activeDatesBuilder.buildPerDayType(serviceCalendarFrameObject); + + // Date outside the validity of the service calendar frame is not included in the active dates + assertEquals( + 2, + dayTypeIdActiveDatesMap + .values() + .stream() + .filter(ActiveDates::isValid) + .count() + ); + } + + @Test + void testActiveDatesForDayTypeAssignmentsWithOperatingDays() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + + // Validity on service calendar frame + serviceCalendarFrame.createValidBetween( + LocalDateTime.of(2024, 11, 20, 0, 0), + LocalDateTime.of(2024, 11, 22, 0, 0) + ); + + List dayTypes = + serviceCalendarFrame.createDayTypes(3); + + List operatingDays = + serviceCalendarFrame.createOperatingDays(3, LocalDate.of(2024, 11, 21)); + + serviceCalendarFrame.createDayTypeAssignmentsWithOperatingDays( + dayTypes, + operatingDays + ); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + ActiveDatesBuilder activeDatesBuilder = new ActiveDatesBuilder(); + + Map dayTypeIdActiveDatesMap = + activeDatesBuilder.buildPerDayType(serviceCalendarFrameObject); + + // Dates outside the validity of the service calendar frame is not included in the active dates + assertEquals( + 2, + dayTypeIdActiveDatesMap + .values() + .stream() + .filter(ActiveDates::isValid) + .count() + ); + } + + @Test + void testOperatingDaysAndDatesAreNotEffectedByDayTypes() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + + // Validity on service calendar frame + serviceCalendarFrame.createValidBetween( + LocalDateTime.of(2024, 11, 20, 0, 0), + LocalDateTime.of(2024, 11, 25, 0, 0) + ); + + NetexEntitiesTestFactory.CreateDayType dayType1 = serviceCalendarFrame + .createDayType(1) + .withDaysOfWeek(DayOfWeekEnumeration.THURSDAY); + + NetexEntitiesTestFactory.CreateDayType dayType2 = serviceCalendarFrame + .createDayType(2) + .withDaysOfWeek(DayOfWeekEnumeration.FRIDAY); + + serviceCalendarFrame + .createDayTypeAssignment(2, dayType1) + .withDate( + LocalDate.of(2024, 11, 21) // Thursday + ); + + serviceCalendarFrame + .createDayTypeAssignment(3, dayType2) + .withOperatingDayRef( + serviceCalendarFrame.createOperatingDay(LocalDate.of(2024, 11, 22)) // Friday + ); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + ActiveDatesBuilder activeDatesBuilder = new ActiveDatesBuilder(); + + Map dayTypeIdActiveDatesMap = + activeDatesBuilder.buildPerDayType(serviceCalendarFrameObject); + + // Date outside the validity of the service calendar frame is not included in the active dates + assertEquals( + "2024-11-21", + dayTypeIdActiveDatesMap.get(new DayTypeId(dayType1.ref())).toString() + ); + assertEquals( + "2024-11-22", + dayTypeIdActiveDatesMap.get(new DayTypeId(dayType2.ref())).toString() + ); + } + + @Test + void testActiveDatesForDayTypeAssignmentsWithDatesDaysAndPeriods() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + + // Validity on service calendar frame + serviceCalendarFrame.createValidBetween( + LocalDateTime.of(2024, 11, 20, 0, 0), + LocalDateTime.of(2024, 11, 30, 0, 0) + ); + + NetexEntitiesTestFactory.CreateDayType dayType1 = serviceCalendarFrame + .createDayType(1) + .withDaysOfWeek( + DayOfWeekEnumeration.MONDAY, + DayOfWeekEnumeration.WEDNESDAY, + DayOfWeekEnumeration.FRIDAY + ); + + NetexEntitiesTestFactory.CreateDayType dayType2 = + serviceCalendarFrame.createDayType(2); + NetexEntitiesTestFactory.CreateDayType dayType3 = + serviceCalendarFrame.createDayType(3); + + serviceCalendarFrame + .createDayTypeAssignment(1, dayType1) + .withOperatingPeriodRef( + serviceCalendarFrame.createOperatingPeriod( + LocalDate.of(2024, 11, 25), + LocalDate.of(2024, 11, 30) + ) + ); + + serviceCalendarFrame + .createDayTypeAssignment(2, dayType2) + .withDate(LocalDate.of(2024, 11, 19)); + + serviceCalendarFrame + .createDayTypeAssignment(3, dayType3) + .withOperatingDayRef( + serviceCalendarFrame.createOperatingDay(LocalDate.of(2024, 11, 23)) + ); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + ActiveDatesBuilder activeDatesBuilder = new ActiveDatesBuilder(); + + Map dayTypeIdActiveDatesMap = + activeDatesBuilder.buildPerDayType(serviceCalendarFrameObject); + + // Date outside the validity of the service calendar frame is not included in the active dates + assertEquals( + "2024-11-25,2024-11-27,2024-11-29", + dayTypeIdActiveDatesMap.get(new DayTypeId(dayType1.ref())).toString() + ); + assertEquals( + "", + dayTypeIdActiveDatesMap.get(new DayTypeId(dayType2.ref())).toString() + ); + assertEquals( + "2024-11-23", + dayTypeIdActiveDatesMap.get(new DayTypeId(dayType3.ref())).toString() + ); + } + + @Test + void testActiveDatesForDayTypeAssignmentsWithOperatingPeriodsOutsideValidPeriod() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + + // Validity on service calendar frame + serviceCalendarFrame.createValidBetween( + LocalDateTime.of(2024, 11, 23, 0, 0), + LocalDateTime.of(2024, 12, 3, 0, 0) + ); + + List dayTypes = + serviceCalendarFrame.createDayTypes(3, DayOfWeekEnumeration.EVERYDAY); + + /* + TST:DayType:1 = 2024-11-20,2024-11-21,2024-11-22,2024-11-23,2024-11-24,2024-11-25 + TST:DayType:2 = 2024-11-25,2024-11-26,2024-11-27,2024-11-28,2024-11-29,2024-11-30 + TST:DayType:3 = 2024-11-30,2024-12-01,2024-12-02,2024-12-03,2024-12-04,2024-12-05 + */ + List operatingPeriods = + serviceCalendarFrame.createOperatingPeriods( + 3, + LocalDate.of(2024, 11, 20), + LocalDate.of(2024, 11, 25), + 5 + ); + + serviceCalendarFrame.createDayTypeAssignmentsWithOperatingPeriods( + dayTypes, + operatingPeriods + ); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + ActiveDatesBuilder activeDatesBuilder = new ActiveDatesBuilder(); + + Map dayTypeIdActiveDatesMap = + activeDatesBuilder.buildPerDayType(serviceCalendarFrameObject); + + // Date outside the validity of the service calendar frame is not included in the active dates + assertEquals( + "2024-11-23,2024-11-24,2024-11-25", + dayTypeIdActiveDatesMap + .get(new DayTypeId(dayTypes.get(0).ref())) + .toString() + ); + assertEquals( + "2024-11-25,2024-11-26,2024-11-27,2024-11-28,2024-11-29,2024-11-30", + dayTypeIdActiveDatesMap + .get(new DayTypeId(dayTypes.get(1).ref())) + .toString() + ); + assertEquals( + "2024-11-30,2024-12-01,2024-12-02,2024-12-03", + dayTypeIdActiveDatesMap + .get(new DayTypeId(dayTypes.get(2).ref())) + .toString() + ); + } + + @Test + void testActiveDatesForDayTypeAssignmentsWithOperatingPeriodsEveryDay() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + + // Validity on service calendar frame + serviceCalendarFrame.createValidBetween( + LocalDateTime.of(2024, 11, 20, 0, 0), + LocalDateTime.of(2024, 12, 5, 0, 0) + ); + + List dayTypes = + serviceCalendarFrame.createDayTypes(3, DayOfWeekEnumeration.EVERYDAY); + + /* + TST:DayType:1 = 2024-11-20,2024-11-21,2024-11-22,2024-11-23,2024-11-24,2024-11-25 + TST:DayType:2 = 2024-11-25,2024-11-26,2024-11-27,2024-11-28,2024-11-29,2024-11-30 + TST:DayType:3 = 2024-11-30,2024-12-01,2024-12-02,2024-12-03,2024-12-04,2024-12-05 + */ + List operatingPeriods = + serviceCalendarFrame.createOperatingPeriods( + 3, + LocalDate.of(2024, 11, 20), + LocalDate.of(2024, 11, 25), + 5 + ); + + serviceCalendarFrame.createDayTypeAssignmentsWithOperatingPeriods( + dayTypes, + operatingPeriods + ); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + ActiveDatesBuilder activeDatesBuilder = new ActiveDatesBuilder(); + + Map dayTypeIdActiveDatesMap = + activeDatesBuilder.buildPerDayType(serviceCalendarFrameObject); + + // No Dates are outside the validity of the service calendar frame and + // DayTypes are set to 'Every Day' so all the DayTypeAssignments, so all the days are active. + assertEquals( + "2024-11-20,2024-11-21,2024-11-22,2024-11-23,2024-11-24,2024-11-25", + dayTypeIdActiveDatesMap + .get(new DayTypeId(dayTypes.get(0).ref())) + .toString() + ); + assertEquals( + "2024-11-25,2024-11-26,2024-11-27,2024-11-28,2024-11-29,2024-11-30", + dayTypeIdActiveDatesMap + .get(new DayTypeId(dayTypes.get(1).ref())) + .toString() + ); + assertEquals( + "2024-11-30,2024-12-01,2024-12-02,2024-12-03,2024-12-04,2024-12-05", + dayTypeIdActiveDatesMap + .get(new DayTypeId(dayTypes.get(2).ref())) + .toString() + ); + } + + @Test + void testActiveDatesForDayTypeAssignmentsWithOperatingPeriodsOnWeekDays() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + + // Validity on service calendar frame + serviceCalendarFrame.createValidBetween( + LocalDateTime.of(2024, 11, 20, 0, 0), + LocalDateTime.of(2024, 12, 5, 0, 0) + ); + + List dayTypes = + serviceCalendarFrame.createDayTypes(3, DayOfWeekEnumeration.WEEKDAYS); + + /* + TST:DayType:1 = 2024-11-20,2024-11-21,2024-11-22,2024-11-23,2024-11-24,2024-11-25 + TST:DayType:2 = 2024-11-25,2024-11-26,2024-11-27,2024-11-28,2024-11-29,2024-11-30 + TST:DayType:3 = 2024-11-30,2024-12-01,2024-12-02,2024-12-03,2024-12-04,2024-12-05 + */ + List operatingPeriods = + serviceCalendarFrame.createOperatingPeriods( + 3, + LocalDate.of(2024, 11, 20), + LocalDate.of(2024, 11, 25), + 5 + ); + + serviceCalendarFrame.createDayTypeAssignmentsWithOperatingPeriods( + dayTypes, + operatingPeriods + ); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + ActiveDatesBuilder activeDatesBuilder = new ActiveDatesBuilder(); + + Map dayTypeIdActiveDatesMap = + activeDatesBuilder.buildPerDayType(serviceCalendarFrameObject); + + // No Dates are outside the validity of the service calendar frame and + // DayTypes are set to 'Week days' so the dates on week ends are filtered out. + assertEquals( + "2024-11-20,2024-11-21,2024-11-22,2024-11-25", + dayTypeIdActiveDatesMap + .get(new DayTypeId(dayTypes.get(0).ref())) + .toString() + ); + assertEquals( + "2024-11-25,2024-11-26,2024-11-27,2024-11-28,2024-11-29", + dayTypeIdActiveDatesMap + .get(new DayTypeId(dayTypes.get(1).ref())) + .toString() + ); + assertEquals( + "2024-12-02,2024-12-03,2024-12-04,2024-12-05", + dayTypeIdActiveDatesMap + .get(new DayTypeId(dayTypes.get(2).ref())) + .toString() + ); + } + + @Test + void testActiveDatesForDayTypeAssignmentsWithOperatingPeriodsOnWeekends() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + + // Validity on service calendar frame + serviceCalendarFrame.createValidBetween( + LocalDateTime.of(2024, 11, 20, 0, 0), + LocalDateTime.of(2024, 12, 5, 0, 0) + ); + + List dayTypes = + serviceCalendarFrame.createDayTypes(3, DayOfWeekEnumeration.WEEKEND); + + /* + TST:DayType:1 = 2024-11-20,2024-11-21,2024-11-22,2024-11-23,2024-11-24,2024-11-25 + TST:DayType:2 = 2024-11-25,2024-11-26,2024-11-27,2024-11-28,2024-11-29,2024-11-30 + TST:DayType:3 = 2024-11-30,2024-12-01,2024-12-02,2024-12-03,2024-12-04,2024-12-05 + */ + List operatingPeriods = + serviceCalendarFrame.createOperatingPeriods( + 3, + LocalDate.of(2024, 11, 20), + LocalDate.of(2024, 11, 25), + 5 + ); + + serviceCalendarFrame.createDayTypeAssignmentsWithOperatingPeriods( + dayTypes, + operatingPeriods + ); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + ActiveDatesBuilder activeDatesBuilder = new ActiveDatesBuilder(); + + Map dayTypeIdActiveDatesMap = + activeDatesBuilder.buildPerDayType(serviceCalendarFrameObject); + + // No Dates are outside the validity of the service calendar frame and + // DayTypes are set to 'Week ends' so the dates on week days are filtered out. + assertEquals( + "2024-11-23,2024-11-24", + dayTypeIdActiveDatesMap + .get(new DayTypeId(dayTypes.get(0).ref())) + .toString() + ); + assertEquals( + "2024-11-30", + dayTypeIdActiveDatesMap + .get(new DayTypeId(dayTypes.get(1).ref())) + .toString() + ); + assertEquals( + "2024-11-30,2024-12-01", + dayTypeIdActiveDatesMap + .get(new DayTypeId(dayTypes.get(2).ref())) + .toString() + ); + } + + @Test + void testActiveDatesForDayTypeAssignmentsWithOperatingPeriodsWithSelectedDates() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + + // Validity on service calendar frame + serviceCalendarFrame.createValidBetween( + LocalDateTime.of(2024, 11, 20, 0, 0), + LocalDateTime.of(2024, 12, 5, 0, 0) + ); + + List dayTypes = + serviceCalendarFrame.createDayTypes( + 3, + DayOfWeekEnumeration.MONDAY, + DayOfWeekEnumeration.WEDNESDAY, + DayOfWeekEnumeration.FRIDAY + ); + + /* + TST:DayType:1 = 2024-11-20,2024-11-21,2024-11-22,2024-11-23,2024-11-24,2024-11-25 + TST:DayType:2 = 2024-11-25,2024-11-26,2024-11-27,2024-11-28,2024-11-29,2024-11-30 + TST:DayType:3 = 2024-11-30,2024-12-01,2024-12-02,2024-12-03,2024-12-04,2024-12-05 + */ + List operatingPeriods = + serviceCalendarFrame.createOperatingPeriods( + 3, + LocalDate.of(2024, 11, 20), + LocalDate.of(2024, 11, 25), + 5 + ); + + serviceCalendarFrame.createDayTypeAssignmentsWithOperatingPeriods( + dayTypes, + operatingPeriods + ); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + ActiveDatesBuilder activeDatesBuilder = new ActiveDatesBuilder(); + + Map dayTypeIdActiveDatesMap = + activeDatesBuilder.buildPerDayType(serviceCalendarFrameObject); + + // No Dates are outside the validity of the service calendar frame and + // Only the dates on Monday, Wednesday and Friday are included in the active dates, as per day types. + assertEquals( + "2024-11-20,2024-11-22,2024-11-25", + dayTypeIdActiveDatesMap + .get(new DayTypeId(dayTypes.get(0).ref())) + .toString() + ); + assertEquals( + "2024-11-25,2024-11-27,2024-11-29", + dayTypeIdActiveDatesMap + .get(new DayTypeId(dayTypes.get(1).ref())) + .toString() + ); + assertEquals( + "2024-12-02,2024-12-04", + dayTypeIdActiveDatesMap + .get(new DayTypeId(dayTypes.get(2).ref())) + .toString() + ); + } + + @Test + void testActiveDatesForDayTypeAssignmentsInServiceCalendar() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + + // Creating Service Calendar inside Service Calendar Frame + NetexEntitiesTestFactory.CreateServiceCalendar serviceCalendar = + serviceCalendarFrame.createServiceCalendar(); + + // Validity on service calendar + serviceCalendar.createValidBetween( + LocalDateTime.of(2024, 11, 20, 0, 0), + LocalDateTime.of(2024, 11, 30, 0, 0) + ); + + NetexEntitiesTestFactory.CreateDayType dayType1 = serviceCalendar + .createDayType(1) + .withDaysOfWeek( + DayOfWeekEnumeration.MONDAY, + DayOfWeekEnumeration.WEDNESDAY, + DayOfWeekEnumeration.FRIDAY + ); + + NetexEntitiesTestFactory.CreateDayType dayType2 = + serviceCalendar.createDayType(2); + NetexEntitiesTestFactory.CreateDayType dayType3 = + serviceCalendar.createDayType(3); + + serviceCalendar + .createDayTypeAssignment(1, dayType1) + .withOperatingPeriodRef( + serviceCalendar.createOperatingPeriod( + LocalDate.of(2024, 11, 25), + LocalDate.of(2024, 11, 30) + ) + ); + + serviceCalendar + .createDayTypeAssignment(2, dayType2) + .withDate(LocalDate.of(2024, 11, 19)); + + serviceCalendar + .createDayTypeAssignment(3, dayType3) + .withOperatingDayRef( + serviceCalendar.createOperatingDay(LocalDate.of(2024, 11, 23)) + ); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + ActiveDatesBuilder activeDatesBuilder = new ActiveDatesBuilder(); + + Map dayTypeIdActiveDatesMap = + activeDatesBuilder.buildPerDayType(serviceCalendarFrameObject); + + // Date outside the validity of the service calendar frame is not included in the active dates + assertEquals( + "2024-11-25,2024-11-27,2024-11-29", + dayTypeIdActiveDatesMap.get(new DayTypeId(dayType1.ref())).toString() + ); + assertEquals( + "", + dayTypeIdActiveDatesMap.get(new DayTypeId(dayType2.ref())).toString() + ); + assertEquals( + "2024-11-23", + dayTypeIdActiveDatesMap.get(new DayTypeId(dayType3.ref())).toString() + ); + } + + @Test + void testActiveDatesFromBothServiceCalendarFrameAndServiceCalendar() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + + // Validity on service calendar frame + serviceCalendarFrame.createValidBetween( + LocalDateTime.of(2024, 11, 20, 0, 0), + LocalDateTime.of(2024, 11, 30, 0, 0) + ); + + // Creating Service Calendar inside Service Calendar Frame + NetexEntitiesTestFactory.CreateServiceCalendar serviceCalendar = + serviceCalendarFrame.createServiceCalendar(); + + // Creating data in Service Calendar Frame + NetexEntitiesTestFactory.CreateDayType dayType1 = serviceCalendarFrame + .createDayType(1) + .withDaysOfWeek( + DayOfWeekEnumeration.MONDAY, + DayOfWeekEnumeration.WEDNESDAY, + DayOfWeekEnumeration.FRIDAY + ); + + serviceCalendarFrame + .createDayTypeAssignment(1, dayType1) + .withOperatingPeriodRef( + serviceCalendarFrame.createOperatingPeriod( + LocalDate.of(2024, 11, 25), + LocalDate.of(2024, 11, 30) + ) + ); + + // Creating data in service calendar + NetexEntitiesTestFactory.CreateDayType dayType2 = + serviceCalendar.createDayType(2); + NetexEntitiesTestFactory.CreateDayType dayType3 = + serviceCalendar.createDayType(3); + + serviceCalendar + .createDayTypeAssignment(2, dayType2) + .withDate(LocalDate.of(2024, 11, 21)); + + serviceCalendar + .createDayTypeAssignment(3, dayType3) + .withOperatingDayRef( + serviceCalendar.createOperatingDay(LocalDate.of(2024, 11, 23)) + ); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + ActiveDatesBuilder activeDatesBuilder = new ActiveDatesBuilder(); + + Map dayTypeIdActiveDatesMap = + activeDatesBuilder.buildPerDayType(serviceCalendarFrameObject); + + // Date outside the validity of the service calendar frame is not included in the active dates + assertEquals( + "2024-11-25,2024-11-27,2024-11-29", + dayTypeIdActiveDatesMap.get(new DayTypeId(dayType1.ref())).toString() + ); + assertEquals( + "2024-11-21", + dayTypeIdActiveDatesMap.get(new DayTypeId(dayType2.ref())).toString() + ); + assertEquals( + "2024-11-23", + dayTypeIdActiveDatesMap.get(new DayTypeId(dayType3.ref())).toString() + ); + } + + @Test + void testActiveDatesWithBuildPerOperationDays() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + + // Validity on service calendar frame + serviceCalendarFrame.createValidBetween( + LocalDateTime.of(2024, 11, 20, 0, 0), + LocalDateTime.of(2024, 11, 22, 0, 0) + ); + + // Creating 3 Operating days: 2024-11-21, 2024-11-22, 2024-11-23 + List operatingDays = + serviceCalendarFrame.createOperatingDays(3, LocalDate.of(2024, 11, 21)); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + ActiveDatesBuilder activeDatesBuilder = new ActiveDatesBuilder(); + + // Build per operating days + Map dayTypeIdActiveDatesMap = + activeDatesBuilder.buildPerOperatingDay(serviceCalendarFrameObject); + + assertEquals( + "2024-11-21", + dayTypeIdActiveDatesMap + .get(new OperatingDayId(operatingDays.get(0).ref())) + .toString() + ); + assertEquals( + "2024-11-22", + dayTypeIdActiveDatesMap + .get(new OperatingDayId(operatingDays.get(1).ref())) + .toString() + ); + // the third operating day is outside the validity of the service calendar frame + assertNull( + dayTypeIdActiveDatesMap.get( + new OperatingDayId(operatingDays.get(2).ref()) + ) + ); + } + + /** + * When no validity is set, all the dates should be valid. + */ + @Test + void testActiveDatesForDayTypeAssignmentsWithDatesWithNoValidity() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + + List dayTypes = + serviceCalendarFrame.createDayTypes(3); + + serviceCalendarFrame.createDayTypeAssignmentsWithDates( + dayTypes, + LocalDate.of(2024, 11, 21) + ); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + ActiveDatesBuilder activeDatesBuilder = new ActiveDatesBuilder(); + + Map dayTypeIdActiveDatesMap = + activeDatesBuilder.buildPerDayType(serviceCalendarFrameObject); + + assertEquals( + 3, + dayTypeIdActiveDatesMap + .values() + .stream() + .filter(ActiveDates::isValid) + .count() + ); + } + + /** + * Validity is set with no from and to dates, all the dates should be valid. + */ + @Test + void testActiveDatesForDayTypeAssignmentsWithDatesWithEmptyValidity() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + + serviceCalendarFrame.withValidBetween( + new NetexEntitiesTestFactory.CreateValidBetween(1) + ); + + List dayTypes = + serviceCalendarFrame.createDayTypes(3); + + serviceCalendarFrame.createDayTypeAssignmentsWithDates( + dayTypes, + LocalDate.of(2024, 11, 21) + ); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + ActiveDatesBuilder activeDatesBuilder = new ActiveDatesBuilder(); + + Map dayTypeIdActiveDatesMap = + activeDatesBuilder.buildPerDayType(serviceCalendarFrameObject); + + assertEquals( + 3, + dayTypeIdActiveDatesMap + .values() + .stream() + .filter(ActiveDates::isValid) + .count() + ); + } + + /** + * When no validity is set, all the operating days should be valid. + */ + @Test + void testActiveDatesForDayTypeAssignmentsWithOperatingDaysWithNoValidity() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + + List dayTypes = + serviceCalendarFrame.createDayTypes(3); + + List operatingDays = + serviceCalendarFrame.createOperatingDays(3, LocalDate.of(2024, 11, 21)); + + serviceCalendarFrame.createDayTypeAssignmentsWithOperatingDays( + dayTypes, + operatingDays + ); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + ActiveDatesBuilder activeDatesBuilder = new ActiveDatesBuilder(); + + Map dayTypeIdActiveDatesMap = + activeDatesBuilder.buildPerDayType(serviceCalendarFrameObject); + + // Dates outside the validity of the service calendar frame is not included in the active dates + assertEquals( + 3, + dayTypeIdActiveDatesMap + .values() + .stream() + .filter(ActiveDates::isValid) + .count() + ); + } + + /** + * Validity is set with no from and to dates, all the operating days should be valid. + */ + @Test + void testActiveDatesForDayTypeAssignmentsWithOperatingDaysWithEmptyValidity() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + + serviceCalendarFrame.withValidBetween( + new NetexEntitiesTestFactory.CreateValidBetween(1) + ); + + List dayTypes = + serviceCalendarFrame.createDayTypes(3); + + List operatingDays = + serviceCalendarFrame.createOperatingDays(3, LocalDate.of(2024, 11, 21)); + + serviceCalendarFrame.createDayTypeAssignmentsWithOperatingDays( + dayTypes, + operatingDays + ); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + ActiveDatesBuilder activeDatesBuilder = new ActiveDatesBuilder(); + + Map dayTypeIdActiveDatesMap = + activeDatesBuilder.buildPerDayType(serviceCalendarFrameObject); + + // Dates outside the validity of the service calendar frame is not included in the active dates + assertEquals( + 3, + dayTypeIdActiveDatesMap + .values() + .stream() + .filter(ActiveDates::isValid) + .count() + ); + } + + /** + * When no validity is set, all the dates in operating period should be valid. + */ + @Test + void testActiveDatesForDayTypeAssignmentsWithOperatingPeriodsWithNoValidity() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + + List dayTypes = + serviceCalendarFrame.createDayTypes(3, DayOfWeekEnumeration.EVERYDAY); + + /* + TST:DayType:1 = 2024-11-20,2024-11-21,2024-11-22,2024-11-23,2024-11-24,2024-11-25 + TST:DayType:2 = 2024-11-25,2024-11-26,2024-11-27,2024-11-28,2024-11-29,2024-11-30 + TST:DayType:3 = 2024-11-30,2024-12-01,2024-12-02,2024-12-03,2024-12-04,2024-12-05 + */ + List operatingPeriods = + serviceCalendarFrame.createOperatingPeriods( + 3, + LocalDate.of(2024, 11, 20), + LocalDate.of(2024, 11, 25), + 5 + ); + + serviceCalendarFrame.createDayTypeAssignmentsWithOperatingPeriods( + dayTypes, + operatingPeriods + ); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + ActiveDatesBuilder activeDatesBuilder = new ActiveDatesBuilder(); + + Map dayTypeIdActiveDatesMap = + activeDatesBuilder.buildPerDayType(serviceCalendarFrameObject); + + // Date outside the validity of the service calendar frame is not included in the active dates + assertEquals( + "2024-11-20,2024-11-21,2024-11-22,2024-11-23,2024-11-24,2024-11-25", + dayTypeIdActiveDatesMap + .get(new DayTypeId(dayTypes.get(0).ref())) + .toString() + ); + assertEquals( + "2024-11-25,2024-11-26,2024-11-27,2024-11-28,2024-11-29,2024-11-30", + dayTypeIdActiveDatesMap + .get(new DayTypeId(dayTypes.get(1).ref())) + .toString() + ); + assertEquals( + "2024-11-30,2024-12-01,2024-12-02,2024-12-03,2024-12-04,2024-12-05", + dayTypeIdActiveDatesMap + .get(new DayTypeId(dayTypes.get(2).ref())) + .toString() + ); + } + + /** + * Validity is set with no from and to dates, all the dates in operating period should be valid. + */ + @Test + void testActiveDatesForDayTypeAssignmentsWithOperatingPeriodsWithEmptyValidity() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + + serviceCalendarFrame.withValidBetween( + new NetexEntitiesTestFactory.CreateValidBetween(1) + ); + + List dayTypes = + serviceCalendarFrame.createDayTypes(3, DayOfWeekEnumeration.EVERYDAY); + + /* + TST:DayType:1 = 2024-11-20,2024-11-21,2024-11-22,2024-11-23,2024-11-24,2024-11-25 + TST:DayType:2 = 2024-11-25,2024-11-26,2024-11-27,2024-11-28,2024-11-29,2024-11-30 + TST:DayType:3 = 2024-11-30,2024-12-01,2024-12-02,2024-12-03,2024-12-04,2024-12-05 + */ + List operatingPeriods = + serviceCalendarFrame.createOperatingPeriods( + 3, + LocalDate.of(2024, 11, 20), + LocalDate.of(2024, 11, 25), + 5 + ); + + serviceCalendarFrame.createDayTypeAssignmentsWithOperatingPeriods( + dayTypes, + operatingPeriods + ); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + ActiveDatesBuilder activeDatesBuilder = new ActiveDatesBuilder(); + + Map dayTypeIdActiveDatesMap = + activeDatesBuilder.buildPerDayType(serviceCalendarFrameObject); + + // Date outside the validity of the service calendar frame is not included in the active dates + assertEquals( + "2024-11-20,2024-11-21,2024-11-22,2024-11-23,2024-11-24,2024-11-25", + dayTypeIdActiveDatesMap + .get(new DayTypeId(dayTypes.get(0).ref())) + .toString() + ); + assertEquals( + "2024-11-25,2024-11-26,2024-11-27,2024-11-28,2024-11-29,2024-11-30", + dayTypeIdActiveDatesMap + .get(new DayTypeId(dayTypes.get(1).ref())) + .toString() + ); + assertEquals( + "2024-11-30,2024-12-01,2024-12-02,2024-12-03,2024-12-04,2024-12-05", + dayTypeIdActiveDatesMap + .get(new DayTypeId(dayTypes.get(2).ref())) + .toString() + ); + } + + /** + * When the validity is set only with from date, + * all the dates from the from date should be valid. + * From date is inclusive. + */ + @Test + void testActiveDatesForDayTypeAssignmentsWithDatesWithOnlyFromDateValidity() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + + serviceCalendarFrame.withValidBetween( + new NetexEntitiesTestFactory.CreateValidBetween(1) + .withFromDate(LocalDateTime.of(2024, 11, 22, 0, 0)) + ); + + List dayTypes = + serviceCalendarFrame.createDayTypes(3); + + serviceCalendarFrame.createDayTypeAssignmentsWithDates( + dayTypes, + LocalDate.of(2024, 11, 21) + ); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + ActiveDatesBuilder activeDatesBuilder = new ActiveDatesBuilder(); + + Map dayTypeIdActiveDatesMap = + activeDatesBuilder.buildPerDayType(serviceCalendarFrameObject); + + assertEquals( + 2, + dayTypeIdActiveDatesMap + .values() + .stream() + .filter(ActiveDates::isValid) + .count() + ); + } + + /** + * When the validity is set only with to date, + * all the dates till the to date should be valid. + * To date is exclusive. + * TODO: Should it be inclusive or exclusive? In Chouette it is exclusive. + */ + @Test + void testActiveDatesForDayTypeAssignmentsWithDatesWithOnlyToDateValidity() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + + serviceCalendarFrame.withValidBetween( + new NetexEntitiesTestFactory.CreateValidBetween(1) + .withToDate(LocalDateTime.of(2024, 11, 22, 0, 0)) + ); + + List dayTypes = + serviceCalendarFrame.createDayTypes(3); + + serviceCalendarFrame.createDayTypeAssignmentsWithDates( + dayTypes, + LocalDate.of(2024, 11, 21) + ); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + ActiveDatesBuilder activeDatesBuilder = new ActiveDatesBuilder(); + + Map dayTypeIdActiveDatesMap = + activeDatesBuilder.buildPerDayType(serviceCalendarFrameObject); + + assertEquals( + 1, + dayTypeIdActiveDatesMap + .values() + .stream() + .filter(ActiveDates::isValid) + .count() + ); + } + + /** + * When the validity is set only with from date, + * all the operating days from the from date should be valid. + * From date is inclusive. + */ + @Test + void testActiveDatesForDayTypeAssignmentsWithOperatingDaysWithFromDateValidity() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + + serviceCalendarFrame.withValidBetween( + new NetexEntitiesTestFactory.CreateValidBetween(1) + .withFromDate(LocalDateTime.of(2024, 11, 22, 0, 0)) + ); + + List dayTypes = + serviceCalendarFrame.createDayTypes(3); + + List operatingDays = + serviceCalendarFrame.createOperatingDays(3, LocalDate.of(2024, 11, 21)); + + serviceCalendarFrame.createDayTypeAssignmentsWithOperatingDays( + dayTypes, + operatingDays + ); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + ActiveDatesBuilder activeDatesBuilder = new ActiveDatesBuilder(); + + Map dayTypeIdActiveDatesMap = + activeDatesBuilder.buildPerDayType(serviceCalendarFrameObject); + + // Dates outside the validity of the service calendar frame is not included in the active dates + assertEquals( + 2, + dayTypeIdActiveDatesMap + .values() + .stream() + .filter(ActiveDates::isValid) + .count() + ); + } + + /** + * When the validity is set only with to date, + * all the operating days till the to date should be valid. + * To date is exclusive. + * TODO: Should it be inclusive or exclusive? In Chouette it is exclusive. + */ + @Test + void testActiveDatesForDayTypeAssignmentsWithOperatingDaysWithToDateValidity() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + + serviceCalendarFrame.withValidBetween( + new NetexEntitiesTestFactory.CreateValidBetween(1) + .withToDate(LocalDateTime.of(2024, 11, 22, 0, 0)) + ); + + List dayTypes = + serviceCalendarFrame.createDayTypes(3); + + List operatingDays = + serviceCalendarFrame.createOperatingDays(3, LocalDate.of(2024, 11, 21)); + + serviceCalendarFrame.createDayTypeAssignmentsWithOperatingDays( + dayTypes, + operatingDays + ); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + ActiveDatesBuilder activeDatesBuilder = new ActiveDatesBuilder(); + + Map dayTypeIdActiveDatesMap = + activeDatesBuilder.buildPerDayType(serviceCalendarFrameObject); + + // Dates outside the validity of the service calendar frame is not included in the active dates + assertEquals( + 1, + dayTypeIdActiveDatesMap + .values() + .stream() + .filter(ActiveDates::isValid) + .count() + ); + } + + /** + * When the validity is set only with from date, + * all the dates in operating period from the from date should be valid. + * From date is inclusive. + */ + @Test + void testActiveDatesForDayTypeAssignmentsWithOperatingPeriodsWithFromDateValidity() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + + serviceCalendarFrame.withValidBetween( + new NetexEntitiesTestFactory.CreateValidBetween(1) + .withFromDate(LocalDateTime.of(2024, 11, 22, 0, 0)) + ); + + List dayTypes = + serviceCalendarFrame.createDayTypes(3, DayOfWeekEnumeration.EVERYDAY); + + /* + TST:DayType:1 = 2024-11-20,2024-11-21,2024-11-22,2024-11-23,2024-11-24,2024-11-25 + TST:DayType:2 = 2024-11-25,2024-11-26,2024-11-27,2024-11-28,2024-11-29,2024-11-30 + TST:DayType:3 = 2024-11-30,2024-12-01,2024-12-02,2024-12-03,2024-12-04,2024-12-05 + */ + List operatingPeriods = + serviceCalendarFrame.createOperatingPeriods( + 3, + LocalDate.of(2024, 11, 20), + LocalDate.of(2024, 11, 25), + 5 + ); + + serviceCalendarFrame.createDayTypeAssignmentsWithOperatingPeriods( + dayTypes, + operatingPeriods + ); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + ActiveDatesBuilder activeDatesBuilder = new ActiveDatesBuilder(); + + Map dayTypeIdActiveDatesMap = + activeDatesBuilder.buildPerDayType(serviceCalendarFrameObject); + + // Date outside the validity of the service calendar frame is not included in the active dates + assertEquals( + "2024-11-22,2024-11-23,2024-11-24,2024-11-25", + dayTypeIdActiveDatesMap + .get(new DayTypeId(dayTypes.get(0).ref())) + .toString() + ); + assertEquals( + "2024-11-25,2024-11-26,2024-11-27,2024-11-28,2024-11-29,2024-11-30", + dayTypeIdActiveDatesMap + .get(new DayTypeId(dayTypes.get(1).ref())) + .toString() + ); + assertEquals( + "2024-11-30,2024-12-01,2024-12-02,2024-12-03,2024-12-04,2024-12-05", + dayTypeIdActiveDatesMap + .get(new DayTypeId(dayTypes.get(2).ref())) + .toString() + ); + } + + /** + * When the validity is set only with to date, + * all the dates in operating period till the to date should be valid. + * To date is inclusive. + * TODO: Should it be inclusive or exclusive? In Chouette it is inclusive. + */ + @Test + void testActiveDatesForDayTypeAssignmentsWithOperatingPeriodsWithToDateValidity() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + + serviceCalendarFrame.withValidBetween( + new NetexEntitiesTestFactory.CreateValidBetween(1) + .withToDate(LocalDateTime.of(2024, 11, 27, 0, 0)) + ); + + List dayTypes = + serviceCalendarFrame.createDayTypes(3, DayOfWeekEnumeration.EVERYDAY); + + /* + TST:DayType:1 = 2024-11-20,2024-11-21,2024-11-22,2024-11-23,2024-11-24,2024-11-25 + TST:DayType:2 = 2024-11-25,2024-11-26,2024-11-27,2024-11-28,2024-11-29,2024-11-30 + TST:DayType:3 = 2024-11-30,2024-12-01,2024-12-02,2024-12-03,2024-12-04,2024-12-05 + */ + List operatingPeriods = + serviceCalendarFrame.createOperatingPeriods( + 3, + LocalDate.of(2024, 11, 20), + LocalDate.of(2024, 11, 25), + 5 + ); + + serviceCalendarFrame.createDayTypeAssignmentsWithOperatingPeriods( + dayTypes, + operatingPeriods + ); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + ActiveDatesBuilder activeDatesBuilder = new ActiveDatesBuilder(); + + Map dayTypeIdActiveDatesMap = + activeDatesBuilder.buildPerDayType(serviceCalendarFrameObject); + + // Date outside the validity of the service calendar frame is not included in the active dates + assertEquals( + "2024-11-20,2024-11-21,2024-11-22,2024-11-23,2024-11-24,2024-11-25", + dayTypeIdActiveDatesMap + .get(new DayTypeId(dayTypes.get(0).ref())) + .toString() + ); + assertEquals( + "2024-11-25,2024-11-26,2024-11-27", + dayTypeIdActiveDatesMap + .get(new DayTypeId(dayTypes.get(1).ref())) + .toString() + ); + assertEquals( + "", + dayTypeIdActiveDatesMap + .get(new DayTypeId(dayTypes.get(2).ref())) + .toString() + ); + } +} diff --git a/src/test/java/no/entur/antu/netexdata/collectors/activedatecollector/calender/CalendarUtilitiesTest.java b/src/test/java/no/entur/antu/netexdata/collectors/activedatecollector/calender/CalendarUtilitiesTest.java new file mode 100644 index 00000000..a384464c --- /dev/null +++ b/src/test/java/no/entur/antu/netexdata/collectors/activedatecollector/calender/CalendarUtilitiesTest.java @@ -0,0 +1,136 @@ +package no.entur.antu.netexdata.collectors.activedatecollector.calender; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import com.google.common.collect.Multimap; +import java.time.LocalDateTime; +import java.util.List; +import java.util.function.Function; +import no.entur.antu.netextestdata.NetexEntitiesTestFactory; +import org.entur.netex.validation.validator.model.DayTypeId; +import org.junit.jupiter.api.Test; +import org.rutebanken.netex.model.Common_VersionFrameStructure; +import org.rutebanken.netex.model.DayTypeAssignment; +import org.rutebanken.netex.model.ValidBetween; +import org.rutebanken.netex.model.ValidityConditions_RelStructure; + +class CalendarUtilitiesTest { + + @Test + void testToMultimap() { + // Arrange + NetexEntitiesTestFactory.CreateDayType dayType1 = + new NetexEntitiesTestFactory.CreateDayType(1); + DayTypeAssignment assignment1 = + new NetexEntitiesTestFactory.CreateDayTypeAssignment(1, dayType1) + .create(); + NetexEntitiesTestFactory.CreateDayType dayType2 = + new NetexEntitiesTestFactory.CreateDayType(2); + DayTypeAssignment assignment2 = + new NetexEntitiesTestFactory.CreateDayTypeAssignment(2, dayType2) + .create(); + NetexEntitiesTestFactory.CreateDayType dayType3 = + new NetexEntitiesTestFactory.CreateDayType(3); + DayTypeAssignment assignment3 = + new NetexEntitiesTestFactory.CreateDayTypeAssignment(1, dayType3) + .create(); + + List assignments = List.of( + assignment1, + assignment2, + assignment3 + ); + + Function keyMapper = DayTypeId::of; + Function valueMapper = + Function.identity(); + + // Act + Multimap result = assignments + .stream() + .collect(CalendarUtilities.toMultimap(keyMapper, valueMapper)); + + // Assert + assertTrue(result.get(new DayTypeId(dayType1.ref())).contains(assignment1)); + assertTrue(result.get(new DayTypeId(dayType2.ref())).contains(assignment2)); + assertTrue(result.get(new DayTypeId(dayType3.ref())).contains(assignment3)); + } + + @Test + void testGetOrDefault() { + assertEquals("default", CalendarUtilities.getOrDefault(null, "default")); + assertEquals("value", CalendarUtilities.getOrDefault("value", "default")); + } + + @Test + void testIsWithinValidRange() { + ValidBetween validBetween = new ValidBetween() + .withFromDate(LocalDateTime.of(2022, 1, 1, 0, 0)) + .withToDate(LocalDateTime.of(2022, 12, 31, 23, 59)); + + LocalDateTime withinRange = LocalDateTime.of(2022, 6, 15, 12, 0); + LocalDateTime beforeRange = LocalDateTime.of(2021, 12, 31, 23, 59); + LocalDateTime afterRange = LocalDateTime.of(2023, 1, 1, 0, 0); + + assertTrue(CalendarUtilities.isWithinValidRange(withinRange, validBetween)); + assertFalse( + CalendarUtilities.isWithinValidRange(beforeRange, validBetween) + ); + assertFalse(CalendarUtilities.isWithinValidRange(afterRange, validBetween)); + } + + @Test + void testGetValidityForFrameOrDefault() { + Common_VersionFrameStructure frameStructure = mock( + Common_VersionFrameStructure.class + ); + + ValidBetween defaultValidity = new ValidBetween() + .withFromDate(LocalDateTime.of(2022, 1, 1, 0, 0)) + .withToDate(LocalDateTime.of(2022, 12, 31, 23, 59)); + + when(frameStructure.getValidBetween()).thenReturn(null); + when(frameStructure.getContentValidityConditions()).thenReturn(null); + when(frameStructure.getValidityConditions()).thenReturn(null); + + ValidBetween result = CalendarUtilities.getValidityForFrameOrDefault( + frameStructure, + defaultValidity + ); + + assertEquals(defaultValidity, result); + } + + @Test + void testGetValidBetween_withEmptyStructure() { + ValidityConditions_RelStructure structure = mock( + ValidityConditions_RelStructure.class + ); + when(structure.getValidityConditionRefOrValidBetweenOrValidityCondition_()) + .thenReturn(List.of()); + + ValidBetween result = CalendarUtilities.getValidBetween(structure); + + assertNull(result); + } + + @Test + void testGetValidBetween_withValidCondition() { + ValidityConditions_RelStructure structure = mock( + ValidityConditions_RelStructure.class + ); + ValidBetween validBetween = new ValidBetween() + .withFromDate(LocalDateTime.of(2023, 1, 1, 0, 0)) + .withToDate(LocalDateTime.of(2023, 12, 31, 23, 59)); + + when(structure.getValidityConditionRefOrValidBetweenOrValidityCondition_()) + .thenReturn(List.of(validBetween)); + + ValidBetween result = CalendarUtilities.getValidBetween(structure); + + assertNotNull(result); + assertEquals(LocalDateTime.of(2023, 1, 1, 0, 0), result.getFromDate()); + assertEquals(LocalDateTime.of(2023, 12, 31, 23, 59), result.getToDate()); + } +} diff --git a/src/test/java/no/entur/antu/netexdata/collectors/activedatecollector/calender/ServiceCalendarFrameObjectTest.java b/src/test/java/no/entur/antu/netexdata/collectors/activedatecollector/calender/ServiceCalendarFrameObjectTest.java new file mode 100644 index 00000000..c9e733ea --- /dev/null +++ b/src/test/java/no/entur/antu/netexdata/collectors/activedatecollector/calender/ServiceCalendarFrameObjectTest.java @@ -0,0 +1,627 @@ +package no.entur.antu.netexdata.collectors.activedatecollector.calender; + +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.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import no.entur.antu.netextestdata.NetexEntitiesTestFactory; +import org.junit.jupiter.api.Test; +import org.rutebanken.netex.model.DayOfWeekEnumeration; + +class ServiceCalendarFrameObjectTest { + + /** + * Test Validity is set on service calendar inside Service calendar frame. + */ + @Test + void testValidityOnServiceCalendar() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + + NetexEntitiesTestFactory.CreateServiceCalendar serviceCalendar = + serviceCalendarFrame.createServiceCalendar(); + LocalDateTime fromDateTime = LocalDateTime.of(2024, 11, 20, 0, 0); + LocalDateTime toDateTime = LocalDateTime.of(2024, 11, 22, 0, 0); + + // Validity on service calendar frame + serviceCalendar.createValidBetween(fromDateTime, toDateTime); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + assertEquals( + fromDateTime, + serviceCalendarFrameObject.serviceCalendar().validBetween().getFromDate() + ); + assertEquals( + toDateTime, + serviceCalendarFrameObject.serviceCalendar().validBetween().getToDate() + ); + } + + /** + * Test Validity is set on service calendar frame. + */ + @Test + void testValidityOnServiceCalendarFrame() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + LocalDateTime fromDateTime = LocalDateTime.of(2024, 11, 20, 0, 0); + LocalDateTime toDateTime = LocalDateTime.of(2024, 11, 22, 0, 0); + + // Validity on service calendar frame + serviceCalendarFrame.createValidBetween(fromDateTime, toDateTime); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + assertNull(serviceCalendarFrameObject.serviceCalendar()); + assertEquals( + fromDateTime, + serviceCalendarFrameObject.validBetween().getFromDate() + ); + assertEquals( + toDateTime, + serviceCalendarFrameObject.validBetween().getToDate() + ); + } + + /** + * Validity is set on service calendar frame only. + * Service calendar uses validity from service calendar frame. + */ + @Test + void testServiceCalendarUsesValidityFromServiceCalendarFrame() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + + // Validity on service calendar frame + LocalDateTime fromDateTimeServiceCalendarFrame = LocalDateTime.of( + 2024, + 11, + 20, + 0, + 0 + ); + LocalDateTime toDateTimeServiceCalendarFrame = LocalDateTime.of( + 2024, + 11, + 22, + 0, + 0 + ); + serviceCalendarFrame.createValidBetween( + fromDateTimeServiceCalendarFrame, + toDateTimeServiceCalendarFrame + ); + + // Creating service calendar inside service calendar frame + serviceCalendarFrame.createServiceCalendar(); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + assertNotNull(serviceCalendarFrameObject.serviceCalendar()); + + // validity for service calendar frame should be set on service calendar frame object + assertEquals( + fromDateTimeServiceCalendarFrame, + serviceCalendarFrameObject.validBetween().getFromDate() + ); + assertEquals( + toDateTimeServiceCalendarFrame, + serviceCalendarFrameObject.validBetween().getToDate() + ); + + // validity for service calendar frame should be set on service calendar object + assertEquals( + fromDateTimeServiceCalendarFrame, + serviceCalendarFrameObject.serviceCalendar().validBetween().getFromDate() + ); + assertEquals( + toDateTimeServiceCalendarFrame, + serviceCalendarFrameObject.serviceCalendar().validBetween().getToDate() + ); + } + + /** + * Validity is set on both service calendar frame and service calendar. + * Both uses their own validity. + */ + @Test + void testBothServiceCalendarFrameAndServiceCalendarHasValidity() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + + // Validity on service calendar frame + LocalDateTime fromDateTimeServiceCalendarFrame = LocalDateTime.of( + 2024, + 11, + 20, + 0, + 0 + ); + LocalDateTime toDateTimeServiceCalendarFrame = LocalDateTime.of( + 2024, + 11, + 22, + 0, + 0 + ); + serviceCalendarFrame.createValidBetween( + fromDateTimeServiceCalendarFrame, + toDateTimeServiceCalendarFrame + ); + + NetexEntitiesTestFactory.CreateServiceCalendar serviceCalendar = + serviceCalendarFrame.createServiceCalendar(); + + // Validity on service calendar + LocalDateTime fromDateTimeServiceCalendar = LocalDateTime.of( + 2024, + 12, + 20, + 0, + 0 + ); + LocalDateTime toDateTimeServiceCalendar = LocalDateTime.of( + 2024, + 12, + 22, + 0, + 0 + ); + serviceCalendar.createValidBetween( + fromDateTimeServiceCalendar, + toDateTimeServiceCalendar + ); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + // validity for service calendar frame should be set on service calendar frame object + assertEquals( + fromDateTimeServiceCalendarFrame, + serviceCalendarFrameObject.validBetween().getFromDate() + ); + assertEquals( + toDateTimeServiceCalendarFrame, + serviceCalendarFrameObject.validBetween().getToDate() + ); + + // validity for service calendar should be set on service calendar object + assertEquals( + fromDateTimeServiceCalendar, + serviceCalendarFrameObject.serviceCalendar().validBetween().getFromDate() + ); + assertEquals( + toDateTimeServiceCalendar, + serviceCalendarFrameObject.serviceCalendar().validBetween().getToDate() + ); + } + + /** + * Validity is set on composite frame, service calendar frame and service calendar. + * Both Service calendar frame and service calendar uses their own validity. + */ + @Test + void testCompositeFrameAndBothServiceCalendarFrameAndServiceCalendarHasValidity() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + LocalDateTime fromDateTimeCompositeFrame = LocalDateTime.of( + 2024, + 10, + 20, + 0, + 0 + ); + LocalDateTime toDateTimeCompositeFrame = LocalDateTime.of( + 2024, + 10, + 22, + 0, + 0 + ); + NetexEntitiesTestFactory.CreateValidBetween validBetweenCompositeFrame = + new NetexEntitiesTestFactory.CreateValidBetween(1) + .withFromDate(fromDateTimeCompositeFrame) + .withToDate(toDateTimeCompositeFrame); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrameInsideCompositeFrame = + netexEntitiesTestFactory + .createCompositeFrame() + .createServiceCalendarFrame(); + + // Validity on service calendar frame + LocalDateTime fromDateTimeServiceCalendarFrame = LocalDateTime.of( + 2024, + 11, + 20, + 0, + 0 + ); + LocalDateTime toDateTimeServiceCalendarFrame = LocalDateTime.of( + 2024, + 11, + 22, + 0, + 0 + ); + serviceCalendarFrameInsideCompositeFrame.createValidBetween( + fromDateTimeServiceCalendarFrame, + toDateTimeServiceCalendarFrame + ); + + NetexEntitiesTestFactory.CreateServiceCalendar serviceCalendar = + serviceCalendarFrameInsideCompositeFrame.createServiceCalendar(); + + // Validity on service calendar + LocalDateTime fromDateTimeServiceCalendar = LocalDateTime.of( + 2024, + 12, + 20, + 0, + 0 + ); + LocalDateTime toDateTimeServiceCalendar = LocalDateTime.of( + 2024, + 12, + 22, + 0, + 0 + ); + serviceCalendar.createValidBetween( + fromDateTimeServiceCalendar, + toDateTimeServiceCalendar + ); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable( + serviceCalendarFrameInsideCompositeFrame.create(), + validBetweenCompositeFrame.create() + ); + + // validity for service calendar frame should be set on service calendar frame object + assertEquals( + fromDateTimeServiceCalendarFrame, + serviceCalendarFrameObject.validBetween().getFromDate() + ); + assertEquals( + toDateTimeServiceCalendarFrame, + serviceCalendarFrameObject.validBetween().getToDate() + ); + + // validity for service calendar should be set on service calendar object + assertEquals( + fromDateTimeServiceCalendar, + serviceCalendarFrameObject.serviceCalendar().validBetween().getFromDate() + ); + assertEquals( + toDateTimeServiceCalendar, + serviceCalendarFrameObject.serviceCalendar().validBetween().getToDate() + ); + } + + /** + * Validity is set on composite frame only. + * Both Service calendar frame and service calendar uses validity from composite frame. + */ + @Test + void testDayTypesInServiceCalendarFrameInCompositeFrame() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory + .createCompositeFrame() + .createServiceCalendarFrame(); + + // creating service calendar inside service calendar frame + serviceCalendarFrame.createServiceCalendar(); + + // Validity on composite frame + LocalDateTime fromDateTimeCompositeFrame = LocalDateTime.of( + 2024, + 11, + 20, + 0, + 0 + ); + LocalDateTime toDateTimeCompositeFrame = LocalDateTime.of( + 2024, + 11, + 22, + 0, + 0 + ); + NetexEntitiesTestFactory.CreateValidBetween validBetween = + new NetexEntitiesTestFactory.CreateValidBetween(1) + .withFromDate(fromDateTimeCompositeFrame) + .withToDate(toDateTimeCompositeFrame); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable( + serviceCalendarFrame.create(), + validBetween.create() + ); + + // validity for composite frame should be set on service calendar frame object + assertEquals( + fromDateTimeCompositeFrame, + serviceCalendarFrameObject.validBetween().getFromDate() + ); + assertEquals( + toDateTimeCompositeFrame, + serviceCalendarFrameObject.validBetween().getToDate() + ); + + // validity for composite frame should be set on service calendar object + assertEquals( + fromDateTimeCompositeFrame, + serviceCalendarFrameObject.serviceCalendar().validBetween().getFromDate() + ); + assertEquals( + toDateTimeCompositeFrame, + serviceCalendarFrameObject.serviceCalendar().validBetween().getToDate() + ); + } + + /** + * Validity is set on composite frame and service calendar frame. + * Service calendar should use the Validity from service calendar frame. + */ + @Test + void testDayTypesWithServiceCalendarFrameOverridingValidity() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory + .createCompositeFrame() + .createServiceCalendarFrame(); + + // Validity on service calendar frame + LocalDateTime fromDateTimeServiceCalendarFrame = LocalDateTime.of( + 2024, + 12, + 20, + 0, + 0 + ); + LocalDateTime toDateTimeServiceCalendarFrame = LocalDateTime.of( + 2024, + 12, + 22, + 0, + 0 + ); + serviceCalendarFrame.createValidBetween( + fromDateTimeServiceCalendarFrame, + toDateTimeServiceCalendarFrame + ); + + // creating service calendar inside service calendar frame + serviceCalendarFrame.createServiceCalendar(); + + // Validity on composite frame + LocalDateTime fromDateTimeCompositeFrame = LocalDateTime.of( + 2024, + 11, + 20, + 0, + 0 + ); + LocalDateTime toDateTimeCompositeFrame = LocalDateTime.of( + 2024, + 11, + 22, + 0, + 0 + ); + NetexEntitiesTestFactory.CreateValidBetween validBetween = + new NetexEntitiesTestFactory.CreateValidBetween(1) + .withFromDate(fromDateTimeCompositeFrame) + .withToDate(toDateTimeCompositeFrame); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable( + serviceCalendarFrame.create(), + validBetween.create() + ); + + // validity for ServiceCalendarFrame should be set on service calendar frame object + assertEquals( + fromDateTimeServiceCalendarFrame, + serviceCalendarFrameObject.validBetween().getFromDate() + ); + assertEquals( + toDateTimeServiceCalendarFrame, + serviceCalendarFrameObject.validBetween().getToDate() + ); + + // validity for ServiceCalendarFrame should be set on service calendar object + assertEquals( + fromDateTimeServiceCalendarFrame, + serviceCalendarFrameObject.serviceCalendar().validBetween().getFromDate() + ); + assertEquals( + toDateTimeServiceCalendarFrame, + serviceCalendarFrameObject.serviceCalendar().validBetween().getToDate() + ); + } + + @Test + void testCalendarDataIsCorrectlyParsedInServiceCalenderFrame() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + serviceCalendarFrame.createValidBetween( + LocalDateTime.of(2024, 11, 20, 0, 0), + LocalDateTime.of(2024, 11, 22, 0, 0) + ); + + List dayTypes = + serviceCalendarFrame.createDayTypes( + 3, + DayOfWeekEnumeration.MONDAY, + DayOfWeekEnumeration.TUESDAY + ); + + List operatingDays = + serviceCalendarFrame.createOperatingDays(2, LocalDate.of(2024, 11, 1)); + + List operatingPeriods = + serviceCalendarFrame.createOperatingPeriods( + 4, + LocalDate.of(2024, 12, 1), + LocalDate.of(2024, 12, 6) + ); + + serviceCalendarFrame.createDayTypeAssignmentsWithDates( + dayTypes, + LocalDate.of(2024, 11, 20) + ); + serviceCalendarFrame.createDayTypeAssignmentsWithOperatingDays( + dayTypes, + operatingDays + ); + serviceCalendarFrame.createDayTypeAssignmentsWithOperatingPeriods( + dayTypes, + operatingPeriods + ); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + assertEquals( + 3, + serviceCalendarFrameObject.calendarData().dayTypes().size() + ); + assertEquals( + 2, + serviceCalendarFrameObject.calendarData().operatingDays().size() + ); + assertEquals( + 4, + serviceCalendarFrameObject.calendarData().operatingPeriods().size() + ); + assertEquals( + 9, + serviceCalendarFrameObject.calendarData().dayTypeAssignments().size() + ); + } + + @Test + void testCalendarDataIsCorrectlyParsedInServiceCalender() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + serviceCalendarFrame.createValidBetween( + LocalDateTime.of(2024, 11, 20, 0, 0), + LocalDateTime.of(2024, 11, 22, 0, 0) + ); + + NetexEntitiesTestFactory.CreateServiceCalendar serviceCalendar = + serviceCalendarFrame.createServiceCalendar(); + + List dayTypes = + serviceCalendar.createDayTypes( + 3, + DayOfWeekEnumeration.MONDAY, + DayOfWeekEnumeration.TUESDAY + ); + + List operatingDays = + serviceCalendar.createOperatingDays(2, LocalDate.of(2024, 11, 1)); + + List operatingPeriods = + serviceCalendar.createOperatingPeriods( + 4, + LocalDate.of(2024, 12, 1), + LocalDate.of(2024, 12, 6) + ); + + serviceCalendar.createDayTypeAssignmentsWithDates( + dayTypes, + LocalDate.of(2024, 11, 20) + ); + serviceCalendar.createDayTypeAssignmentsWithOperatingDays( + dayTypes, + operatingDays + ); + serviceCalendar.createDayTypeAssignmentsWithOperatingPeriods( + dayTypes, + operatingPeriods + ); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + assertEquals( + 3, + serviceCalendarFrameObject + .serviceCalendar() + .calendarData() + .dayTypes() + .size() + ); + assertEquals( + 2, + serviceCalendarFrameObject + .serviceCalendar() + .calendarData() + .operatingDays() + .size() + ); + assertEquals( + 4, + serviceCalendarFrameObject + .serviceCalendar() + .calendarData() + .operatingPeriods() + .size() + ); + assertEquals( + 9, + serviceCalendarFrameObject + .serviceCalendar() + .calendarData() + .dayTypeAssignments() + .size() + ); + } + + @Test + void testNoValidityPresent() { + NetexEntitiesTestFactory netexEntitiesTestFactory = + new NetexEntitiesTestFactory(); + + NetexEntitiesTestFactory.CreateServiceCalendarFrame serviceCalendarFrame = + netexEntitiesTestFactory.createServiceCalendarFrame(); + + ServiceCalendarFrameObject serviceCalendarFrameObject = + ServiceCalendarFrameObject.ofNullable(serviceCalendarFrame.create()); + + assertNotNull(serviceCalendarFrameObject); + } +} diff --git a/src/test/java/no/entur/antu/netexdata/collectors/activedatecollector/calender/ValidOperatingPeriodTest.java b/src/test/java/no/entur/antu/netexdata/collectors/activedatecollector/calender/ValidOperatingPeriodTest.java new file mode 100644 index 00000000..19d56594 --- /dev/null +++ b/src/test/java/no/entur/antu/netexdata/collectors/activedatecollector/calender/ValidOperatingPeriodTest.java @@ -0,0 +1,177 @@ +package no.entur.antu.netexdata.collectors.activedatecollector.calender; + +import static org.junit.jupiter.api.Assertions.*; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import no.entur.antu.netextestdata.NetexEntitiesTestFactory; +import org.entur.netex.validation.validator.model.OperatingDayId; +import org.junit.jupiter.api.Test; +import org.rutebanken.netex.model.OperatingDay; +import org.rutebanken.netex.model.OperatingPeriod; +import org.rutebanken.netex.model.ValidBetween; + +class ValidOperatingPeriodTest { + + @Test + void testValidPeriodWithNoValidBetween() { + OperatingPeriod operatingPeriod = + new NetexEntitiesTestFactory.CreateOperatingPeriod( + 1, + LocalDate.of(2023, 1, 1), + LocalDate.of(2023, 12, 31) + ) + .create(); + + Map operatingDays = Map.of(); + + ValidOperatingPeriod result = ValidOperatingPeriod.of( + operatingPeriod, + null, + operatingDays + ); + + assertEquals(LocalDate.of(2023, 1, 1), result.startDate()); + assertEquals(LocalDate.of(2023, 12, 31), result.endDate()); + } + + @Test + void testPeriodAdjustedToValidBetween() { + OperatingPeriod operatingPeriod = + new NetexEntitiesTestFactory.CreateOperatingPeriod( + 1, + LocalDate.of(2023, 1, 1), + LocalDate.of(2023, 12, 31) + ) + .create(); + ValidBetween validBetween = new ValidBetween() + .withFromDate(LocalDateTime.of(2023, 6, 1, 0, 0)) + .withToDate(LocalDateTime.of(2023, 10, 31, 0, 0)); + + Map operatingDays = Map.of(); + + ValidOperatingPeriod result = ValidOperatingPeriod.of( + operatingPeriod, + validBetween, + operatingDays + ); + + assertEquals(LocalDate.of(2023, 6, 1), result.startDate()); + assertEquals(LocalDate.of(2023, 10, 31), result.endDate()); + } + + @Test + void testPeriodCompletelyOutsideValidBetween() { + OperatingPeriod operatingPeriod = + new NetexEntitiesTestFactory.CreateOperatingPeriod( + 1, + LocalDate.of(2023, 1, 1), + LocalDate.of(2023, 3, 31) + ) + .create(); + + ValidBetween validBetween = new ValidBetween() + .withFromDate(LocalDateTime.of(2023, 6, 1, 0, 0)) + .withToDate(LocalDateTime.of(2023, 10, 31, 0, 0)); + Map operatingDays = Map.of(); + + ValidOperatingPeriod result = ValidOperatingPeriod.of( + operatingPeriod, + validBetween, + operatingDays + ); + + assertNull(result.startDate()); + assertNull(result.endDate()); + } + + @Test + void testValidDatesOnWeekDays() { + ValidOperatingPeriod period = new ValidOperatingPeriod( + LocalDate.of(2023, 6, 1), + LocalDate.of(2023, 6, 10) + ); + + int intDayTypes = 0b0011111; // All weekdays + + List result = period.toDates(List.of(), intDayTypes); + + List expectedDates = List.of( + LocalDate.of(2023, 6, 1), + LocalDate.of(2023, 6, 2), + LocalDate.of(2023, 6, 5), + LocalDate.of(2023, 6, 6), + LocalDate.of(2023, 6, 7), + LocalDate.of(2023, 6, 8), + LocalDate.of(2023, 6, 9) + ); + assertEquals(expectedDates, result); + } + + @Test + void testValidDatesOnWeekDaysAndExcludedDates() { + ValidOperatingPeriod period = new ValidOperatingPeriod( + LocalDate.of(2023, 6, 1), + LocalDate.of(2023, 6, 10) + ); + + List excludedDates = List.of(LocalDate.of(2023, 6, 5)); + int intDayTypes = 0b0011111; // All weekdays + + List result = period.toDates(excludedDates, intDayTypes); + + List expectedDates = List.of( + LocalDate.of(2023, 6, 1), + LocalDate.of(2023, 6, 2), + LocalDate.of(2023, 6, 6), + LocalDate.of(2023, 6, 7), + LocalDate.of(2023, 6, 8), + LocalDate.of(2023, 6, 9) + ); + assertEquals(expectedDates, result); + } + + @Test + void testToDatesOnWeekends() { + ValidOperatingPeriod period = new ValidOperatingPeriod( + LocalDate.of(2023, 6, 1), + LocalDate.of(2023, 6, 10) + ); + + int intDayTypes = 0b1100000; // Weekend + + List result = period.toDates(null, intDayTypes); + + List expectedDates = List.of( + LocalDate.of(2023, 6, 3), + LocalDate.of(2023, 6, 4), + LocalDate.of(2023, 6, 10) + ); + assertEquals(expectedDates, result); + } + + @Test + void testToDates_InvalidPeriod() { + ValidOperatingPeriod period = new ValidOperatingPeriod(null, null); + List excludedDates = List.of(); + int intDayTypes = 0b0111111; + + List result = period.toDates(excludedDates, intDayTypes); + + assertTrue(result.isEmpty()); + } + + @Test + void testIsValid() { + ValidOperatingPeriod validPeriod = new ValidOperatingPeriod( + LocalDate.of(2023, 1, 1), + LocalDate.of(2023, 12, 31) + ); + ValidOperatingPeriod invalidPeriod = new ValidOperatingPeriod(null, null); + + assertTrue(validPeriod.isValid()); + assertFalse(invalidPeriod.isValid()); + } +} diff --git a/src/test/java/no/entur/antu/netextestdata/NetexEntitiesTestFactory.java b/src/test/java/no/entur/antu/netextestdata/NetexEntitiesTestFactory.java index 28b9b531..d03a8cfc 100644 --- a/src/test/java/no/entur/antu/netextestdata/NetexEntitiesTestFactory.java +++ b/src/test/java/no/entur/antu/netextestdata/NetexEntitiesTestFactory.java @@ -8,12 +8,14 @@ import java.math.BigInteger; import java.time.Duration; import java.time.LocalDate; +import java.time.LocalDateTime; import java.time.LocalTime; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import java.util.stream.IntStream; import net.opengis.gml._3.AbstractRingPropertyType; import net.opengis.gml._3.DirectPositionListType; @@ -24,14 +26,11 @@ import net.opengis.gml._3.PolygonType; import org.entur.netex.index.api.NetexEntitiesIndex; import org.entur.netex.index.impl.NetexEntitiesIndexImpl; +import org.entur.netex.validation.test.jaxb.support.JAXBUtils; import org.rutebanken.netex.model.*; public class NetexEntitiesTestFactory { - private static final DayType EVERYDAY = new DayType() - .withId("EVERYDAY") - .withName(new MultilingualString().withValue("everyday")); - private CreateGenericLine line; private CreateRoute route; @@ -50,6 +49,9 @@ public class NetexEntitiesTestFactory { private final List passengerStopAssignments = new ArrayList<>(); + private CreateCompositeFrame compositeFrame; + private List serviceCalendarFrames; + public NetexEntitiesIndex create() { NetexEntitiesIndex netexEntitiesIndex = new NetexEntitiesIndexImpl(); @@ -67,6 +69,18 @@ public NetexEntitiesIndex create() { netexEntitiesIndex.getRouteIndex().put(route.ref(), route.create()); } + if (compositeFrame != null) { + netexEntitiesIndex.getCompositeFrames().add(compositeFrame.create()); + } + + if (serviceCalendarFrames != null) { + serviceCalendarFrames.forEach(serviceCalendarFrame -> + netexEntitiesIndex + .getServiceCalendarFrames() + .add(serviceCalendarFrame.create()) + ); + } + fillIndexes(netexEntitiesIndex); return netexEntitiesIndex; } @@ -179,8 +193,7 @@ public CreateLine createLine(int id) { * @return CreateLine */ public CreateLine createLine() { - line = new CreateLine(1); - return (CreateLine) line; + return createLine(1); } /** @@ -202,8 +215,7 @@ public CreateFlexibleLine createFlexibleLine(int id) { * @return CreateFlexibleLine */ public CreateFlexibleLine createFlexibleLine() { - line = new CreateFlexibleLine(1); - return (CreateFlexibleLine) line; + return createFlexibleLine(1); } /** @@ -229,12 +241,7 @@ public CreateRoute createRoute(int id) { * @return CreateRoute */ public CreateRoute createRoute() { - if (line == null) { - line = new CreateLine(1); - } - - route = new CreateRoute(1, line); - return route; + return createRoute(1); } /** @@ -255,9 +262,7 @@ public CreateJourneyPattern createJourneyPattern(int id) { * @return CreateJourneyPattern */ public CreateJourneyPattern createJourneyPattern() { - CreateJourneyPattern createJourneyPattern = new CreateJourneyPattern(1); - journeyPatterns.add(createJourneyPattern); - return createJourneyPattern; + return createJourneyPattern(1); } /** @@ -294,18 +299,30 @@ public CreateServiceJourney createServiceJourney( */ public CreateServiceJourney createServiceJourney( CreateJourneyPattern journeyPattern + ) { + return createServiceJourney(1, journeyPattern); + } + + /** + * Adds numberOfServiceJourneys new service journeys with the given journey pattern. + * The line will be created if it does not exist, with id 1 + * The service journeys will have ids from 1 to numberOfServiceJourneys + * + * @param createJourneyPattern the journey pattern ref for the service journeys + * @param numberOfServiceJourneys the number of service journeys to create + * @return List of CreateServiceJourney created + */ + public List createServiceJourneys( + CreateJourneyPattern createJourneyPattern, + int numberOfServiceJourneys ) { if (line == null) { line = new CreateLine(1); } - - CreateServiceJourney createServiceJourney = new CreateServiceJourney( - 1, - line, - journeyPattern - ); - serviceJourneys.add(createServiceJourney); - return createServiceJourney; + return IntStream + .rangeClosed(1, numberOfServiceJourneys) + .mapToObj(index -> createServiceJourney(index, createJourneyPattern)) + .toList(); } /** @@ -336,12 +353,7 @@ public CreateDeadRun createDeadRun( * @return CreateDeadRun */ public CreateDeadRun createDeadRun(CreateJourneyPattern journeyPattern) { - if (line == null) { - line = new CreateLine(1); - } - CreateDeadRun deadRun = new CreateDeadRun(1, line, journeyPattern); - deadRuns.add(deadRun); - return deadRun; + return createDeadRun(1, journeyPattern); } /** @@ -374,36 +386,7 @@ public CreateDatedServiceJourney createDatedServiceJourney( CreateServiceJourney serviceJourneyRef, CreateOperatingDay operatingDayRef ) { - CreateDatedServiceJourney createDatedServiceJourney = - new CreateDatedServiceJourney(1, serviceJourneyRef, operatingDayRef); - datedServiceJourneys.add(createDatedServiceJourney); - return createDatedServiceJourney; - } - - /** - * Adds numberOfServiceJourneys new service journeys with the given journey pattern. - * The line will be created if it does not exist, with id 1 - * The service journeys will have ids from 1 to numberOfServiceJourneys - * - * @param createJourneyPattern the journey pattern ref for the service journeys - * @param numberOfServiceJourneys the number of service journeys to create - * @return List of CreateServiceJourney created - */ - public List createServiceJourneys( - CreateJourneyPattern createJourneyPattern, - int numberOfServiceJourneys - ) { - if (line == null) { - line = new CreateLine(1); - } - List createServiceJourneys = IntStream - .rangeClosed(1, numberOfServiceJourneys) - .mapToObj(index -> - new CreateServiceJourney(index, line, createJourneyPattern) - ) - .toList(); - serviceJourneys.addAll(createServiceJourneys); - return createServiceJourneys; + return createDatedServiceJourney(1, serviceJourneyRef, operatingDayRef); } /** @@ -427,10 +410,7 @@ public CreateServiceJourneyInterchange createServiceJourneyInterchange( * @return CreateServiceJourneyInterchange */ public CreateServiceJourneyInterchange createServiceJourneyInterchange() { - CreateServiceJourneyInterchange createServiceJourneyInterchange = - new CreateServiceJourneyInterchange(1); - interchanges.add(createServiceJourneyInterchange); - return createServiceJourneyInterchange; + return createServiceJourneyInterchange(1); } /** @@ -460,11 +440,11 @@ public CreateServiceLink createServiceLink( ScheduledStopPointRefStructure fromScheduledStopPointRef, ScheduledStopPointRefStructure toScheduledStopPointRef ) { - CreateServiceLink createServiceLink = new CreateServiceLink(1) - .withFromScheduledStopPointRef(fromScheduledStopPointRef) - .withToScheduledStopPointRef(toScheduledStopPointRef); - serviceLinks.add(createServiceLink); - return createServiceLink; + return createServiceLink( + 1, + fromScheduledStopPointRef, + toScheduledStopPointRef + ); } /** @@ -486,10 +466,7 @@ public CreateFlexibleStopPlace createFlexibleStopPlace(int id) { * @return CreateFlexibleStopPlace */ public CreateFlexibleStopPlace createFlexibleStopPlace() { - CreateFlexibleStopPlace createFlexibleStopPlace = - new CreateFlexibleStopPlace(1); - flexibleStopPlaces.add(createFlexibleStopPlace); - return createFlexibleStopPlace; + return createFlexibleStopPlace(1); } /** @@ -511,29 +488,56 @@ public CreatePassengerStopAssignment createPassengerStopAssignment(int id) { * @return CreatePassengerStopAssignment */ public CreatePassengerStopAssignment createPassengerStopAssignment() { - CreatePassengerStopAssignment createPassengerStopAssignment = - new CreatePassengerStopAssignment(1); - passengerStopAssignments.add(createPassengerStopAssignment); - return createPassengerStopAssignment; + return createPassengerStopAssignment(1); } /** - * Adds a new day type with the given id + * Create a new CompositeFrame with the given id * - * @param id the id of the day type - * @return CreateDayType + * @param id the id of the CompositeFrame + * @return CreateCompositeFrame */ - public CreateOperatingDay createOperatingDay(int id, LocalDate date) { - return new CreateOperatingDay(id, date); + public CreateCompositeFrame createCompositeFrame(int id) { + compositeFrame = new CreateCompositeFrame(id); + return compositeFrame; } /** - * Adds a new day type with id 1 + * Create a new CompositeFrame with id 1 * - * @return CreateDayType + * @return CreateCompositeFrame */ - public CreateOperatingDay createOperatingDay(LocalDate date) { - return new CreateOperatingDay(1, date); + public CreateCompositeFrame createCompositeFrame() { + return createCompositeFrame(1); + } + + /** + * Adds new service calendar frame with the given id + * The existing service calendar frame will be overwritten. + * The service calendar frame will be added outside the composite frame. + * + * @param id the id of the service calendar frame + * @return CreateServiceCalendarFrame + */ + public CreateServiceCalendarFrame createServiceCalendarFrame(int id) { + if (serviceCalendarFrames == null) { + serviceCalendarFrames = new ArrayList<>(); + } + CreateServiceCalendarFrame serviceCalendarFrame = + new CreateServiceCalendarFrame(id); + serviceCalendarFrames.add(serviceCalendarFrame); + return serviceCalendarFrame; + } + + /** + * Adds new service calendar frame with id 1 + * The existing service calendar frame will be overwritten. + * The service calendar frame will be added outside the composite frame. + * + * @return CreateServiceCalendarFrame + */ + public CreateServiceCalendarFrame createServiceCalendarFrame() { + return createServiceCalendarFrame(1); } /** @@ -635,6 +639,570 @@ public final String ref() { public abstract T create(); } + public static class CreateCompositeFrame + extends CreateEntity { + + private CreateValidBetween validBetween; + private List serviceCalendarFrames; + + public CreateCompositeFrame(int id) { + super(id); + } + + /** + * Adds new service calendar frame with the given id + * The existing service calendar frame will be overwritten. + * The service calendar frame will be added in the composite frame. + * + * @param id the id of the service calendar frame + * @return CreateServiceCalendarFrame + */ + public CreateServiceCalendarFrame createServiceCalendarFrame(int id) { + if (serviceCalendarFrames == null) { + serviceCalendarFrames = new ArrayList<>(); + } + CreateServiceCalendarFrame serviceCalendarFrame = + new CreateServiceCalendarFrame(id); + serviceCalendarFrames.add(serviceCalendarFrame); + return serviceCalendarFrame; + } + + /** + * Adds new service calendar frame with id 1 + * The existing service calendar frame will be overwritten. + * The service calendar frame will be added in the composite frame. + * + * @return CreateServiceCalendarFrame + */ + public CreateServiceCalendarFrame createServiceCalendarFrame() { + return createServiceCalendarFrame(1); + } + + public CompositeFrame create() { + CompositeFrame compositeFrame = new CompositeFrame().withId(ref()); + + if (validBetween != null) { + compositeFrame.withValidBetween(validBetween.create()); + } + + if (serviceCalendarFrames != null) { + compositeFrame.setFrames( + new Frames_RelStructure() + .withCommonFrame( + serviceCalendarFrames + .stream() + .map(CreateServiceCalendarFrame::create) + .map(JAXBUtils::createJaxbElement) + .collect(Collectors.toCollection(ArrayList::new)) + ) + ); + } + return compositeFrame; + } + } + + public static class CreateValidBetween extends CreateEntity { + + private LocalDateTime fromDateTime; + private LocalDateTime localDateTime; + + public CreateValidBetween(int id) { + super(id); + } + + public CreateValidBetween withFromDate(LocalDateTime fromDateTime) { + this.fromDateTime = fromDateTime; + return this; + } + + public CreateValidBetween withToDate(LocalDateTime localDateTime) { + this.localDateTime = localDateTime; + return this; + } + + @Override + public ValidBetween create() { + return new ValidBetween() + .withFromDate(fromDateTime) + .withToDate(localDateTime); + } + } + + protected abstract static class CreateGenericCalender< + T extends EntityStructure + > + extends CreateEntity { + + protected CreateValidBetween validBetween; + protected List dayTypeAssignments; + protected List dayTypes; + protected List operatingDays; + protected List operatingPeriods; + + public CreateGenericCalender(int id) { + super(id); + } + + public void withValidBetween(CreateValidBetween validBetween) { + this.validBetween = validBetween; + } + + public CreateValidBetween createValidBetween( + LocalDateTime fromDateTime, + LocalDateTime toDateTime + ) { + validBetween = + new CreateValidBetween(1) + .withFromDate(fromDateTime) + .withToDate(toDateTime); + return validBetween; + } + + /** + * Adds a new operating day with the given id + * + * @param id the id of the operating day + * @return CreateOperatingDay + */ + public CreateOperatingDay createOperatingDay(int id, LocalDate date) { + if (operatingDays == null) { + operatingDays = new ArrayList<>(); + } + CreateOperatingDay operatingDay = new CreateOperatingDay(id, date); + operatingDays.add(operatingDay); + return operatingDay; + } + + /** + * Adds a new operating day with id 1 + * + * @return CreateOperatingDay + */ + public CreateOperatingDay createOperatingDay(LocalDate date) { + return createOperatingDay(1, date); + } + + /** + * Adds numberOfOperatingDays new operating days starting from the given date, adding one day for each operating day. + * + * @param numberOfOperatingDays the number of operating days to create + * @param date the start date for the first operating day + * @return List of CreateOperatingDay + */ + public List createOperatingDays( + int numberOfOperatingDays, + LocalDate date + ) { + return IntStream + .rangeClosed(1, numberOfOperatingDays) + .mapToObj(i -> createOperatingDay(i, date.plusDays(i - 1))) + .toList(); + } + + /** + * Adds a new operating period with the given id + * + * @param id the id of the operating period + * @return CreateOperatingPeriod + */ + public CreateOperatingPeriod createOperatingPeriod( + int id, + LocalDate fromDate, + LocalDate toDate + ) { + if (operatingPeriods == null) { + operatingPeriods = new ArrayList<>(); + } + CreateOperatingPeriod operatingPeriod = new CreateOperatingPeriod( + id, + fromDate, + toDate + ); + operatingPeriods.add(operatingPeriod); + return operatingPeriod; + } + + /** + * Adds a new operating period with id 1 + * + * @return CreateOperatingPeriod + */ + public CreateOperatingPeriod createOperatingPeriod( + LocalDate fromDate, + LocalDate toDate + ) { + return createOperatingPeriod(1, fromDate, toDate); + } + + /** + * Adds numberOfOperatingPeriods new operating periods starting from the given dates, adding one day for both from and to dates. + * + * @param numberOfOperatingPeriods the number of operating periods to create + * @param fromDate the start date for the first operating period + * @param toDate the end date for the first operating period + * @return List of CreateOperatingPeriod + */ + public List createOperatingPeriods( + int numberOfOperatingPeriods, + LocalDate fromDate, + LocalDate toDate + ) { + return IntStream + .rangeClosed(1, numberOfOperatingPeriods) + .mapToObj(i -> + createOperatingPeriod( + i, + fromDate.plusDays(i - 1), + toDate.plusDays(i - 1) + ) + ) + .toList(); + } + + public List createOperatingPeriods( + int numberOfOperatingPeriods, + LocalDate fromDate, + LocalDate toDate, + int numberOfDaysBetweenPeriods + ) { + return IntStream + .rangeClosed(1, numberOfOperatingPeriods) + .mapToObj(i -> + createOperatingPeriod( + i, + fromDate.plusDays((long) (i - 1) * numberOfDaysBetweenPeriods), + toDate.plusDays((long) (i - 1) * numberOfDaysBetweenPeriods) + ) + ) + .toList(); + } + + /** + * Adds a new day type with the given id + * + * @param id the id of the day type + * @return CreateDayType + */ + public CreateDayType createDayType(int id) { + if (dayTypes == null) { + dayTypes = new ArrayList<>(); + } + CreateDayType dayType = new CreateDayType(id); + dayTypes.add(dayType); + return dayType; + } + + /** + * Adds a new day type with id 1 + * + * @return CreateDayType + */ + public CreateDayType createDayType() { + return createDayType(1); + } + + /** + * Adds numberOfDayTypes new day types with the given ids + * The day types will be created with the given dayOfWeeks in circular fashion. + * If no dayOfWeeks are provided, the day types will be created without daysOfWeek. + * + * @param numberOfDayTypes the number of day types to create + * @param dayOfWeeks the day of weeks to assign to the day types + * @return List of CreateDayType + */ + public List createDayTypes( + int numberOfDayTypes, + DayOfWeekEnumeration... dayOfWeeks + ) { + List createDayTypes = IntStream + .rangeClosed(1, numberOfDayTypes) + .mapToObj(this::createDayType) + .toList(); + + if (dayOfWeeks.length > 0) { + return createDayTypes + .stream() + .map(createDayType -> + createDayType.withDaysOfWeek( + dayOfWeeks[(createDayType.id - 1) % dayOfWeeks.length] + ) + ) + .toList(); + } + + return createDayTypes; + } + + /** + * Adds a new day type assignment with the given id + * + * @param id the id of the day type assignment + * @return CreateDayTypeAssignment + */ + public CreateDayTypeAssignment createDayTypeAssignment( + int id, + CreateDayType dayTypeRef + ) { + if (dayTypeAssignments == null) { + dayTypeAssignments = new ArrayList<>(); + } + CreateDayTypeAssignment dayTypeAssignment = new CreateDayTypeAssignment( + id, + dayTypeRef + ); + dayTypeAssignments.add(dayTypeAssignment); + return dayTypeAssignment; + } + + /** + * Adds a new day type assignment with id 1 + * + * @return CreateDayTypeAssignment + */ + public CreateDayTypeAssignment createDayTypeAssignment( + CreateDayType dayTypeRef + ) { + return createDayTypeAssignment(1, dayTypeRef); + } + + /** + * Creates the dayTypes.size() number of day type assignments with dates starting from startDate, adding one day for each day type assignment. + * + * @param dayTypes the day types to create day type assignments for + * @param startDate the start date for the first day type assignment + * @return List of CreateDayTypeAssignment + */ + public List createDayTypeAssignmentsWithDates( + List dayTypes, + LocalDate startDate + ) { + return IntStream + .range(0, dayTypes.size()) + .mapToObj(i -> + createDayTypeAssignment(i + 1, dayTypes.get(i)) + .withDate(startDate.plusDays(i)) + ) + .toList(); + } + + /** + * Creates the dayTypes.size() number of day type assignments with operating days, by traversing the operatingDays list in circular fashion. + * + * @param dayTypes the day types to create day type assignments for + * @param operatingDays the operating days to assign to the day type assignments + * @return List of CreateDayTypeAssignment + */ + public List createDayTypeAssignmentsWithOperatingDays( + List dayTypes, + List operatingDays + ) { + List createDayTypeAssignments = IntStream + .range(0, dayTypes.size()) + .mapToObj(i -> createDayTypeAssignment(i + 1, dayTypes.get(i))) + .toList(); + + if (operatingDays != null && !operatingDays.isEmpty()) { + return IntStream + .range(0, createDayTypeAssignments.size()) + .mapToObj(i -> + createDayTypeAssignments + .get(i) + .withOperatingDayRef(operatingDays.get(i % operatingDays.size())) + ) + .toList(); + } + + return createDayTypeAssignments; + } + + /** + * Creates the dayTypes.size() number of day type assignments with operating periods, by traversing the operatingPeriods list in circular fashion. + * + * @param dayTypes the day types to create day type assignments for + * @param operatingPeriods the operating periods to assign to the day type assignments + * @return List of CreateDayTypeAssignment + */ + public List createDayTypeAssignmentsWithOperatingPeriods( + List dayTypes, + List operatingPeriods + ) { + List dayTypeAssignments = IntStream + .range(0, dayTypes.size()) + .mapToObj(i -> createDayTypeAssignment(i + 1, dayTypes.get(i))) + .toList(); + + if (operatingPeriods != null && !operatingPeriods.isEmpty()) { + return IntStream + .range(0, dayTypeAssignments.size()) + .mapToObj(i -> + dayTypeAssignments + .get(i) + .withOperatingPeriodRef( + operatingPeriods.get(i % operatingPeriods.size()) + ) + ) + .toList(); + } + + return dayTypeAssignments; + } + } + + public static class CreateServiceCalendarFrame + extends CreateGenericCalender { + + private CreateServiceCalendar serviceCalendar; + + public CreateServiceCalendarFrame(int id) { + super(id); + } + + public CreateServiceCalendar createServiceCalendar(int id) { + serviceCalendar = new CreateServiceCalendar(id); + return serviceCalendar; + } + + public CreateServiceCalendar createServiceCalendar() { + return createServiceCalendar(1); + } + + @Override + public ServiceCalendarFrame create() { + ServiceCalendarFrame serviceCalendarFrame = new ServiceCalendarFrame() + .withId(ref()); + + if (validBetween != null) { + serviceCalendarFrame.withValidBetween(validBetween.create()); + } + + if (serviceCalendar != null) { + serviceCalendarFrame.withServiceCalendar(serviceCalendar.create()); + } + + if (dayTypeAssignments != null) { + DayTypeAssignmentsInFrame_RelStructure dayTypeAssignmentsInFrameRelStructure = + new DayTypeAssignmentsInFrame_RelStructure() + .withDayTypeAssignment( + dayTypeAssignments + .stream() + .map(CreateDayTypeAssignment::create) + .collect(Collectors.toCollection(ArrayList::new)) + ); + serviceCalendarFrame.setDayTypeAssignments( + dayTypeAssignmentsInFrameRelStructure + ); + } + + if (dayTypes != null) { + DayTypesInFrame_RelStructure dayTypesInFrameRelStructure = + new DayTypesInFrame_RelStructure() + .withDayType_( + dayTypes + .stream() + .map(CreateDayType::create) + .map(JAXBUtils::createJaxbElement) + .collect(Collectors.toCollection(ArrayList::new)) + ); + serviceCalendarFrame.setDayTypes(dayTypesInFrameRelStructure); + } + + if (operatingDays != null) { + OperatingDaysInFrame_RelStructure operatingDaysInFrameRelStructure = + new OperatingDaysInFrame_RelStructure() + .withOperatingDay( + operatingDays + .stream() + .map(CreateOperatingDay::create) + .collect(Collectors.toCollection(ArrayList::new)) + ); + serviceCalendarFrame.setOperatingDays(operatingDaysInFrameRelStructure); + } + + if (operatingPeriods != null) { + OperatingPeriodsInFrame_RelStructure operatingPeriodsInFrameRelStructure = + new OperatingPeriodsInFrame_RelStructure() + .withOperatingPeriodOrUicOperatingPeriod( + operatingPeriods + .stream() + .map(CreateOperatingPeriod::create) + .collect(Collectors.toCollection(ArrayList::new)) + ); + serviceCalendarFrame.setOperatingPeriods( + operatingPeriodsInFrameRelStructure + ); + } + + return serviceCalendarFrame; + } + } + + public static class CreateServiceCalendar + extends CreateGenericCalender { + + public CreateServiceCalendar(int id) { + super(id); + } + + @Override + public ServiceCalendar create() { + ServiceCalendar serviceCalendar = new ServiceCalendar().withId(ref()); + + if (validBetween != null) { + serviceCalendar.withValidBetween(validBetween.create()); + } + + if (dayTypeAssignments != null) { + DayTypeAssignments_RelStructure dayTypeAssignmentsRelStructure = + new DayTypeAssignments_RelStructure() + .withDayTypeAssignment( + dayTypeAssignments + .stream() + .map(CreateDayTypeAssignment::create) + .collect(Collectors.toCollection(ArrayList::new)) + ); + serviceCalendar.setDayTypeAssignments(dayTypeAssignmentsRelStructure); + } + if (dayTypes != null) { + DayTypes_RelStructure dayTypesRelStructure = new DayTypes_RelStructure() + .withDayTypeRefOrDayType_( + dayTypes + .stream() + .map(CreateDayType::create) + .map(JAXBUtils::createJaxbElement) + .collect(Collectors.toCollection(ArrayList::new)) + ); + serviceCalendar.setDayTypes(dayTypesRelStructure); + } + + if (operatingDays != null) { + OperatingDays_RelStructure operatingDaysRelStructure = + new OperatingDays_RelStructure() + .withOperatingDayRefOrOperatingDay( + operatingDays + .stream() + .map(CreateOperatingDay::create) + .collect(Collectors.toCollection(ArrayList::new)) + ); + serviceCalendar.setOperatingDays(operatingDaysRelStructure); + } + + if (operatingPeriods != null) { + OperatingPeriods_RelStructure operatingPeriodsRelStructure = + new OperatingPeriods_RelStructure() + .withOperatingPeriodRefOrOperatingPeriodOrUicOperatingPeriod( + operatingPeriods + .stream() + .map(CreateOperatingPeriod::create) + .map(JAXBUtils::createJaxbElement) + .collect(Collectors.toCollection(ArrayList::new)) + ); + serviceCalendar.setOperatingPeriods(operatingPeriodsRelStructure); + } + + return serviceCalendar; + } + } + public static class CreateFlexibleArea extends CreateEntity { private List coordinates; @@ -821,6 +1389,26 @@ public DatedServiceJourney create() { } } + public static class CreateOperatingPeriod + extends CreateEntity { + + private final LocalDate fromDate; + private final LocalDate toDate; + + public CreateOperatingPeriod(int id, LocalDate fromDate, LocalDate toDate) { + super(id); + this.fromDate = fromDate; + this.toDate = toDate; + } + + public OperatingPeriod create() { + return new OperatingPeriod() + .withId(ref()) + .withFromDate(fromDate.atStartOfDay()) + .withToDate(toDate.atStartOfDay()); + } + } + public static class CreateOperatingDay extends CreateEntity { private final LocalDate calendarDate; @@ -839,26 +1427,42 @@ public OperatingDay create() { public static class CreateDayType extends CreateEntity { + private Collection daysOfWeek; + private boolean asSinglePropertyOfDay; + public CreateDayType(int id) { super(id); } + public CreateDayType withDaysOfWeek(DayOfWeekEnumeration... daysOfWeek) { + this.daysOfWeek = Arrays.asList(daysOfWeek); + return this; + } + public DayType create() { - return new DayType().withId(ref()); + DayType dayType = new DayType().withId(ref()); + + if (daysOfWeek != null && !daysOfWeek.isEmpty()) { + return dayType.withProperties( + new PropertiesOfDay_RelStructure() + .withPropertyOfDay(new PropertyOfDay().withDaysOfWeek(daysOfWeek)) + ); + } + return dayType; } } public static class CreateDayTypeAssignment extends CreateEntity { - private CreateDayType DayTypeRef; private LocalDate date; + private final CreateDayType dayTypeRef; private CreateOperatingDay operatingDayRef; - // TODO: create CreateOperatingPeriod - private String operatingPeriodRef; + private CreateOperatingPeriod operatingPeriodRef; - public CreateDayTypeAssignment(int id) { + public CreateDayTypeAssignment(int id, CreateDayType DayTypeRef) { super(id); + this.dayTypeRef = DayTypeRef; } public CreateDayTypeAssignment withDate(LocalDate date) { @@ -878,7 +1482,7 @@ public CreateDayTypeAssignment withOperatingDayRef( } public CreateDayTypeAssignment withOperatingPeriodRef( - String operatingPeriodRef + CreateOperatingPeriod operatingPeriodRef ) { this.operatingPeriodRef = operatingPeriodRef; this.date = null; @@ -888,7 +1492,10 @@ public CreateDayTypeAssignment withOperatingPeriodRef( public DayTypeAssignment create() { DayTypeAssignment dayTypeAssignment = new DayTypeAssignment() - .withId(ref()); + .withId(ref()) + .withDayTypeRef( + createJaxbElement(new DayTypeRefStructure().withRef(dayTypeRef.ref())) + ); Optional .ofNullable(date) @@ -896,14 +1503,16 @@ public DayTypeAssignment create() { Optional .ofNullable(operatingDayRef) + .map(CreateOperatingDay::ref) .ifPresent(ref -> dayTypeAssignment.withOperatingDayRef( - new OperatingDayRefStructure().withRef(ref.ref()) + new OperatingDayRefStructure().withRef(ref) ) ); Optional .ofNullable(operatingPeriodRef) + .map(CreateOperatingPeriod::ref) .ifPresent(ref -> dayTypeAssignment.withOperatingPeriodRef( createJaxbElement(new OperatingPeriodRefStructure().withRef(ref)) @@ -1261,20 +1870,23 @@ public LinkInJourneyPattern create() { } } - public static class CreateDeadRun extends CreateEntity { + public abstract static class CreateJourney + extends CreateEntity { - private final CreateGenericLine lineRef; - private final CreateJourneyPattern journeyPattern; - private final List timetabledPassingTimes = - new ArrayList<>(); + protected final CreateGenericLine line; + protected final CreateJourneyPattern journeyPattern; + protected List timetabledPassingTimes; + protected List dayTypes; + protected AllVehicleModesOfTransportEnumeration transportMode; + protected TransportSubmodeStructure transportSubmode; - public CreateDeadRun( + public CreateJourney( int id, - CreateGenericLine lineRef, + CreateGenericLine line, CreateJourneyPattern journeyPattern ) { super(id); - this.lineRef = lineRef; + this.line = line; this.journeyPattern = journeyPattern; } @@ -1289,117 +1901,173 @@ public CreateTimetabledPassingTime createTimetabledPassingTime( int id, CreateStopPointInJourneyPattern createStopPointInJourneyPattern ) { + if (timetabledPassingTimes == null) { + timetabledPassingTimes = new ArrayList<>(); + } CreateTimetabledPassingTime createTimetabledPassingTime = new CreateTimetabledPassingTime(id, createStopPointInJourneyPattern); timetabledPassingTimes.add(createTimetabledPassingTime); return createTimetabledPassingTime; } + /** + * Creates timetabled passing times for the given stop points in the journey pattern + * + * @param stopPointsInJourneyPattern the stop points in the journey pattern + * @return List of CreateTimetabledPassingTime + */ + public List createTimetabledPassingTimes( + List stopPointsInJourneyPattern + ) { + return IntStream + .range(0, stopPointsInJourneyPattern.size()) + .mapToObj(i -> + createTimetabledPassingTime(i + 1, stopPointsInJourneyPattern.get(i)) + ) + .toList(); + } + + public CreateJourney addDayTypeRef(DayTypeRefStructure dayTypeRef) { + if (dayTypes == null) { + dayTypes = new ArrayList<>(); + } + dayTypes.add(dayTypeRef); + return this; + } + + public CreateJourney addDayTypeRef(CreateDayType dayType) { + return addDayTypeRef(new DayTypeRefStructure().withRef(dayType.ref())); + } + + public CreateJourney addDayTypeRefs(List dayTypes) { + dayTypes.forEach(this::addDayTypeRef); + return this; + } + + public CreateJourney withTransportMode( + AllVehicleModesOfTransportEnumeration transportMode + ) { + this.transportMode = transportMode; + return this; + } + + public CreateJourney withTransportSubmode( + TransportSubmodeStructure transportSubmode + ) { + this.transportSubmode = transportSubmode; + return this; + } + } + + public static class CreateDeadRun extends CreateJourney { + + public CreateDeadRun( + int id, + CreateGenericLine lineRef, + CreateJourneyPattern journeyPattern + ) { + super(id, lineRef, journeyPattern); + } + public DeadRun create() { DeadRun deadRun = new DeadRun() .withId(ref()) .withLineRef( - createJaxbElement(new LineRefStructure().withRef(lineRef.ref())) + createJaxbElement(new LineRefStructure().withRef(line.ref())) ) - .withDayTypes(createEveryDayRefs()) .withJourneyPatternRef( createJaxbElement( new JourneyPatternRefStructure().withRef(journeyPattern.ref()) ) ); - deadRun.withPassingTimes( - new TimetabledPassingTimes_RelStructure() - .withTimetabledPassingTime( - timetabledPassingTimes - .stream() - .map(CreateTimetabledPassingTime::create) - .toList() - ) - ); + if (dayTypes != null) { + deadRun.withDayTypes( + new DayTypeRefs_RelStructure() + .withDayTypeRef( + dayTypes + .stream() + .map(JAXBUtils::createJaxbElement) + .collect(Collectors.toCollection(ArrayList::new)) + ) + ); + } + + if (timetabledPassingTimes != null) { + deadRun.withPassingTimes( + new TimetabledPassingTimes_RelStructure() + .withTimetabledPassingTime( + timetabledPassingTimes + .stream() + .map(CreateTimetabledPassingTime::create) + .toList() + ) + ); + } + + if (transportMode != null) { + deadRun.withTransportMode(transportMode); + } + + if (transportSubmode != null) { + deadRun.withTransportSubmode(transportSubmode); + } return deadRun; } } public static class CreateServiceJourney - extends CreateEntity + extends CreateJourney implements CreateRef { - private final CreateGenericLine line; - private final CreateJourneyPattern journeyPattern; - private final List timetabledPassingTimes = - new ArrayList<>(); - private AllVehicleModesOfTransportEnumeration transportMode; - private TransportSubmodeStructure transportSubmode; - public CreateServiceJourney( int id, - CreateGenericLine line, + CreateGenericLine lineRef, CreateJourneyPattern journeyPattern ) { - super(id); - this.line = line; - this.journeyPattern = journeyPattern; + super(id, lineRef, journeyPattern); } public VehicleJourneyRefStructure refObject() { return NetexEntitiesTestFactory.createServiceJourneyRef(id); } - /** - * Adds a new timetabled passing time with the given id - * - * @param id the id of the timetabled passing time - * @param createStopPointInJourneyPattern the stop point in the journey pattern ref for the timetabled passing time - * @return CreateTimetabledPassingTime - */ - public CreateTimetabledPassingTime createTimetabledPassingTime( - int id, - CreateStopPointInJourneyPattern createStopPointInJourneyPattern - ) { - CreateTimetabledPassingTime createTimetabledPassingTime = - new CreateTimetabledPassingTime(id, createStopPointInJourneyPattern); - timetabledPassingTimes.add(createTimetabledPassingTime); - return createTimetabledPassingTime; - } - - public CreateServiceJourney withTransportMode( - AllVehicleModesOfTransportEnumeration transportMode - ) { - this.transportMode = transportMode; - return this; - } - - public CreateServiceJourney withTransportSubmode( - TransportSubmodeStructure transportSubmode - ) { - this.transportSubmode = transportSubmode; - return this; - } - public ServiceJourney create() { ServiceJourney serviceJourney = new ServiceJourney() .withId(ref()) .withLineRef( createJaxbElement(new LineRefStructure().withRef(line.ref())) ) - .withDayTypes(createEveryDayRefs()) .withJourneyPatternRef( createJaxbElement( new JourneyPatternRefStructure().withRef(journeyPattern.ref()) ) ); - serviceJourney.withPassingTimes( - new TimetabledPassingTimes_RelStructure() - .withTimetabledPassingTime( - timetabledPassingTimes - .stream() - .map(CreateTimetabledPassingTime::create) - .toList() - ) - ); + if (dayTypes != null) { + serviceJourney.withDayTypes( + new DayTypeRefs_RelStructure() + .withDayTypeRef( + dayTypes + .stream() + .map(JAXBUtils::createJaxbElement) + .collect(Collectors.toCollection(ArrayList::new)) + ) + ); + } + + if (timetabledPassingTimes != null) { + serviceJourney.withPassingTimes( + new TimetabledPassingTimes_RelStructure() + .withTimetabledPassingTime( + timetabledPassingTimes + .stream() + .map(CreateTimetabledPassingTime::create) + .toList() + ) + ); + } if (transportMode != null) { serviceJourney.withTransportMode(transportMode); @@ -1620,15 +2288,4 @@ public ServiceLink create() { ); } } - - private static DayTypeRefs_RelStructure createEveryDayRefs() { - return new DayTypeRefs_RelStructure() - .withDayTypeRef(Collections.singleton(createEveryDayRef())); - } - - private static JAXBElement createEveryDayRef() { - return createJaxbElement( - new DayTypeRefStructure().withRef(EVERYDAY.getId()) - ); - } } diff --git a/src/test/java/no/entur/antu/validation/ValidationTest.java b/src/test/java/no/entur/antu/validation/ValidationTest.java index 50038826..3445d492 100644 --- a/src/test/java/no/entur/antu/validation/ValidationTest.java +++ b/src/test/java/no/entur/antu/validation/ValidationTest.java @@ -119,6 +119,30 @@ protected void mockGetServiceJourneyStops( .thenReturn(serviceJourneyStops); } + protected void mockGetServiceJourneyDayTypes( + Map> serviceJourneyDayTypes + ) { + Mockito + .when(netexDataRepositoryMock.serviceJourneyDayTypes(anyString())) + .thenReturn(serviceJourneyDayTypes); + } + + protected void mockGetServiceJourneyOperatingDays( + Map> serviceJourneyOperatingDays + ) { + Mockito + .when(netexDataRepositoryMock.serviceJourneyOperatingDays(anyString())) + .thenReturn(serviceJourneyOperatingDays); + } + + protected void mockGetActiveDays( + Map activeDates + ) { + Mockito + .when(netexDataRepositoryMock.activeDates(anyString())) + .thenReturn(activeDates); + } + protected void mockGetServiceJourneyInterchangeInfo( List serviceJourneyInterchangeInfos ) { diff --git a/src/test/java/no/entur/antu/validation/validator/interchange/waittime/UnexpectedWaitTimeAndActiveDatesValidatorTest.java b/src/test/java/no/entur/antu/validation/validator/interchange/waittime/UnexpectedWaitTimeAndActiveDatesValidatorTest.java new file mode 100644 index 00000000..73283dfb --- /dev/null +++ b/src/test/java/no/entur/antu/validation/validator/interchange/waittime/UnexpectedWaitTimeAndActiveDatesValidatorTest.java @@ -0,0 +1,1022 @@ +package no.entur.antu.validation.validator.interchange.waittime; + +import static java.util.stream.Collectors.toMap; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; +import no.entur.antu.netextestdata.NetexEntitiesTestFactory; +import no.entur.antu.validation.ValidationTest; +import org.entur.netex.validation.validator.ValidationReport; +import org.entur.netex.validation.validator.ValidationReportEntry; +import org.entur.netex.validation.validator.model.ActiveDates; +import org.entur.netex.validation.validator.model.ActiveDatesId; +import org.entur.netex.validation.validator.model.DayTypeId; +import org.entur.netex.validation.validator.model.OperatingDayId; +import org.entur.netex.validation.validator.model.ScheduledStopPointId; +import org.entur.netex.validation.validator.model.ServiceJourneyId; +import org.entur.netex.validation.validator.model.ServiceJourneyInterchangeInfo; +import org.entur.netex.validation.validator.model.ServiceJourneyStop; +import org.junit.jupiter.api.Test; +import org.rutebanken.netex.model.ServiceJourneyInterchange; + +class UnexpectedWaitTimeAndActiveDatesValidatorTest extends ValidationTest { + + private class TestInterchange { + + static class TestServiceJourney { + + private TestServiceJourney(int serviceJourneyId) { + this.serviceJourneyId = serviceJourneyId; + } + + static class TestActiveDates { + + String activeDatesRef; + List activeDates = new ArrayList<>(); + + public ActiveDatesId getActiveDatesId() { + return ActiveDatesId.of(activeDatesRef); + } + + public ActiveDates getActiveDates() { + return new ActiveDates(activeDates); + } + + TestActiveDates withActiveDatesRef(String activeDatesRef) { + this.activeDatesRef = activeDatesRef; + return this; + } + + TestActiveDates addActiveDate(LocalDate activeDate) { + this.activeDates.add(activeDate); + return this; + } + } + + int serviceJourneyId; + int scheduledStopPointId; + List dayTypes = new ArrayList<>(); + List operatingDays = new ArrayList<>(); + LocalTime arrivalTime; + LocalTime departureTime; + int arrivalDayOffset; + int departureDayOffset; + + public ServiceJourneyId serviceJourneyId() { + return new ServiceJourneyId("TST:ServiceJourney:" + serviceJourneyId); + } + + public ScheduledStopPointId scheduledStopPointId() { + return new ScheduledStopPointId( + "TST:ScheduledStopPoint:" + scheduledStopPointId + ); + } + + public ServiceJourneyStop serviceJourneyStop() { + return new ServiceJourneyStop( + scheduledStopPointId(), + arrivalTime, + departureTime, + arrivalDayOffset, + departureDayOffset + ); + } + + public List dayTypeRefs() { + return dayTypes + .stream() + .map(activeDate -> activeDate.activeDatesRef) + .map(DayTypeId::new) + .toList(); + } + + public List operatingDayRefs() { + return operatingDays + .stream() + .map(activeDate -> activeDate.activeDatesRef) + .map(OperatingDayId::new) + .toList(); + } + + public Map activeDatesMap() { + return Stream + .of(dayTypes, operatingDays) + .flatMap(List::stream) + .collect( + toMap( + TestActiveDates::getActiveDatesId, + TestActiveDates::getActiveDates + ) + ); + } + + TestServiceJourney withScheduledStopPointId(int scheduledStopPointId) { + this.scheduledStopPointId = scheduledStopPointId; + return this; + } + + TestServiceJourney addTestDayType( + int dayTypeId, + LocalDate... activeDates + ) { + TestActiveDates testDayTypes = new TestActiveDates() + .withActiveDatesRef("TST:DayType:" + dayTypeId); + Stream.of(activeDates).forEach(testDayTypes::addActiveDate); + this.dayTypes.add(testDayTypes); + return this; + } + + TestServiceJourney addTestOperatingDays( + int operatingDayId, + LocalDate... activeDates + ) { + TestActiveDates testOperatingDays = new TestActiveDates() + .withActiveDatesRef("TST:OperatingDay:" + operatingDayId); + Stream.of(activeDates).forEach(testOperatingDays::addActiveDate); + this.operatingDays.add(testOperatingDays); + return this; + } + + TestServiceJourney withArrivalTime(LocalTime arrivalTime) { + this.arrivalTime = arrivalTime; + return this; + } + + TestServiceJourney withDepartureTime(LocalTime departureTime) { + this.departureTime = departureTime; + return this; + } + + TestServiceJourney withArrivalDayOffset(int arrivalDayOffset) { + this.arrivalDayOffset = arrivalDayOffset; + return this; + } + + TestServiceJourney withDepartureDayOffset(int departureDayOffset) { + this.departureDayOffset = departureDayOffset; + return this; + } + } + + private TestServiceJourney fromServiceJourney; + private TestServiceJourney toServiceJourney; + + TestServiceJourney newTestServiceJourney(int serviceJourneyId) { + return new TestServiceJourney(serviceJourneyId); + } + + TestInterchange withFromServiceJourney( + TestServiceJourney fromServiceJourney + ) { + this.fromServiceJourney = fromServiceJourney; + return this; + } + + TestInterchange withToServiceJourney(TestServiceJourney toServiceJourney) { + this.toServiceJourney = toServiceJourney; + return this; + } + + public void doMock() { + mockGetServiceJourneyStops( + Map.of( + fromServiceJourney.serviceJourneyId(), + List.of(fromServiceJourney.serviceJourneyStop()), + toServiceJourney.serviceJourneyId(), + List.of(toServiceJourney.serviceJourneyStop()) + ) + ); + + mockGetServiceJourneyDayTypes( + Map.of( + fromServiceJourney.serviceJourneyId(), + fromServiceJourney.dayTypeRefs(), + toServiceJourney.serviceJourneyId(), + toServiceJourney.dayTypeRefs() + ) + ); + + mockGetServiceJourneyOperatingDays( + Map.of( + fromServiceJourney.serviceJourneyId(), + fromServiceJourney.operatingDayRefs(), + toServiceJourney.serviceJourneyId(), + toServiceJourney.operatingDayRefs() + ) + ); + + mockGetActiveDays( + Stream + .of( + fromServiceJourney.activeDatesMap(), + toServiceJourney.activeDatesMap() + ) + .flatMap(map -> map.entrySet().stream()) + .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)) + ); + } + + private ValidationReport runTest() { + NetexEntitiesTestFactory factory = new NetexEntitiesTestFactory(); + + ServiceJourneyInterchange serviceJourneyInterchange = factory + .createServiceJourneyInterchange(1) + .withFromPointRef( + NetexEntitiesTestFactory.createScheduledStopPointRef( + fromServiceJourney.scheduledStopPointId + ) + ) + .withToPointRef( + NetexEntitiesTestFactory.createScheduledStopPointRef( + toServiceJourney.scheduledStopPointId + ) + ) + .withFromJourneyRef( + NetexEntitiesTestFactory.createServiceJourneyRef( + fromServiceJourney.serviceJourneyId + ) + ) + .withToJourneyRef( + NetexEntitiesTestFactory.createServiceJourneyRef( + toServiceJourney.serviceJourneyId + ) + ) + .create(); + + mockGetServiceJourneyInterchangeInfo( + List.of( + ServiceJourneyInterchangeInfo.of( + "test.xml", + serviceJourneyInterchange + ) + ) + ); + + return runDatasetValidation( + UnexpectedWaitTimeAndActiveDatesValidator.class + ); + } + } + + @Test + void testValidWaitTimeAndActiveDays() { + TestInterchange testInterchange = new TestInterchange(); + testInterchange + .withFromServiceJourney( + testInterchange + .newTestServiceJourney(1) + .withScheduledStopPointId(1) + .addTestDayType(1, LocalDate.of(2024, 11, 1)) + .withDepartureTime(LocalTime.of(9, 53, 0)) + ) + .withToServiceJourney( + testInterchange + .newTestServiceJourney(2) + .withScheduledStopPointId(2) + .addTestDayType(2, LocalDate.of(2024, 11, 1)) + .withArrivalTime(LocalTime.of(9, 56, 0)) + ); + + testInterchange.doMock(); + + ValidationReport validationReport = testInterchange.runTest(); + + assertThat(validationReport.getValidationReportEntries().size(), is(0)); + } + + /* + * Interchange with feeder service journey with days types and consumer service journey with dated service journey. + * Is this a real world scenario? + */ + @Test + void testValidWaitTimeAndActiveDays_MixOfDaysTypesAndDatedServiceJourneys() { + TestInterchange testInterchange = new TestInterchange(); + testInterchange + .withFromServiceJourney( + testInterchange + .newTestServiceJourney(1) + .withScheduledStopPointId(1) + .addTestDayType(1, LocalDate.of(2024, 11, 1)) + .withDepartureTime(LocalTime.of(9, 53, 0)) + ) + .withToServiceJourney( + testInterchange + .newTestServiceJourney(2) + .withScheduledStopPointId(2) + .addTestOperatingDays(2, LocalDate.of(2024, 11, 1)) + .withArrivalTime(LocalTime.of(9, 56, 0)) + ); + + testInterchange.doMock(); + + ValidationReport validationReport = testInterchange.runTest(); + + assertThat(validationReport.getValidationReportEntries().size(), is(0)); + } + + @Test + void testValidWaitTimeAndActiveDaysWithDaysOffSet() { + TestInterchange testInterchange = new TestInterchange(); + testInterchange + .withFromServiceJourney( + testInterchange + .newTestServiceJourney(1) + .withScheduledStopPointId(1) + .addTestDayType(1, LocalDate.of(2024, 11, 1)) + .withDepartureTime(LocalTime.of(9, 53, 0)) + .withDepartureDayOffset(3) + ) + .withToServiceJourney( + testInterchange + .newTestServiceJourney(2) + .withScheduledStopPointId(2) + .addTestDayType(2, LocalDate.of(2024, 11, 1)) + .withArrivalTime(LocalTime.of(9, 56, 0)) + .withArrivalDayOffset(3) + ); + + testInterchange.doMock(); + ValidationReport validationReport = testInterchange.runTest(); + + assertThat(validationReport.getValidationReportEntries().size(), is(0)); + } + + @Test + void testWaitTimeEqualsWarningLimit() { + TestInterchange testInterchange = new TestInterchange(); + testInterchange + .withFromServiceJourney( + testInterchange + .newTestServiceJourney(1) + .withScheduledStopPointId(1) + .addTestDayType(1, LocalDate.of(2024, 11, 1)) + .withDepartureTime(LocalTime.of(9, 53, 0)) + ) + .withToServiceJourney( + testInterchange + .newTestServiceJourney(2) + .withScheduledStopPointId(2) + .addTestDayType(2, LocalDate.of(2024, 11, 1)) + .withArrivalTime(LocalTime.of(9, 53, 0)) + ); + + testInterchange.doMock(); + ValidationReport validationReport = testInterchange.runTest(); + + assertThat(validationReport.getValidationReportEntries().size(), is(0)); + } + + /* + * Test that the wait time between two service journeys is more than the warning limit. + * Waiting Limit is 1 hour + */ + @Test + void testWaitTimeExceedingWarningLimit() { + TestInterchange testInterchange = new TestInterchange(); + testInterchange + .withFromServiceJourney( + testInterchange + .newTestServiceJourney(1) + .withScheduledStopPointId(1) + .addTestDayType(1, LocalDate.of(2024, 11, 1)) + .withDepartureTime(LocalTime.of(9, 53, 0)) + ) + .withToServiceJourney( + testInterchange + .newTestServiceJourney(2) + .withScheduledStopPointId(2) + .addTestDayType(2, LocalDate.of(2024, 11, 1)) + .withArrivalTime(LocalTime.of(10, 55, 0)) // waiting time 1 hour and 2 minutes + ); + + testInterchange.doMock(); + ValidationReport validationReport = testInterchange.runTest(); + + assertThat(validationReport.getValidationReportEntries().size(), is(1)); + + assertThat( + validationReport + .getValidationReportEntries() + .stream() + .findFirst() + .map(ValidationReportEntry::getName) + .orElse(null), + is("Wait time in interchange exceeds warning limit") + ); + } + + /* + * Test that the wait time between two service journeys is less than the warning limit. + * Waiting Limit is 1 hour + */ + @Test + void testWaitTimeExceedingWarningLimitWithDayOffset() { + TestInterchange testInterchange = new TestInterchange(); + testInterchange + .withFromServiceJourney( + testInterchange + .newTestServiceJourney(1) + .withScheduledStopPointId(1) + .addTestDayType(1, LocalDate.of(2024, 11, 2)) + .withDepartureTime(LocalTime.of(9, 53, 0)) + ) + .withToServiceJourney( + testInterchange + .newTestServiceJourney(2) + .withScheduledStopPointId(2) + .addTestDayType(2, LocalDate.of(2024, 11, 1)) + .withArrivalTime(LocalTime.of(10, 55, 0)) // waiting time 1 hour and 2 minutes + .withArrivalDayOffset(1) + ); + + testInterchange.doMock(); + ValidationReport validationReport = testInterchange.runTest(); + + assertThat(validationReport.getValidationReportEntries().size(), is(1)); + + assertThat( + validationReport + .getValidationReportEntries() + .stream() + .findFirst() + .map(ValidationReportEntry::getName) + .orElse(null), + is("Wait time in interchange exceeds warning limit") + ); + } + + /* + * Test that the wait time between two service journeys is less than the maximum limit. + * Maximum Limit is 3 hours + */ + @Test + void testWaitTimeExceedingErrorLimit() { + TestInterchange testInterchange = new TestInterchange(); + testInterchange + .withFromServiceJourney( + testInterchange + .newTestServiceJourney(1) + .withScheduledStopPointId(1) + .addTestDayType(1, LocalDate.of(2024, 11, 1)) + .withDepartureTime(LocalTime.of(9, 53, 0)) + ) + .withToServiceJourney( + testInterchange + .newTestServiceJourney(2) + .withScheduledStopPointId(2) + .addTestDayType(2, LocalDate.of(2024, 11, 1)) + .withArrivalTime(LocalTime.of(12, 55, 0)) // waiting time 3 hours 2 minutes + ); + + testInterchange.doMock(); + ValidationReport validationReport = testInterchange.runTest(); + + assertThat(validationReport.getValidationReportEntries().size(), is(1)); + + assertThat( + validationReport + .getValidationReportEntries() + .stream() + .findFirst() + .map(ValidationReportEntry::getName) + .orElse(null), + is("Wait time in interchange exceeds maximum limit") + ); + } + + /* + * Test that the wait time between two service journeys is more than the maximum limit. + * Maximum Limit is 3 hours + */ + @Test + void testWaitTimeExceedingErrorLimitWithDayOffset() { + TestInterchange testInterchange = new TestInterchange(); + + testInterchange + .withFromServiceJourney( + testInterchange + .newTestServiceJourney(1) + .withScheduledStopPointId(1) + .addTestDayType(1, LocalDate.of(2024, 11, 1)) + .withDepartureTime(LocalTime.of(9, 53, 0)) + .withDepartureDayOffset(1) + ) + .withToServiceJourney( + testInterchange + .newTestServiceJourney(2) + .withScheduledStopPointId(2) + .addTestDayType(2, LocalDate.of(2024, 11, 2)) + .withArrivalTime(LocalTime.of(12, 55, 0)) // waiting time 3 hours 2 minutes + ); + + testInterchange.doMock(); + ValidationReport validationReport = testInterchange.runTest(); + + assertThat(validationReport.getValidationReportEntries().size(), is(1)); + + assertThat( + validationReport + .getValidationReportEntries() + .stream() + .findFirst() + .map(ValidationReportEntry::getName) + .orElse(null), + is("Wait time in interchange exceeds maximum limit") + ); + } + + @Test + void testNoSharedActiveDays_oneDayTypePerServiceJourney() { + TestInterchange testInterchange = new TestInterchange(); + testInterchange + .withFromServiceJourney( + testInterchange + .newTestServiceJourney(1) + .withScheduledStopPointId(1) + .addTestDayType(1, LocalDate.of(2024, 11, 1)) + .withDepartureTime(LocalTime.of(9, 53, 0)) + ) + .withToServiceJourney( + testInterchange + .newTestServiceJourney(2) + .withScheduledStopPointId(2) + .addTestDayType(2, LocalDate.of(2024, 11, 2)) + .withArrivalTime(LocalTime.of(9, 56, 0)) + ); + + testInterchange.doMock(); + + ValidationReport validationReport = testInterchange.runTest(); + + assertThat(validationReport.getValidationReportEntries().size(), is(1)); + + assertThat( + validationReport + .getValidationReportEntries() + .stream() + .findFirst() + .map(ValidationReportEntry::getName) + .orElse(null), + is("No shared active date found in interchange") + ); + } + + @Test + void testNoSharedActiveDays_multipleDayTypePerServiceJourney() { + TestInterchange testInterchange = new TestInterchange(); + testInterchange + .withFromServiceJourney( + testInterchange + .newTestServiceJourney(1) + .withScheduledStopPointId(1) + .addTestDayType(1, LocalDate.of(2024, 11, 1)) + .addTestDayType(2, LocalDate.of(2024, 11, 3)) + .addTestDayType(3, LocalDate.of(2024, 11, 5)) + .addTestDayType(4, LocalDate.of(2024, 11, 7)) + .withDepartureTime(LocalTime.of(9, 53, 0)) + ) + .withToServiceJourney( + testInterchange + .newTestServiceJourney(2) + .withScheduledStopPointId(2) + .addTestDayType(5, LocalDate.of(2024, 11, 2)) + .addTestDayType(6, LocalDate.of(2024, 11, 4)) + .addTestDayType(7, LocalDate.of(2024, 11, 6)) + .addTestDayType(8, LocalDate.of(2024, 11, 8)) + .addTestDayType(9, LocalDate.of(2024, 11, 10)) + .withArrivalTime(LocalTime.of(9, 56, 0)) + ); + + testInterchange.doMock(); + + ValidationReport validationReport = testInterchange.runTest(); + + assertThat(validationReport.getValidationReportEntries().size(), is(1)); + + assertThat( + validationReport + .getValidationReportEntries() + .stream() + .findFirst() + .map(ValidationReportEntry::getName) + .orElse(null), + is("No shared active date found in interchange") + ); + } + + @Test + void testHasSharedActiveDay_multipleDayTypePerServiceJourney() { + TestInterchange testInterchange = new TestInterchange(); + testInterchange + .withFromServiceJourney( + testInterchange + .newTestServiceJourney(1) + .withScheduledStopPointId(1) + .addTestDayType(1, LocalDate.of(2024, 11, 1)) + .addTestDayType(2, LocalDate.of(2024, 11, 3)) // shared active date + .addTestDayType(3, LocalDate.of(2024, 11, 5)) + .addTestDayType(4, LocalDate.of(2024, 11, 7)) + .withDepartureTime(LocalTime.of(9, 53, 0)) + ) + .withToServiceJourney( + testInterchange + .newTestServiceJourney(2) + .withScheduledStopPointId(2) + .addTestDayType(5, LocalDate.of(2024, 11, 2)) + .addTestDayType(6, LocalDate.of(2024, 11, 3)) // shared active date + .addTestDayType(7, LocalDate.of(2024, 11, 6)) + .addTestDayType(8, LocalDate.of(2024, 11, 8)) + .addTestDayType(9, LocalDate.of(2024, 11, 10)) + .withArrivalTime(LocalTime.of(9, 56, 0)) + ); + + testInterchange.doMock(); + + ValidationReport validationReport = testInterchange.runTest(); + + assertThat(validationReport.getValidationReportEntries().size(), is(0)); + } + + @Test + void testValidWaitTimeAndActiveDays_datedServiceJourneys() { + TestInterchange testInterchange = new TestInterchange(); + testInterchange + .withFromServiceJourney( + testInterchange + .newTestServiceJourney(1) + .withScheduledStopPointId(1) + .addTestOperatingDays(1, LocalDate.of(2024, 11, 1)) + .withDepartureTime(LocalTime.of(9, 53, 0)) + ) + .withToServiceJourney( + testInterchange + .newTestServiceJourney(2) + .withScheduledStopPointId(2) + .addTestOperatingDays(2, LocalDate.of(2024, 11, 1)) + .withArrivalTime(LocalTime.of(9, 56, 0)) + ); + + testInterchange.doMock(); + + ValidationReport validationReport = testInterchange.runTest(); + + assertThat(validationReport.getValidationReportEntries().size(), is(0)); + } + + @Test + void testValidWaitTimeAndActiveDaysWithDaysOffSet_datedServiceJourneys() { + TestInterchange testInterchange = new TestInterchange(); + testInterchange + .withFromServiceJourney( + testInterchange + .newTestServiceJourney(1) + .withScheduledStopPointId(1) + .addTestOperatingDays(1, LocalDate.of(2024, 11, 1)) + .withDepartureTime(LocalTime.of(9, 53, 0)) + .withDepartureDayOffset(3) + ) + .withToServiceJourney( + testInterchange + .newTestServiceJourney(2) + .withScheduledStopPointId(2) + .addTestOperatingDays(2, LocalDate.of(2024, 11, 1)) + .withArrivalTime(LocalTime.of(9, 56, 0)) + .withArrivalDayOffset(3) + ); + + testInterchange.doMock(); + ValidationReport validationReport = testInterchange.runTest(); + + assertThat(validationReport.getValidationReportEntries().size(), is(0)); + } + + @Test + void testWaitTimeEqualsWarningLimit_datedServiceJourneys() { + TestInterchange testInterchange = new TestInterchange(); + testInterchange + .withFromServiceJourney( + testInterchange + .newTestServiceJourney(1) + .withScheduledStopPointId(1) + .addTestOperatingDays(1, LocalDate.of(2024, 11, 1)) + .withDepartureTime(LocalTime.of(9, 53, 0)) + ) + .withToServiceJourney( + testInterchange + .newTestServiceJourney(2) + .withScheduledStopPointId(2) + .addTestOperatingDays(2, LocalDate.of(2024, 11, 1)) + .withArrivalTime(LocalTime.of(9, 53, 0)) + ); + + testInterchange.doMock(); + ValidationReport validationReport = testInterchange.runTest(); + + assertThat(validationReport.getValidationReportEntries().size(), is(0)); + } + + /* + * Test that the wait time between two service journeys is more than the warning limit. + * Waiting Limit is 1 hour + */ + @Test + void testWaitTimeExceedingWarningLimit_datedServiceJourneys() { + TestInterchange testInterchange = new TestInterchange(); + testInterchange + .withFromServiceJourney( + testInterchange + .newTestServiceJourney(1) + .withScheduledStopPointId(1) + .addTestOperatingDays(1, LocalDate.of(2024, 11, 1)) + .withDepartureTime(LocalTime.of(9, 53, 0)) + ) + .withToServiceJourney( + testInterchange + .newTestServiceJourney(2) + .withScheduledStopPointId(2) + .addTestOperatingDays(2, LocalDate.of(2024, 11, 1)) + .withArrivalTime(LocalTime.of(10, 55, 0)) // waiting time 1 hour and 2 minutes + ); + + testInterchange.doMock(); + ValidationReport validationReport = testInterchange.runTest(); + + assertThat(validationReport.getValidationReportEntries().size(), is(1)); + + assertThat( + validationReport + .getValidationReportEntries() + .stream() + .findFirst() + .map(ValidationReportEntry::getName) + .orElse(null), + is("Wait time in interchange exceeds warning limit") + ); + } + + /* + * Test that the wait time between two service journeys is more than the warning limit. + * Waiting Limit is 1 hour + */ + @Test + void testWaitTimeExceedingWarningLimitWithDayOffset_datedServiceJourneys() { + TestInterchange testInterchange = new TestInterchange(); + testInterchange + .withFromServiceJourney( + testInterchange + .newTestServiceJourney(1) + .withScheduledStopPointId(1) + .addTestOperatingDays(1, LocalDate.of(2024, 11, 2)) + .withDepartureTime(LocalTime.of(9, 53, 0)) + ) + .withToServiceJourney( + testInterchange + .newTestServiceJourney(2) + .withScheduledStopPointId(2) + .addTestOperatingDays(2, LocalDate.of(2024, 11, 1)) + .withArrivalTime(LocalTime.of(10, 55, 0)) // waiting time 1 hour and 2 minutes + .withArrivalDayOffset(1) + ); + + testInterchange.doMock(); + ValidationReport validationReport = testInterchange.runTest(); + + assertThat(validationReport.getValidationReportEntries().size(), is(1)); + + assertThat( + validationReport + .getValidationReportEntries() + .stream() + .findFirst() + .map(ValidationReportEntry::getName) + .orElse(null), + is("Wait time in interchange exceeds warning limit") + ); + } + + /* + * Test that the wait time between two service journeys is more than the maximum limit. + * Maximum Limit is 3 hours + */ + @Test + void testWaitTimeExceedingErrorLimit_datedServiceJourneys() { + TestInterchange testInterchange = new TestInterchange(); + testInterchange + .withFromServiceJourney( + testInterchange + .newTestServiceJourney(1) + .withScheduledStopPointId(1) + .addTestOperatingDays(1, LocalDate.of(2024, 11, 1)) + .withDepartureTime(LocalTime.of(9, 53, 0)) + ) + .withToServiceJourney( + testInterchange + .newTestServiceJourney(2) + .withScheduledStopPointId(2) + .addTestOperatingDays(2, LocalDate.of(2024, 11, 1)) + .withArrivalTime(LocalTime.of(12, 55, 0)) // waiting time 3 hours 2 minutes + ); + + testInterchange.doMock(); + ValidationReport validationReport = testInterchange.runTest(); + + assertThat(validationReport.getValidationReportEntries().size(), is(1)); + + assertThat( + validationReport + .getValidationReportEntries() + .stream() + .findFirst() + .map(ValidationReportEntry::getName) + .orElse(null), + is("Wait time in interchange exceeds maximum limit") + ); + } + + /* + * Test that the wait time between two service journeys is less than the maximum limit. + * Maximum Limit is 3 hours + */ + @Test + void testWaitTimeExceedingErrorLimitWithDayOffset_datedServiceJourneys() { + TestInterchange testInterchange = new TestInterchange(); + + testInterchange + .withFromServiceJourney( + testInterchange + .newTestServiceJourney(1) + .withScheduledStopPointId(1) + .addTestOperatingDays(1, LocalDate.of(2024, 11, 1)) + .withDepartureTime(LocalTime.of(9, 53, 0)) + .withDepartureDayOffset(1) + ) + .withToServiceJourney( + testInterchange + .newTestServiceJourney(2) + .withScheduledStopPointId(2) + .addTestOperatingDays(2, LocalDate.of(2024, 11, 2)) + .withArrivalTime(LocalTime.of(12, 55, 0)) // waiting time 3 hours 2 minutes + ); + + testInterchange.doMock(); + ValidationReport validationReport = testInterchange.runTest(); + + assertThat(validationReport.getValidationReportEntries().size(), is(1)); + + assertThat( + validationReport + .getValidationReportEntries() + .stream() + .findFirst() + .map(ValidationReportEntry::getName) + .orElse(null), + is("Wait time in interchange exceeds maximum limit") + ); + } + + @Test + void testNoSharedActiveDays_datedServiceJourneys() { + TestInterchange testInterchange = new TestInterchange(); + testInterchange + .withFromServiceJourney( + testInterchange + .newTestServiceJourney(1) + .withScheduledStopPointId(1) + .addTestOperatingDays(1, LocalDate.of(2024, 11, 1)) + .withDepartureTime(LocalTime.of(9, 53, 0)) + ) + .withToServiceJourney( + testInterchange + .newTestServiceJourney(2) + .withScheduledStopPointId(2) + .addTestOperatingDays(2, LocalDate.of(2024, 11, 2)) + .withArrivalTime(LocalTime.of(9, 56, 0)) + ); + + testInterchange.doMock(); + + ValidationReport validationReport = testInterchange.runTest(); + + assertThat(validationReport.getValidationReportEntries().size(), is(1)); + + assertThat( + validationReport + .getValidationReportEntries() + .stream() + .findFirst() + .map(ValidationReportEntry::getName) + .orElse(null), + is("No shared active date found in interchange") + ); + } + + @Test + void testNoDatedServiceJourneyOrDayTypeExistsForConsumerServiceJourney() { + TestInterchange testInterchange = new TestInterchange(); + testInterchange + .withFromServiceJourney( + testInterchange + .newTestServiceJourney(1) + .withScheduledStopPointId(1) + .addTestDayType(1, LocalDate.of(2024, 11, 1)) + .withDepartureTime(LocalTime.of(9, 53, 0)) + ) + .withToServiceJourney( + testInterchange + .newTestServiceJourney(2) + .withScheduledStopPointId(2) + .withArrivalTime(LocalTime.of(9, 56, 0)) + ); + + testInterchange.doMock(); + + ValidationReport validationReport = testInterchange.runTest(); + + assertThat(validationReport.getValidationReportEntries().size(), is(1)); + + assertThat( + validationReport + .getValidationReportEntries() + .stream() + .findFirst() + .map(ValidationReportEntry::getName) + .orElse(null), + is("No shared active date found in interchange") + ); + } + + @Test + void testNoDatedServiceJourneyOrDayTypeExistsForFeederServiceJourney() { + TestInterchange testInterchange = new TestInterchange(); + testInterchange + .withFromServiceJourney( + testInterchange + .newTestServiceJourney(5) + .withScheduledStopPointId(1) + .withDepartureTime(LocalTime.of(9, 53, 0)) + ) + .withToServiceJourney( + testInterchange + .newTestServiceJourney(6) + .withScheduledStopPointId(2) + .addTestOperatingDays(2, LocalDate.of(2024, 11, 1)) + .withArrivalTime(LocalTime.of(9, 56, 0)) + ); + + testInterchange.doMock(); + + ValidationReport validationReport = testInterchange.runTest(); + + assertThat(validationReport.getValidationReportEntries().size(), is(1)); + + assertThat( + validationReport + .getValidationReportEntries() + .stream() + .findFirst() + .map(ValidationReportEntry::getName) + .orElse(null), + is("No shared active date found in interchange") + ); + } + + @Test + void testNoDatedServiceJourneyOrDayTypeExistsForBothServiceJourneys() { + TestInterchange testInterchange = new TestInterchange(); + testInterchange + .withFromServiceJourney( + testInterchange + .newTestServiceJourney(5) + .withScheduledStopPointId(1) + .withDepartureTime(LocalTime.of(9, 53, 0)) + ) + .withToServiceJourney( + testInterchange + .newTestServiceJourney(6) + .withScheduledStopPointId(2) + .withArrivalTime(LocalTime.of(9, 56, 0)) + ); + + testInterchange.doMock(); + + ValidationReport validationReport = testInterchange.runTest(); + + assertThat(validationReport.getValidationReportEntries().size(), is(1)); + + assertThat( + validationReport + .getValidationReportEntries() + .stream() + .findFirst() + .map(ValidationReportEntry::getName) + .orElse(null), + is("No shared active date found in interchange") + ); + } +} diff --git a/src/test/java/no/entur/antu/validation/validator/servicejourney/servicealteration/InvalidServiceAlterationValidatorTest.java b/src/test/java/no/entur/antu/validation/validator/servicejourney/servicealteration/InvalidServiceAlterationValidatorTest.java index 27cab16f..71f64b08 100644 --- a/src/test/java/no/entur/antu/validation/validator/servicejourney/servicealteration/InvalidServiceAlterationValidatorTest.java +++ b/src/test/java/no/entur/antu/validation/validator/servicejourney/servicealteration/InvalidServiceAlterationValidatorTest.java @@ -36,7 +36,10 @@ private NetexEntitiesTestFactory.CreateDatedServiceJourney datedServiceJourneyDr id, netexEntitiesTestFactory.createJourneyPattern(id) ), - netexEntitiesTestFactory.createOperatingDay(id, LocalDate.of(2024, 12, 1)) + new NetexEntitiesTestFactory.CreateOperatingDay( + id, + LocalDate.of(2024, 12, 1) + ) ); } diff --git a/src/test/java/no/entur/antu/validation/validator/servicejourney/servicealteration/MissingReplacementValidatorTest.java b/src/test/java/no/entur/antu/validation/validator/servicejourney/servicealteration/MissingReplacementValidatorTest.java index 85581589..bc459921 100644 --- a/src/test/java/no/entur/antu/validation/validator/servicejourney/servicealteration/MissingReplacementValidatorTest.java +++ b/src/test/java/no/entur/antu/validation/validator/servicejourney/servicealteration/MissingReplacementValidatorTest.java @@ -27,12 +27,18 @@ private ValidationReport runValidation( private NetexEntitiesTestFactory.CreateDatedServiceJourney datedServiceJourneyDraft( int id, - NetexEntitiesTestFactory testData + NetexEntitiesTestFactory netexEntitiesTestFactory ) { - return testData.createDatedServiceJourney( + return netexEntitiesTestFactory.createDatedServiceJourney( id, - testData.createServiceJourney(id, testData.createJourneyPattern(id)), - testData.createOperatingDay(id, LocalDate.parse("2024-12-01")) + netexEntitiesTestFactory.createServiceJourney( + id, + netexEntitiesTestFactory.createJourneyPattern(id) + ), + new NetexEntitiesTestFactory.CreateOperatingDay( + id, + LocalDate.parse("2024-12-01") + ) ); } diff --git a/src/test/java/no/entur/antu/validation/validator/servicejourney/speed/UnexpectedSpeedValidatorTest.java b/src/test/java/no/entur/antu/validation/validator/servicejourney/speed/UnexpectedSpeedValidatorTest.java index f9e1d8a1..a5c1b70a 100644 --- a/src/test/java/no/entur/antu/validation/validator/servicejourney/speed/UnexpectedSpeedValidatorTest.java +++ b/src/test/java/no/entur/antu/validation/validator/servicejourney/speed/UnexpectedSpeedValidatorTest.java @@ -17,6 +17,7 @@ import org.junit.jupiter.api.Test; import org.rutebanken.netex.model.AllVehicleModesOfTransportEnumeration; import org.rutebanken.netex.model.BusSubmodeEnumeration; +import org.rutebanken.netex.model.ServiceJourney; import org.rutebanken.netex.model.TransportSubmodeStructure; class UnexpectedSpeedValidatorTest extends ValidationTest { @@ -224,7 +225,7 @@ private ValidationReport runTestWithQuayCoordinates( List stopPointInJourneyPatterns = createJourneyPattern.createStopPointsInJourneyPattern(4); - NetexEntitiesTestFactory.CreateServiceJourney createServiceJourney = + NetexEntitiesTestFactory.CreateJourney createServiceJourney = netexEntitiesTestFactory .createServiceJourney(createJourneyPattern) .withTransportMode(AllVehicleModesOfTransportEnumeration.BUS)